Enum values are singletons just in the scope of their class-loader. A class-loader holds a set of classes that can work together. But an instance of class A, created by class-loader X, can not work together with an instance of class A created by class-loader Y, although both may have been compiled from exactly the same source and thus are identical.
This Blog introduces a custom class-loader and a test application that compares enum values belonging to different class loaders, showing that they are unique only in the scope of their class-loader.
A Java web- or application-server, which normally runs in one JVM (Java Virtual Machine), uses class-loaders to keep its loaded applications apart.
That means application X can use the same class A as application Y, but at runtime, class A in application X will be different from class A in application Y. There will be a runtime exception when you try to cast an instance of class A created by X to an instance of class A created by Y. Mind that the class-loader instance makes the difference, not the class-loader type.
So, at runtime, this is reality:
That sounds complicated, and it is. How can we try this out?
The Java ClassLoader
is abstract
, so we need to derive it to have a loader that we can construct in our test. Follwing can be used for loading classes from resource-streams:
1 | import java.io.*; |
This class-loader will try to load every class from its parent's resource stream. This is sufficient for our test.
The overridden loadClass()
method on line 9 will be called recursively for every super-class of the class to be loaded, which is at least java.lang.Object
because implicitly every class derives Object
. This is the reason for the super.loadClass()
call on line 11 that delegates to the parent loader.
Any other call will be delegated to loadClassAsResource()
on line 17 that tries to read bytes from a resource stream given by the fully qualified class name. Theses bytes have then to go through defineClass()
on line 14 which builds a Java class from them.
Let's try out if this works:
1 | public static void main(String[] args) throws Exception { |
The method System.identityHashCode()
can be used to display identities of classes and objects being in memory. It demonstrates that the original class and the reloaded class are not identical.
Anyway, the last statement on line 12 makes this application fail. Output is:
Initial ResourceClassLoader identityHashCode = 237852351
Reloaded ResourceClassLoader identityHashCode = 1807837413
Exception in thread "main" java.lang.ClassCastException:
class fri.classloader.ResourceClassLoader cannot be cast to
class fri.classloader.ResourceClassLoader
(fri.classloader.ResourceClassLoader is in unnamed module of loader
fri.classloader.ResourceClassLoader @3b22cdd0;
fri.classloader.ResourceClassLoader is in unnamed module of loader
'app')
That means, the reloaded class is not compatible to the originally loaded class. Although their fully qualified class-names are the same, they belong to different class loaders.
Here is an example enum that we want to reload:
public enum Seasons
{
SPRING,
SUMMER,
AUTUMN,
WINTER,
;
}
Following test code compares the enum value SUMMER
to a reloaded one:
1 | import java.lang.reflect.Field; |
First we print the identity of the original SUMMER enum class on line 6.
On line 10, a new class-loader is created. It is used to reload the Seasons
enum class, as done on line 11. The identity of the reloaded class is printed on line 14.
We can not cast the reloaded class to Seasons
, this would yield an exception, see the ResourceClassLoader
example above. So we have to use reflection to read the SUMMER
field of the reloaded enum, as done from line 18 to 21.
The print statement on line 23 shows the following:
Original Seasons identityHashCode = 992136656
Reloaded Seasons identityHashCode = 48612937
(Original SUMMER == reloaded SUMMER) is: false
That means, not only the two enum classes are different, also their SUMMER values are not the same and can not be compared using the ==
identity operator.
It is not easy to understand theoretical statements about class loading and singleton behaviors when not having source code to try out how it works. It hope the provided examples are helpful.
ɔ⃝ Fritz Ritzberger, 2021-04-18