Repercussions of changing single class in large project?

A

Andy Dingley

I have a large project - several hundreds of classes. If a single
..java file is changed, then obviously the compiled class is changed
too. However I'm not sure how many other classes would be affected by
this and would also change when recompiled.

By "will change", I'm thinking of a pragmatic meaning here, somewhere
between "the bytecode will be different" and "the program will be in
error if you mix the compiled classes by deploying too few class
files". I'm not interested in changes that merely cause recompilation,
only the end result.


What changes to source would cause dependent classes to change? Is it
as simple as "anything in a signature, nothing outside a signature" ?

Does the way in which a class is imported affect that dependent class'
sensitivity to changes ?

Are these changes transitive? If A depends on B which depends on C,
will changing C "significantly" also cause A to change ?


I appreciate this is a potentially complex question. If there are any
simple rules that can be applied, I'd love to hear them.

Thanks
 
C

Chris Uppal

What changes to source would cause dependent classes to change?

As far as I know, the contents of one classfile -- as produced by the
compiler -- are independent of the source (or classfile) for any other class,
with the following exceptions:

Obviously the faked-up relationship between inner and outer classes is tighter,
but that shouldn't be an issue here.

Numeric, boolean, and String fields (including non-static fields) which are
declared to be final, and initialised to a compile-time constant, are inlined
whereever they are referred to in any [other] class. Hence, if you change the
values, then you must recompile the classes which use them.

There is only one circumstance where changes can propogate transitively --
that's when one class uses another class's constants to define its own. E.g:
public class A { public static int X = 1; }
public class B { public static int Y = A.Y * 10; }
public class C { public static int Z = B.X * 10; }
In the above case, changing A.X means that classes B /and/ C have be
recompiled.

The Java compiler does a great deal of early binding -- it checks that methods
and fields exist in other classes (and that they are accessible, etc) before it
will let you use them. So, clearly, removing a member could cause another
class to become uncompilable. (Come to that, so can removing a class ;-)
Changing the class tree in a way that changes a class's total set of methods
and fields (including inherited ones) counts as adding or removing for these
purposes.

A more subtle, but related, point is that if you add or remove overloaded
methods, then the compiler might change it's mind about how to resolve an
overloaded method call. E.g. if you have in V1 of your class:
public class XYZ
{
public void aMethod(int i) { .... }
}
and a caller in another class:
xyz.aMethod('a');
then the compiler will statically resolve that call to invoke the (possibly
overriden) aMethod(int). If you now add an overload to XYZ
public class XYZ
{
public void aMethod(int i) { .... }
public void aMethod(char c) { .... }
}
and recompile /without/ recompiling the caller, then it will still call
aMethod(int). If you now recompile the caller, then the static part of method
resolution will now see the new method as a better match for the parameter, so
it will generate code to invoke aMethod(char) instead.

============

Some examples of changes which do not propogate to other classfiles:

Changes to the bodies of methods in one class do not result in changes to any
other classfile. Adding or removing members (fields, methods, nested classes)
does not result in changes to any other classfile unless it refers to those
specific members. Re-ordering members does not result in changes to any other
classfile even if it /does/ use those members.

An example of where a fairly drastic change to one class doesn't cause changes
to another class's compiled form. If we start with XYZ in its first form
above, and compile
xyz.aMethod(0);
against it, then we'll get a certain compiled form for the caller. If we now
refactor XYZ into
class ABC
{
public void aMethod(int i) { .... }
}
public class XYZ
extends ABC
{
}
and recompile the caller, then we'll get exactly the same compiled form as
before.

-- chris
 
D

ddimitrov

Chris said:
Numeric, boolean, and String fields (including non-static fields) which are
declared to be final, and initialised to a compile-time constant, are inlined
whereever they are referred to in any [other] class. Hence, if you change the
values, then you must recompile the classes which use them.

Just to add that since Java 1.5, the classfile format supports
java.lang.Class literals as well.

This means that if you have the following code:

interface ReflectionConfiguration {
Class<FooService> fooImplementation = FooServiceImpl.class;
}

class Client {
public static void main(String[] args) {
FooService foo =
ReflectionConfiguration.fooImplementation.newInstance();
foo.doSomething();
}
}

And then swap the fooImplementation to point to different class, the
change will be picked by the Client class if you use Java 1.4, but
won't be picked by 1.5 (unless you compile with -target 1.4).

This example is stupid, but in practice it happens.
 
C

Chris Uppal

ddimitrov said:
interface ReflectionConfiguration {
Class<FooService> fooImplementation = FooServiceImpl.class;
}

class Client {
public static void main(String[] args) {
FooService foo =
ReflectionConfiguration.fooImplementation.newInstance();
foo.doSomething();
}
}

And then swap the fooImplementation to point to different class, the
change will be picked by the Client class if you use Java 1.4, but
won't be picked by 1.5 (unless you compile with -target 1.4).

I'm sorry but I don't understand what you mean.

I /thought/ you meant that class literals were treated like final compile-time
constants an inlined at the point of reference, which is plausible -- but very
nasty if true ;-) But (I'm happy to say) that doesn't seem to be the case.
E.g:

========================
public class Constants
{
static final int A = 22;
static final Class C = String.class;
}
========================
public class Test
{
public static void
main(String[] args)
{
System.out.println(Constants.A);
System.out.println(Constants.C);
}
}
========================

In the first call to println(), the constant A is replaced by its value in the
generated bytecode:

getstatic java/lang/System/out Ljava/io/PrintStream;
bipush 22
invokevirtual java/io/PrintStream/println (I)V

but the reference to the "constant" C is not:

getstatic java/lang/System/out Ljava/io/PrintStream;
getstatic Constants/C Ljava/lang/Class;
invokevirtual java/io/PrintStream/println (Ljava/lang/Object;)V

So if Constants.java is recompiled with a different vaue for C, Test.java does
not have to be recompiled to pick that up. (That's using javac from Sun's
1.5.0_06-b05.)

-- chris
 
D

ddimitrov

It seems that I was wrong about my assumptions for the class literals.

The actual problem that I've seen is that a class does not get
initilized on it's first reference under java 1.5

This is illustrated by the following snippet:

public class Test {
public static void main(String[] args) {
System.out.println("showing " + SomeClass.class);
}
}

class SomeClass {
static { System.out.print("loading and "); }
}

If we use JDK 5, the following commands produce the respective output:
showing class SomeClass
loading and showing class SomeClass
 
C

Chris Uppal

ddimitrov said:
The actual problem that I've seen is that a class does not get
initilized on it's first reference under java 1.5

Or even -- to judge from your example -- when its java.lang.Class object is
used (its toString() is invoked).

Personally, I find that distasteful...

-- chris
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top