Ant doesn't recompile constants
I have a Java project that involves the class GUIConstants
--various public static final
parameters used for laying out the GUI, since different components sometimes have to be the same size or color or whatever.
I'm currently in a stage of doing a visual redesign, which involves changing a few of those constants. However, ant
is making this difficult. I'll change a parameter and recompile, but the old value is still used. If I add some trivial modification to one of the files that uses it and recompile, the correct value will be used. But it's annoying and error-prone to have to track down all the files and modify them. Surely there's a way to force ant
to recompile unchanged files...I just couldn't find it in the man page.
Sidenote: My hypothesis is that when a class that uses a final
variable is compiled, Java instead uses the value itself, not a 开发者_如何学Pythonreference to whatever the variable is pointing at (in a similar fashion to the way using #DEFINE
for constants works in C). So even when the variable points to something else, the original value is baked into the .class
file. Is this true? (It doesn't affect my problem, I'm just curious.)
Thanks in advance.
Your hypothesis is almost right. It's not simply final
variables though, but so called "constant variables":
We call a variable, of primitive type or type String, that is final and initialized with a compile-time constant expression (§15.28) a constant variable. Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1, §13.4.9) and definite assignment (§16).
Then in the section on binary compatibility (§13.1):
References to fields that are constant variables (§4.12.4) are resolved at compile time to the constant value that is denoted. No reference to such a constant field should be present in the code in a binary file (except in the class or interface containing the constant field, which will have code to initialize it)
and (§13.4.9) (my emphasis):
If a field is a constant variable (§4.12.4), then deleting the keyword final or changing its value will not break compatibility with pre-existing binaries by causing them not to run, but they will not see any new value for the usage of the field unless they are recompiled.
In a previous job, we made use of this to have a sort of conditional compilation system, so that we could produce production binaries with all the debug statements stripped out.
When you combine this with the way the javac
task determines what classes to recompile you get the behaviour you are seeing:
Only Java files that have no corresponding .class file or where the class file is older than the .java file will be compiled.
Note: Apache Ant uses only the names of the source and class files to find the classes that need a rebuild. It will not scan the source and therefore will have no knowledge about nested classes, classes that are named different from the source file, and so on. See the
<depend>
task for dependency checking based on other than just existence/modification times.
The simplest way to fix this would be to do a clean full compile every time, with something like ant clean compile
(assuming you have a clean
target that removes all your class files). This might be too slow though.
I was also going to suggest that you look at the depend
task, as suggested in the docs for the javac
task, but looking at the docs for it (I haven't actually used it myself) it seems that it won't help (see the section titled "Limitations"):
The most obvious example of these limitations is that the task can't tell which classes to recompile when a constant primitive data type exported by other classes is changed.
One possible workaround, if you find doing a clean compile every time too slow, would be to make the values in your GUIConstants
class not be constants, at least whilst you make your changes. You could make the values non-final by commenting out all the final
keywords, then the other classes should see your changes. When you are happy with the new values, put the final
s back in and recompile (and test that everything is still working properly of course).
Your theory is wrong. Java embeds finals into the constant pool in the surrounding class file just like it does any non-finals. There's no special "final" optimization, except a flag indicating that it can only be set during construction.
Odds are much better that you have some sort of error in your build chain, like a class or set of classes that are "updated" from old sources, or jar files that are "built" from not-yet updated classes, or programs that are referencing jar files from previous builds, etc. One fast way to verify this is to remove all the compiled items. Often this is done with the "clean" target in the ant build.xml file; however, that target may also be written by hand, so don't assume that it is always correct (especially if you added extra intermediate build steps like class enhancement, etc.).
You want to use a clean
task that deletes all files in your dist directory. Or wherever you happen to be placing the binaries.
Neither "theories" seem correct, removing static has no effect, a recompile from clean always works, my build "system" is almost boiler plate relying only on source within a subdirectory and no other external jars, there are no "stale" classes hanging around
In my case I'm having to do a complete rebuild of one area of my sources - I'm directly including a library from source which doesn't need recompiling but is included by a seperate "deepclean" rule but is isolated from this issue...
The answer of DJClayworth in this answer suggests using a construct like the following to fool the compiler:
public static final int INT_VALUE = Integer.valueOf(100).intValue();
It makes the compiler not see the constant character of this member. I just tested it and it works. Do not forget to execute one complete clean after you have changed the constant to the above construct.
Not a beautiful solution, but good for a temporal workaround.
精彩评论