Blog-Archiv

Sonntag, 25. April 2021

No Java Stateful Enum Serialization

Java enums are safe against serialization, but in case a serialized enum value holds a state, this state will be lost. None of the fields we define on an enum will be serialized with any of the enum's values.

This Blog shows how we can easily try out serialization between two different JVMs.
For brevity I omitted any JavaDoc, which I would not recommend for real-world source-code.

Example Enum

It doesn't make a difference whether we define a non-serializable or a serializable field in our test-enum, because any field will be ignored. Here is an enum that simply wraps a mutable String field:

public enum StatefulEnum
{
    JOHNDOE("John Doe"),
    ONEMORE("One More"),
    ;
    
    private String state;
    
    private StatefulEnum(String state)    {
        this.state = state;
    }
    
    public String getState()	{
    	return state;
    }
    public void setState(String state)	{
    	this.state = state;
    }
}

This is the enum we are about to test. We will modify the state of StatefulEnum.JOHNDOE, then serialize that enum value, then deserialize it again and look whether it kept the modified state.

Serializer

What we need for this is a serialization utility:

import java.io.*;

public final class Serializer
{
    public static byte[] serialize(Object object)    {
        try {
            final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            final ObjectOutputStream objectStream = new ObjectOutputStream(byteStream);
            objectStream.writeObject(object);
            objectStream.close();
            return byteStream.toByteArray();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }   

    public static Object deserialize(byte[] bytes)    {
        try {
            final InputStream inputStream = new ByteArrayInputStream(bytes);
            return new ObjectInputStream(inputStream).readObject();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }   

    private Serializer() {}
}

Calling Serializer.serialize() we can turn any object into a byte array, calling Serializer.deserialize() we can turn the byte array back into an object that can be casted to its target type.

Serialization Test

Now we have to think about how we can test the enum behavior concerning serialization. Do we need to set up an RMI or JMS environment now?

We will use the file system as serialization target and deserialization source. That means instead of transferring data between two JVM instances running in parallel, we will perform two consecutive Java launches, the first writes to a file, the second reads from it.

Strategy is:

  • In case the file doesn't exist, an enum value's state will be modified and the value will be written into a file.

  • Afterwards the file will be read and the enum value will be deserialized and printed.

  • Finally the file will be deleted in case it existed on launching the application, so that the third launch again will write the file.

That way we can observe what happens when deserializing the enum value in the same JVM where we wrote it, and what happens when deserializing it in another JVM.

Here is the test application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.io.*;

public class EnumSerializationTest
{
    public static void main(String[] args) throws Exception {
        new EnumSerializationTest(StatefulEnum.JOHNDOE, "StatefulEnum.ser");
    }

    public EnumSerializationTest(StatefulEnum toSerialize, String fileName) throws Exception {
        final File file = new File(fileName);
        final boolean filePresent = file.exists();
        
        if (filePresent == false) {
            modifyEnumState(toSerialize, "Vasya Pupkin");
            writeEnumToFile(file, toSerialize);
        }
        
        final StatefulEnum deserialized = readEnumFromFile(file);
        System.out.println("Deserialized state of "+deserialized+" = "+deserialized.getState());
        
        if (filePresent == true) {
            file.delete();    // make ready for next launch
            System.out.println("Deleted file "+file);
        }
    }
    
    private void modifyEnumState(StatefulEnum statefulEnum, String newState) {
        System.out.println("Original state of "+statefulEnum+" = "+statefulEnum.getState());
        
        statefulEnum.setState(newState);
        
        System.out.println("Modified state of "+statefulEnum+" = "+statefulEnum.getState());
    }

    private void writeEnumToFile(final File file, final StatefulEnum statefulEnum) throws Exception {
        System.out.println("Writing "+statefulEnum+" = "+statefulEnum.getState()+" to "+file+" ....");
        
        final byte[] serializedBytes = Serializer.serialize(statefulEnum);
        try (final OutputStream outputStream = new FileOutputStream(file)) {
            outputStream.write(serializedBytes);
        }   // auto-close
    }

    private StatefulEnum readEnumFromFile(final File file) throws Exception {
        System.out.println("Reading file "+file+" ....");
        
        try (final InputStream inputStream = new FileInputStream(file)) {
            final byte[] deserializedBytes = inputStream.readAllBytes();
            return (StatefulEnum) Serializer.deserialize(deserializedBytes);
        }   // auto-close
    }
}

On line 6 we decide which enum value we will use for the test, and we assign a file name.

On line 13 we decide whether we have to write the file or just read it. In case the file doesn't exist, we put a new state into the enum value and serialize it to the file with given name, done on line 15.

In any case we read the now always existing serialization from the file and print it out, done in line 18 and 19. Mind that if the file already existed on application start, the JVM will read-in what another JVM had written before, else it will read-in what it wrote itself in the same run.

Finally, on line 22, we delete the file in case it existed on startup. Thus we toggle the existence of the file with every second launch, that makes the application handsome.

The remaining methods, from line 27 on, just do what their name tells, they read and write the file, and they contain print-statements so that we can see what happens. The try() statement will silently close any stream inside the parentheses. On line 49 is the cast-operator that must be applied when deserializing an object.

Can you guess what the output is?

Result

First launch:

java EnumSerializationTest
Original state of JOHNDOE = John Doe
Modified state of JOHNDOE = Vasya Pupkin
Writing JOHNDOE = Vasya Pupkin to StatefulEnum.ser ....
Reading file StatefulEnum.ser ....
Deserialized state of JOHNDOE = Vasya Pupkin

The enum value JOHNDOE was printed with its original state, then it got a new state "Vasya Pupkin", and was serialized with that state. When we read-in that enum value again, it looks like the state was preserved in the serialization. But this is not true.

Second launch:

java EnumSerializationTest
Reading file StatefulEnum.ser ....
Deserialized state of JOHNDOE = John Doe
Deleted file StatefulEnum.ser

Because this was another JVM that holds a pristine JOHNDOE, it turns out that "Vasya Pupkin" has not been serialized into the persistent file. The state of the deserialized JOHNDOE is the initial "John Doe".

So why did the first launch look successful in serializing the state?
Serialization of enums is different from that of normal objects. While normal objects would throw an exception on serialization with non-serializable fields, this doesn't happen with enums (although I didn't demonstrate this here). On deserialization, the ObjectInputStream associates any deserialized enum value with the existing one (enum values are singletons!), and if we print it, we see whatever state has been attached to the existing one. This explains the misleading output of the first launch.

Conclusion

The Java enum documentation clearly states that fields of enum values will not be serialized. You can check this with the source-code above.




Sonntag, 18. April 2021

Java Class Loaders and Enum Values

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.

JVMs, Applications, Class Loaders, Classes

A Java web- or application-server, which normally runs in one JVM (Java Virtual Machine), uses class-loaders to keep its loaded applications apart.

JVM
Application/ClassLoader X
Class A
Class B
Application/ClassLoader Y
Class A
Class B
JVM
Application/ClassLoader Z
Class A
Class B

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:

JVM_1
Application/ClassLoader X_1
Class A_X_1
Class B_X_1
Application/ClassLoader Y_1
Class A_Y_1
Class B_Y_1
JVM_2
Application/ClassLoader Z_2
Class A_Z_2
Class B_Z_2

That sounds complicated, and it is. How can we try this out?

Custom Class Loader

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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.io.*;

/**
 * Prefers to load classes as resources instead of delegating to parent loader.
 */
public class ResourceClassLoader extends ClassLoader
{
    @Override
    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
    	if (className.startsWith("java."))	// this gets called recursively for super-classes,
            return super.loadClass(className, resolve);	// and loading "java." classes is prohibited
    	
        final byte[] bytes = loadClassAsResource(className);
        return defineClass(className, bytes, 0, bytes.length);
    }

    private byte[] loadClassAsResource(String className)  {
    	final String filePath = className.replace('.', '/') + ".class";
    	
    	final InputStream inputStream = getResourceAsStream(filePath);
    	final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            int nextByte = 0;
            while ( (nextByte = inputStream.read()) != -1 ) {
                outputStream.write(nextByte);
            }
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
        return outputStream.toByteArray();
    }
}

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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    public static void main(String[] args) throws Exception {
        final ResourceClassLoader classLoader = new ResourceClassLoader();
        System.out.println(
                "Initial  "+ResourceClassLoader.class.getSimpleName()+
                " identityHashCode = "+System.identityHashCode(ResourceClassLoader.class));
        
        final Class<?> clazz = classLoader.loadClass(ResourceClassLoader.class.getName());
        System.out.println(
                "Reloaded "+clazz.getSimpleName()+
                " identityHashCode = "+System.identityHashCode(clazz));
        
        final ResourceClassLoader reloadedInstance = 
                (ResourceClassLoader) clazz.getDeclaredConstructor().newInstance();
    }

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.

Reloading an Enum Class

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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.lang.reflect.Field;

public class ClassLoaderTest
{
    public static void main(String[] args) throws Exception {
        System.out.println(
            "Original "+Seasons.class.getSimpleName()+
            " identityHashCode = "+System.identityHashCode(Seasons.class));
        
        final ClassLoader customClassLoader = new ResourceClassLoader();
        final Class<?> classLoadedEnum = 
            customClassLoader.loadClass(Seasons.class.getName());
        
        System.out.println(
            "Reloaded "+classLoadedEnum.getSimpleName()+
            " identityHashCode = "+System.identityHashCode(classLoadedEnum));
        
        Object summerReloaded = null;
        for (final Field field : classLoadedEnum.getFields())
            if (field.getName().equals(Seasons.SUMMER.name()))
                summerReloaded = field.get(classLoadedEnum);
        
        System.out.println(
            "(Original "+Seasons.SUMMER+" == reloaded "+summerReloaded+") is: "+
            (Seasons.SUMMER == summerReloaded));
    }
}

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.

Conclusion

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.




Sonntag, 4. April 2021

Remarks About Java Enums

Enumerations are a finite number of distinct values of same type, typically colors, fonts, days of week, month names and so on. Java enums are type-safe. That means you should prefer them to string- or integer-constants, because the compiler can then check for their correct usage in parameters.

Facts

  1. You can not extend an enum, it doesn't support inheritance.
  2. An enum can not extend a class. The compiler will derive it from the JRE class Enum.
  3. You can not add or construct an enum value programmatically at runtime.
  4. Enum values are perfect singletons, thus you can compare them using == instead of equals(), which is fast and null-safe

More detailed:

  • Enums can have constructors, but they are private.
  • Enums can have fields and methods just like a class.
  • Enums can contain even abstract methods, these must be implemented by each enum value individually (see example below).
  • Enums support method overloading.

Mind:

  • You can not overwrite equals() or hashCode(), the only Object method you can override is toString().
  • You can use enum values in annotations, but not their fields and methods (unless they are static).

Further:

  • Enums have an automatically assigned name() which is the identifier of the enum value, e.g. "RED" for Color.RED.
  • Enums have an automatically assigned ordinal() number, starting from 0 (zero), so their order is significant. Do not change the order in case an enum has been stored as integer into a database!
  • Enums have a static valueOf(String) method that converts a string to an enum value.
  • Enums have a generated static values() method that lists all enum values.

When to Not Use

Do not model an enum when you don't know at compile-time how many values there will be at runtime.

Example


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum Color
{
    RED(16711680, "#FF0000"),
    GREEN(65280, "#00FF00"),
    BLUE(255, "#0000FF"),
    ;
    
    public boolean matches(Object color)    {
        return color == this ||
                color.equals(name()) ||
                color.equals(decimal) ||
                color.equals(cssHex);
    }
    
    public final int decimal;
    public final String cssHex;
    
    private Color(int decimal, String cssHex)    {
        this.decimal = decimal;
        this.cssHex = cssHex;
    }
}

This color-enum wraps an integer and a CSS value. It has a constructor and exposes immutable (final) fields.

Mind the semicolon on line 6. It closes the list of enum values. The compiler tolerates a preceding comma.

The matches() method and the fields can be used from outside:

    public static void main(String [] args)    {
        System.out.println("RED matches "+Color.RED+": "+
                Color.RED.matches(Color.RED));
        System.out.println("RED matches \""+Color.RED.cssHex+"\": "+
                Color.RED.matches(Color.RED.cssHex));
        System.out.println("GREEN matches "+GREEN.decimal+": "+
                Color.GREEN.matches(Color.GREEN.decimal));
        System.out.println("BLUE matches \""+Color.BLUE.name()+"\": "+
                Color.BLUE.matches(Color.BLUE.name()));
    }

You can put this this main() directly into the enum and run it with command-line "java Color". Output would be:

RED matches RED: true
RED matches "#FF0000": true
GREEN matches 65280: true
BLUE matches "BLUE": true

No Inheritance

Following would not compile:

public enum SpecificColor extends Color
{
    ....
}

Error message would be:

Syntax error on token "extends", implements expected

Functional Enums

Enums are not meant to carry functionality, but as they can have methods, it is possible. Following example implements different kinds of space-trimming:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public enum Trim
{
    START    {
        @Override
        public String trim(String toTrim)    {
            int i = 0;
            while (i < toTrim.length() && Character.isWhitespace(toTrim.charAt(i)))
                i++;
            return toTrim.substring(i);
        }
    },
    END    {
        @Override
        public String trim(String toTrim)    {
            int i = toTrim.length() - 1;
            while (i >= 0 && Character.isWhitespace(toTrim.charAt(i)))
                i--;
            return toTrim.substring(0, i + 1);
        }
    },
    BOTH    {
        @Override
        public String trim(String toTrim)    {
            return toTrim.trim();
        }
    },
    ;
    
    public abstract String trim(String toTrim);
}

Three different trim() methods are available from this enum, removing spaces from start, from end, or from both. The enum itself just declares an abstract method, which is required, and the individual enum values implement different logics.

Let's try it out:

    public static void main(String [] args)    {
        final String HELLO = " Hello World ";
        
        System.out.println("Trim.START(\""+HELLO+"\"): >"+
                Trim.START.trim(HELLO)+"<");
        System.out.println("Trim.END  (\""+HELLO+"\"): >"+
                Trim.END.trim(HELLO)+"<");
        System.out.println("Trim.BOTH (\""+HELLO+"\"): >"+
                Trim.BOTH.trim(HELLO)+"<");
   }

Output is:

Trim.START(" Hello World "): >Hello World <
Trim.END  (" Hello World "): > Hello World<
Trim.BOTH (" Hello World "): >Hello World<

Singleton

The singleton pattern guarantees that there is only one instance of the singleton class in memory of the application. Singletons are needed for management of restricted resources like printers or databases. They are also used as factories for runtime-binding of custom-classes that are unknown at compile-time ("component loading"). Java static final fields are not perfect singletons, because they can be broken through reflection.

Enum values are safe against

  • construction of new instances
  • non-identical instances through serialization, except when serialization happens between two JVMs or different class-loaders
  • multi-threaded instantiation pitfalls of the singleton instance, see "double-checked locking"
  • corruption through reflection by calling constructor.setAccessible(true)
  • immediate instantiation on class-loading, every individual enum value is loaded lazily

Thus it is safe to use the == identity operator instead of equals() on enums:

Color color = ....;

if (color == Color.RED)
    return error();

....

switch(color) {
    case RED: return error(); 
}

As this is null-safe, it makes your code better readable. Additionally it is faster than calling equals(). Mind also that the switch case syntax has been simplified for enums.

Caveat: enum values are not identical across multiple class-loaders! Multiple class loaders appear in by web- and application-servers, which use one class-loader per application. In case two applications (with different class-loaders) communicate with each other through serialization, the == operator may not work any more.


Constants in Enums

Imagine you want to define following enum that exposes a type field:

public enum PersistenceMedium
{
    H2("database"),
    MYSQL("database"),
    ALFRESCO("document store"),
    ;
    
    public final String type;
    
    private PersistenceMedium(String type)    {
        this.type = type;
    }
}

To avoid code duplication, you want to put the types "database" and "document store" into static constants:

public enum PersistenceMedium
{
    public static final String DATABASE = "database";
    public static final String DOCUMENTSTORE = "document store";

    H2(DATABASE),
    MYSQL(DATABASE),
    ALFRESCO(DOCUMENTSTORE),
    ;
    
    ....
}

The compiler rejects this with an error message, it's a grammar problem: there must not be anything between the enum header and its value definitions. When you put the string constants below the enum values, the compiler tells you that they can't be used before they are defined. So is there no way to have constants inside an enum?

There is a workaround for this:

public enum PersistenceMedium
{
    H2(Constants.DATABASE),
    MYSQL(Constants.DATABASE),
    ALFRESCO(Constants.DOCUMENTSTORE),
    ;
    
    public interface Constants
    {
        String DATABASE = "database";
        String DOCUMENTSTORE = "document store";
    }
    
    public final String type;
    
    private PersistenceMedium(String type)    {
        this.type = type;
    }
}

The fields of the inner interface Constants (may be also an inner static class) can be used as constants, as they would be compiled before the enum values These constants then can be used also outside of the enum:

    public static void main(String[] args) {
        PersistenceMedium medium = PersistenceMedium.valueOf(args[0]);    // given "ALFRESCO"
        
        if (medium.type.equals(PersistenceMedium.Constants.DATABASE))
            System.out.println("It is a database!");
        else if (medium.type.equals(PersistenceMedium.Constants.DOCUMENTSTORE))
            System.out.println("It is a document store!");
    }

The static valueOf(String) method turns a string into an enum value, in this case whatever you pass to the application as command line argument. The input must be identical with one of the enum names (return of name() method).

If you run this main() with argument ALFRESCO, output would be:

It is a document store!

Fields of Enum Values are not Static

Mind that fields (constructor arguments) of enums are NOT available statically at compile time, thus enums are not always suitable containers for constants. This especially strikes with annotations.

This enum

public enum LaunchContext
{
    DESKTOP,
    WEB,
    SERVER,
    ;
}

and this annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MainContext
{
    LaunchContext value();
}

work together perfectly here:

@MainContext(LaunchContext.WEB)
public class Main
{
    public static void main(String[] args) {
        System.out.println(
            "Launch context: "+
            Main.class.getAnnotation(MainContext.class).value()
        );
    }
}

But you can not use the enum's fields or methods in the annotation. Assuming the data-type of the annotation's value() was changed to String, following would not compile either:

@MainContext(LaunchContext.WEB.value())
public class Main
{
    ....
}

Error message is:

The method value() is undefined for the type LaunchContext

This is because value() is not a static method. The same would happen on public fields.

Conclusion

Since its introduction in Java 1.5, enum has received increasing attention, and I've seen lots of big enum implementations, carrying both field- and method-logic. Developers see it as an alternative to classes, with the singleton feature for free. Let's hope they won't become roots of a new monolith culture.