Suppose that you have a Bar class with a method boing:
public class Bar
{
public void boing() {
// ...
}
}
Suppose one day you refactor your classes to create a superclass
and this method gets pushed up to the superclass.
public class Bar
extends Foo
{
}
public class Foo
{
public void boing() {
}
}
This shouldn't be a problem. If you write code that only uses
Bar, it shouldn't matter which version of Java you compile/run with
or which version of your library you compile against/run against.
As long as you don't reference the incompatable change, then you should
ne ok. But you aren't. Here's a simple user of the class.
public class Client
{
static Bar b = new Bar();
public static void main(String[] args) {
b.boing();
}
}
In Java 1.4, no matter which version of the library you compile Client
against, you get the same compilation result:
What is happening here is that the 1.3 compiler finds the boing()
method on the Foo superclass and uses the Foo.boing() method signature
in the invocation. The JVM doesn't really care whether you talk about
the boing() method on Foo or on Bar. Even if both Foo and Bar
implemented the method and your bytecode invoked the boing() method on
Foo, you'd still get version on Bar. That's how virtual dispatch
works. No matter how high up in the inheritence chain you are, you
still get the right method.
So, then what is the problem? The problem is that if you run the
code compiled against the refactored library with the original library
then the code will fail. You'll get a NoClassDefFoundError, telling
you there's no such class as Foo. The Client doesn't reference Foo
anywhere, but the 1.3 compiler creates a hidden dependency.
So, just compile on 1.4 and everything will be fine. You can
still run the 1.4 compiled code in 1.3 without a problem, right? For
the most part yet, but you can still get bit by subtle class
incompatabilities. I'm not talking about real incompatabilities like
using methods that would cause the code to not compile under 1.3 at
all. I'm talking about something like this:
public class SB
{
public static void main(String[] args) {
StringBuffer x = new StringBuffer("x");
StringBuffer y = new StringBuffer("y");
y.append(x);
x.append(y);
System.out.println(x);
}
}
Try compiling this class in 1.3 and 1.4 and then running in both
1.3 and 1.4. This code is valid in 1.3 and 1.4, but if you compile
using 1.4 and run using 1.3, you'll get a NoSuchMethodError.
(if you don't see why, look at the APIs and the then use javap
to dissasseble the class file)
Right now, using the 1.4 compiler with the 1.3 classes seems to
solve all of our compatability issues. But, it's hard to be 100% sure
that we don't have some hidden problems waiting to jump out at us. If
you are curious where the Foo/Bar example comes from, it hit us with
Struts. In 1.0 ActionForward had no super class, but in 1.1 it is
under ForwardConfig. (see my prior entry about using Shell Scripts to
track down the NoClassDefError) The best solution for us is to split our code
base and compile against the version of Struts that we will be
deploying against. (and the same Java version) But, we really
shouldn't have to. As long as we aren't using incompatable features,
it really should just work. Oh well...