Re: Type soundness issues in Java

Kim Bruce <kim@cs.williams.edu> writes:
> I tested Martin's example (below) on the Metrowerks compiler (and IDE)
> running on a Macintosh.  It reports a compile-time error when the
> "implements I" after I dropped it from class A and selected "run".  It
> no doubt succeeds at this because it keeps track of dependencies and
> recompiles what it thinks needs recompiling when I ask it to run
> (e.g., it has the equivalent of its own dynamically configured make
> file).  This is obviously the right thing to do as changes to the
> interface of a class change what should be legal in other classes.
> The Java specification should obviously be rewritten to handle this
> properly.

I agree that development environments should automatically do the
necessary recompilations, if possible. But in general some of the
source files that ought to be recompiled might not be available. For
example, if a change is made to a source file in a widely reused
library, then many source files using that library could need
recompiling. The library developer might not be able to carry out
those recompilations themselves, or otherwise ensure that all the
recompilations take place.

Imagine if the Java language specification said that after changes to
source files, the compiler/development environment should do all
necessary recompilations. This would imply that in cases where the
compiler cannot do all necessary recompilations, due to the relevant
source files not being available, the source code changes would be
disallowed. This would be very restrictive: For example, it would
prevent Sun evolving the Java platform by adding public methods to its
non-final classes (see example below). In practice, that part of the
specification would be ignored when it was inconvenient, and an ad-hoc
concept of binary compatibility would be developed.

Instead, the language specification encourages the "recompile when
necessary" approach (also known as selective recompilation) in the
first paragraph of the chapter on binary compatibility, but in the
next paragraph explains why that isn't always
appropriate. Consequently, it goes on to cover binary compatibility,
describing which changes you can make to source files even when
recompilations of other source files are impossible. (Note that it
does say that the change in Martin's example does not preserve binary
compatibility). So I think the the Java language specification has
already been written to handle these issues properly (though it is by
no means perfect).

As for determining in which situations it is best to use selective
recompilation, and in which situations it is best to use binary
compatibility, I don't think that is a language issue.

The example I mentioned above:

Compile these two classes:

------------------------ A.java------------------------------

public class A {

------------------------ B.java------------------------------

public class B extends A
    public int m() {
	return 42;

    static public void main(String args[]) {
	B b = new B();
	System.out.println("b.m() returned " + b.m());


$ javac A.java B.java
$ java B
b.m() returned 42

Now change A.java as follows and recompile A.java only:

------------------------ A.java------------------------------

public class A {
    public void m() {
	System.out.println("Surely adding a method is ok?");


$ javac A.java
$ java B
b.m() returned 42

So that change to A seems to be binary compatible. But when you
recompile B:

$ javac B.java
B.java:3: Method redefined with different return type: int m() was void m()
    public int m() {
1 error

So for any non-final class which is modified by adding a public
method, it is easy to devise a source file for a subclass which will
compile before the change but not afterwards.

David Wragg