The Java Specialists' Newsletter
Issue 1132005-08-29
Category:
Language
Java version: Sun JDK 5.0
GitHub
Subscribe Free
RSS Feed
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 :)
NEW:
Please see our new "Extreme Java" course, combining
concurrency, a little bit of performance and Java 8.
Extreme Java - Concurrency & Performance for Java 8.
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.
Enum.valueOf()
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.
byte
short
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>.
<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);
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.
java.lang.Enum
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:
Communication<Animal> acom = new
Communication<Animal>(Animal.Ape)
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
Language Articles
Related Java Course