Running on Java 22-ea+27-2262 (Preview)
Home of The JavaSpecialists' Newsletter

113Enum Inversion Problem

Author: Dr. Heinz M. KabutzDate: 2005-08-29Java Version: 5Category: Language
 

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.

Enum Inversion Problem

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.

Alternative Approach

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

 

Comments

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)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...