Imagine we have some class A.java. It uses B.java in some way, i.e. imports it, calls its methods, uses its attributes etc. We compile the files.
Now I would like to get a deeper understanding of what we have got.
Does A.class stores some information about B inside itself? What information - just name, method which is called, final variables which are used? If there is not that much information about B in A.class after compilation - why cant we compile A without B.class at all?
If we replace B.class in our bundle with B.class which is another one - when it works and when not?
If A.class runs ok with new B.class, may A.class use some information from OLD B.class which was incorporated into A.class during its compilation? I.e. may we finally have a mixed logic of B.class in our project?
Basing on the answers on questions above: can we somehow compile class which depends on other classes without knowing their exact implementation at compile time, and provide the dependency only in runtime?
The "import" keyword doesn't actually import anything. Basically it's just a way to refer to the full path of a class by its class name. So when we say
import java.lang.String;
then we can refer to that class just by its name "String" in our code. At compilation time, anywhere we have class "String" will be be replaced by "java.lang.String". That's it.
This is also why if you ever have more than one class with the same name but in different packages, you can import at most only one of them. The other class you must refer to by its full qualified name.
Onto the questions:
1. Does A.class stores some information about B inside itself? just name, method which is called, final variables which are used? If there is not that much information about B in A.class after compilation - why cant we compile A without B.class at all?
Not really. Class A will only use the fully qualified class names and method signatures when you invoke other classes. Using our String example, calling
mystring.charAt(0)
will compile into byte code looking something like this:
aload_1 [myString]
iconst_0
invokevirtual java.lang.String.charAt(int)
No inner state of the other classes is stored inside our Class A except possibly constants that are inlined. So be very careful about making fields public final
if the value may change in the future. (see below for work around)
We need B.class
when we compile Class A so the compiler can make sure class B has those methods we want to use.
2. If we replace B.class in our bundle with B.class which is another one - when it works and when not?
the new B.class will work if the full qualified name (package etc) is identical and it has the correct method signatures we are trying to use. The new class B can have new methods added and can even have different implementations of the methods we use. However, it must not modify the method signature of the methods we use. If the old method no longer exists or its signature is different, we will get an java.lang.LinkageError
at compile time.
3. If A.class runs ok with new B.class, may A.class use some information from OLD B.class which was incorporated into A.class during its compilation? I.e. may we finally have a mixed logic of B.class in our project?
The only issue might be inlined constants from the old B. This is why the Java Coding Guidelines Item 31 (page 115) says "Do not apply public final to constants whose value might chage in later releases". Instead, make a getter method: ex:
class BadFoo{
//bad, VERSION could be inlined
//and later when we change the version, other classes will have the old value!
public static final int VERSION =1;
}
class BetterFoo{
private static int version =1;
//outside classes must call method getVersion()
//field version can not be inlined at compile time.
//JIT may inline at runtime which is OK
public static final int getVersion(){
return version;
}
}
4. Basing on the answers on questions above: can we somehow compile class which depends on other classes without knowing their exact implementation at compile time, and provide the dependency only in runtime?
Yes, code to interfaces.
the interface should contain all the methods that you need to call. Our Class A should only refer to the caller by the interface. If the object instance we want to call is passed in, then Class A doesn't know (or need to know) what the actual type is AND doesn't need to know about it at compile time.
This is one of the main purposes and advantages of Dependency Injection
Even aside from Dependency Injection, coding to interfaces has advantages. For example, if we use Map
instead of HashMap
, we can later change the code to use a different Map implementation such as ConcurrentHashMap
by only changing one place in our code. The rest of our code will just work since it only knows it's a Map.
This is addressed by Chapter 13. Binary Compatibility of the Java Language Specification.
As this may be more specific than you want, I'll give a brief summary:
If class A
uses a feature f
of class B
, the class file of A
must contain a symbolic reference to f
. When class A is loaded, but B
does not provide f
, loading class A
fails with an java.lang.LinkageError
, which prevents A
from being used.
An exception are compile time constant expressions, which the compiler must inline, i.e. the symbolic references must be replaced by their value at compile time, and class A will continue to use the original values even if class B has been changed.
Another exception is that adding or removing annotations has no effect on the correct linkage of the binary representations of programs.
No other information about B
is incorporated into A
at compile time, and everything else about B
may be changed without affecting its use by an already compiled class A
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With