The Phone Call
Me: Did you use the app Jar file from the build?
Them: No… just sync’ed out of your repository and built it manually with javac.
Me: Were you using all the files from the correct branch?
Them: Oh yes, got the app sources from the main branch. Oh… we used the libraries from the other branch, but we only needed the app jar and it seemed to build fine.
Me: (Stunned. Surely they can’t expect that to work reliably.)
Me: (Moment of revelation. Brain melts and reforms in new configuration.)
Me: That should be fine.
C: Link-fragile
Now, I’m just a simple procedural guy. I like to write my subroutines and use ‘em. Java? Yeah, yeah, ‘course I like it, I don’t have to say malloc() and free(). Great stuff.
But gradually I’m realizing just how profoundly different are Java and C.
In C, you don’t just compile a file and expect it to be “fine”. You have to compile and link your whole project with appropriately matching compiler settings, up-to-date header files… all kinds of things can go wrong in the link.
Consider these two C files, a header and a function:
// file: foo.h
typedef struct FooStruct {
int k[100];
} FooStruct;
// file: bar.c
#include "foo.h"
fooStruct *makeFoo(void) {
fooStruct *result = (fooStruct)calloc(1,sizeof(fooStruct));
return fooStruct;
}
The object code for bar.c is influenced by the struct size from foo.h. If you change foo.h, you must recompile bar.c.
Java: Link-ignorant
I did an experiment, with the following two Java classes.
public class Referring {
public static Object returnForward() {
return ReferredTo.getValue1();
}
}
public class ReferredTo {
public static Boolean getValue1()
{
return false;
}
}
It turns out that the identical binary is produced if you change the type of ReferredTo .getValue1 to Integer, or Map, or File… the method Referring.returnForward doesn’t care what it’s getting. It can even return a primitive, like int.
My guess is that Java resolves method invocations by name at run-time. If rigorously true, then it should be possible to compile Java code without any of the classes that it uses and imports. I suspect that the requirement of having appropriate source code or Jars to compile against at all is a concession to good taste (or a prevailing C-ness of attitude at some point in its past, if you prefer).
As a thought experiment, it’s clear that you could implement any functionality you want using only reflection at run-time, assuming you knew the full package and class names for any methods you wanted. Class.forName() and you’re so dynamic.
And indeed, Jython (a Java implementation of a Python interpreter) and Rhino (a Java implementation of an EcmaScript interpreter) both do exactly that.
But I’m glad that the compile-time checking does happen! It means that you can compile against any old Jar files and — if the compile succeeds at all — that the binary class is going to be good and proper and the same one I’d get from the same source file.
Almost.
The Asterisk Considered Harmful
Now, consider this source code:
import AlsLibrary.*;
import BobsLibrary.*;
public class Reporter {
public static int getUtilValue()
{
return Util.getUtilValue();
}
}
That’s great. Presumably the class Util exists in AlsLibrary or BobsLibrary. It can’t be in both: The Java compiler will warn you about that with the message: “reference to Util is ambiguous, both class BobsLibrary.Util in BobsLibrary and class AlsLibrary.Util in AlsLibrary match“.
If the method getUtilValue is in AlsLibrary, we get binary something like, if you’ll pardon my psuedobinary:
getUtilValue: return findbyname("AlsLibrary.Util.getUtilValue").invoke();
And now, the punch line. Oh, but this is rich.
Suppose that we used to have Util in AlsLibrary, but then (after interminable meetings) we move it to BobsLibrary. The identical source code will still compile, but produce a different binary. We’ve finally tricked the compiler, with our import-wildcard!
The generated class will fail at run-time with a class-not-found exception.
This would have been avoided if the source code read:
import AlsLibrary.Util;
import BobsLibrary.Application;
public class Reporter {
public static int getUtilValue()
{
return Util.getUtilValue();
}
}
The Phone Call v2.0
Them: Hey aah, we tried to build but it says that Util can’t be found…
Me: Oh, yeah, we moved it into BobsLibrary. Get the latest of everything and it’ll be fine.
Them: Oh, thank you for breaking us again! Gosh, at least we found out at compile-time instead of run-time!
So remember:
Don’t import with asterisk!
P.S. If you know of other hazards where changes external to the source Java file can affect the generated class file, I’d be interested to hear about them.
A long time I learned to be explicit in my imports not just for that reason, but because I could tell what I was dealing with when I read the code. My co-worker thought it was silly obsessive behavior until he ran into a problem like the one you illustrated and it took him considerable time to hunt down the cause in the very large project. Afterwards he converted ever place he used * and was explicit in his imports from then on.