Abstract: A problem that I encountered when I first started using enums was how to serialize them to some persistent store. My initial approach was to write the ordinal to the database. In this newsletter, I explore some ideas of a more robust approach. It will also show you some applications of Java generics.
Welcome to the 113th edition of The Java(tm) Specialists' Newsletter. First off, I would like to send a special welcome to Uzbekistan, bringing the total number of countries on our list to 111. In Europe we still need subscribers in Albania, so if you know of Java programmers there, please forward them our newsletter and ask them to subscribe :)
I would like to thank Dr Wolfgang Laun from Alcatel Austria for the question that led to this newsletter. We had an inspiring exchange of emails that has culminated in this newsletter. According to Dr Laun's colleagues, he is the resident guru at Alcatel Austria, so I was honoured to join him on this quest for an answer to this problem.
One of the benefits of you coming to one of our courses is that you get free life-time email support from The Java Specialists for Java related questions. Most of our courses are run in-house at companies, so please let us know if your company is desperate to spend a little bit of their training budget :)
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
A problem that I encountered when I first started using enums was how to serialize them to some persistent store. My initial approach was to write the ordinal to the database. (The ordinal is an integer representing the order of the enum value, starting at zero.) Then I started receiving phone calls from clients, asking what value 3 meant in the Status column of the Postboxsendlog table. Since I was using the automatic values of the ordinal, I did not know the answer immediately.
Another problem is that when you change the order of the enum
values or add an enum in the middle, you also modify the
ordinal value. Instead of using the ordinal int, you could
use the toString() value, and then convert the String back to
the enum using Enum.valueOf()
. Whilst this
would be less brittle than using the ordinal, it will break
if we rename the enum values.
A solution to the problem is to have a reverse lookup from some value to an enum. Ideally we would like this to be type safe using the new generics construct. We are limited with the Java 5 enums, since we cannot subclass them.
The initial idea was to embed a reverse lookup table inside each enum, but I rejected that because it did not result in a clean design and would cause the copy-and-paste anti-pattern.
We could map the enum to any value that we wanted to, but in
this example, I am using a byte
. If
you had some weird application where you needed more than 256
different enum values, you could use either a
short
or an
int
.
This interface defines the convert method that will be
implemented by each of the enums and should return a
byte
that would be used to represent
this enum.
public interface EnumConverter { public byte convert(); }
Here are two example enums, Animal and Tree:
public enum Animal implements EnumConverter { Ape(100), Bee(50), Cat(80); private final byte value; Animal(int value) { this.value = (byte) value; } public byte convert() { return value; } } public enum Tree implements EnumConverter { Acorn(30), Birch(60), Cedar(40); private final byte value; Tree(int value) { this.value = (byte) value; } public byte convert() { return value; } }
In order to lookup the enum based on a byte value, we define
the ReverseEnumMap, for which you have to pass in a class
object that is an enum and implements EnumConverter. This
allows us to get all the enum values from the class, and
to then call the convert() method on them. Note the special
syntax that allows us to specify that a generic must
implement several interfaces is done with the &, such as
<V extends Enum<V> & EnumConverter>
.
import java.util.*; public class ReverseEnumMap<V extends Enum<V> & EnumConverter> { private Map<Byte, V> map = new HashMap<Byte, V>(); public ReverseEnumMap(Class<V> valueType) { for (V v : valueType.getEnumConstants()) { map.put(v.convert(), v); } } public V get(byte num) { return map.get(num); } }
You could also hold the reverse map inside an array of size
256. Even though you cannot directly construct an instance
of an array of generics, you can do so using reflection. We
have the enum type class available in the constructor of the
ReverseEnumMap, so it is a matter of simply calling
Array.newInstance(valueType, 256);
We can now use the ReverseEnumMap to lookup the enum matching a byte value. For example, it could be used in the Communication class as follows:
import java.io.*; public class Communication<E extends Enum<E> & EnumConverter> { private final ReverseEnumMap<E> reverse; public Communication(Class<E> ec) { reverse = new ReverseEnumMap<E>(ec); } public void sendOne(OutputStream out, E e) throws IOException { out.write(e.convert()); } public E receiveOne(InputStream in) throws IOException { int b = in.read(); if (b == -1) throw new EOFException(); return reverse.get((byte) b); } }
We can use this by creating a ByteArrayOutputStream, then write an Animal Enum to that, and read it back again:
import java.io.*; public class CommunicationTest { public static void main(String[] args) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Communication<Animal> acom = new Communication<Animal>(Animal.class); acom.sendOne(baos, Animal.Ape); baos.close(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); Animal animal = acom.receiveOne(bais); System.out.println(animal); } }
This is one approach that we can use to read back enums from some byte value.
Enums in Java have a disadvantage, in that you cannot have
a common superclass for your own enums. They are all
automatically derived from the java.lang.Enum
class, which you cannot modify.
Dr Laun was not happy that we were moving the complexity of constructing the ReverseEnumMap inside our client code (in this case the Communication class). I was not happy with putting it into each enum because that would result in unnecessary copy & paste code. However, Dr Laun has convinced me that the fault lies with the enum and therefore any copy & paste code belongs inside them and not in the client code.
There is a slight catch. You cannot declare static methods
inside interfaces. Ideally we would declare that
EnumConverter has a static method that we need to implement
and which could then contain the functionality for converting
from a byte
to the enum value. My idea was to
simply declare it as a non-static method, and let the client
call the method on any instance of the enum.
First, we define an additional method inside EnumConverter:
public interface EnumConverter <E extends Enum<E> & EnumConverter<E>> { byte convert(); E convert(byte val); }
We also move the ReverseEnumMap into the Animal definition:
public enum Animal implements EnumConverter<Animal> { Ape(100), Bee(50), Cat(80); private static ReverseEnumMap<Animal> map = new ReverseEnumMap<Animal>(Animal.class); private final byte value; Animal(int value) { this.value = (byte) value; } public byte convert() { return value; } public Animal convert(byte val) { return map.get(val); } }
We now construct the Communication instance with a sample
enum of the type that we want to convert, such as:
Communication<Animal> acom = new
Communication<Animal>(Animal.Ape)
. The
Communication class would need to change slightly as well:
import java.io.*; public class Communication<E extends Enum<E> & EnumConverter<E>> { private final E enumSample; public Communication(E enumSample) { this.enumSample = enumSample; } public void sendOne(OutputStream out, E e) throws IOException { out.write(e.convert()); } public E receiveOne(InputStream in) throws IOException { int b = in.read(); if (b == -1) throw new EOFException(); return enumSample.convert((byte) b); } }
Neither approach satisfies me completely, but both will work.
Kind regards
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.