Enum

Enum in Java

A simple enum code is:

  public enum EnumTest  {
    
	  ONE(1);
	
	  private int i;
	
	  private EnumTest(int i) {
		  this.i = i;
	  }

	  public int getI() {
		  return i;
	  }

	  public void setI(int i) {
		  this.i = i;
	  }
  }

ONE is an instance of enum EnumTest.

Java requires that the constants be defined first, prior to any fields or methods.

Also, when there are fields and methods, the list of enum constants must end with a semicolon.

The constructor for an enum type must be package-private or private access.

It automatically creates the constants that are defined at the beginning of the enum body.

You cannot invoke an enum constructor yourself.

In addition, there are following notes:

  1. enums are implicitly final and extends Enum, because a class can only extend one parent, and the Java language does not support multiple inheritance and therefore an enum cannot extend anything else.
  2. if an enum is a member of a class, it’s implicitly static
  3. new can never be used with an enum, even within the enum type itself
  4. enum constants are implicitly public static final
  5. Constructors for an enum type should be declared as private even the compiler allows non private declares
  6. all fields of enum should be final and it is better to make them private and provide public accessors

Enum constructor

When we define a constant like this ONE(1):

  1. constructor has to be provided.
  2. it calls the enum constructor with the passed argument when enum class is loaded

How to check

For above simple code, you can run javap EnumTest.class, it will print out the disassembler code to console like:

  public final class example.model.EnumTest extends java.lang.Enum<example.model.EnumTest> {
    public static final example.model.EnumTest ONE;
    static {};
    public int getI();
    public void setI(int);
    public static example.model.EnumTest[] values();
    public static example.model.EnumTest valueOf(java.lang.String);
  }

javap

The javap command disassembles class file to the byte code which is readable for human, but it is not suitable for assembler.

Compiler is used to compile high-level source code to assembly language while assembler compiles assembly code to machine code.

EnumSet and EnumMap

enum implements interface

  public interface Test {
	
	    double apply(double x, double y);

  }

When an enum implements interface, it can be:

  public enum EnumTest  implements Test{
    
	ONE ;

	@Override
	public double apply(double x, double y) {
		// TODO Auto-generated method stub
		return 0;
	}
	
  }

or

  public enum EnumTest  implements Test{
    
	ONE{
		@Override
		public double apply(double x, double y) {
			// TODO Auto-generated method stub
			return 0;
		}
	};

  }

Strategy Pattern

Conventionally the Strategy pattern is written by having an interface that is implemented by different classes. Adding a new strategy meant adding a new implementation class. With enums, this is achieved with less effort, adding a new implementation means defining just another instance with some implementation.

  public enum ParollDay {
	
	MONDAY(PayType.WEEKDAY),
	SUNDAY(PayType.WEEKEND);
	
	private final PayType payType;
	ParollDay(PayType payType) {
		this.payType = payType;
	}
	
	double pay(double hoursWorked, double payRate) {
		return payType.pay(hoursWorked, payRate);
	}
	
	private enum PayType {
		
		WEEKDAY {

			@Override
			double overtimePay(double hrs, double payRate) {
				// TODO Auto-generated method stub
				return 0;
			}
			
		},
		
		WEEKEND {

			@Override
			double overtimePay(double hrs, double payRate) {
				// TODO Auto-generated method stub
				return 0;
			}
			
		};
		
		private static final int HOURS_PER_SHIFT = 8;
		
		abstract double overtimePay(double hrs, double payRate);
		
		double pay(double hoursWorked, double payRate) {
			return 0;
		}
	}

  }

This example is from the book Effective Java

JSON Representation of Enum

Using Jackson libraries, it is possible to have a JSON representation of enum types as if they are POJOs.

Enum Abstract Method

If an enum class has an abstract method, then each instance of the enum class must implement it.

  //enum, generally speaking, comparable in performance to int constants.
  //a minor performance disadvantage of enums over int constants is that there is a space
  //and time cost to load and initialize enum types.except on resource-constrained devices,this is unlikely to be noticeable in practice.
  public enum Operation2 {
	
	PLUS("+") {
		@Override
		double apply(double x, double y) {
			// TODO Auto-generated method stub
			return 0;
		}
	},
	MINUS("-") {
		@Override
		double apply(double x, double y) {
			// TODO Auto-generated method stub
			return 0;
		}
	};
	
	private final String symbol;
	
	//enum constructor aren't permitted to access the enum's static fields, except for compile-time constant fields.
	//this restriction is necessary because these static fileds have not yet been initialized when the constructors run.
	Operation2(String symbol) {
		this.symbol = symbol;
	}
	
	@Override
	public String toString() {
		return symbol;
	}
	
	abstract double apply(double x, double y);

  }

Enum in Mybatis

If you want to map an Enum, you’ll need to use either EnumTypeHandler or EnumOrdinalTypeHandler.

By default, MyBatis uses EnumTypeHandler to convert the Enum values to their names.

EnumTypeHandler does not handle just one specific class, but any class that extends Enum

Custom typehandler

  1. create custom enum type handler which extends BaseTypeHandler or implements TypeHandler

    public class MyStatusTypeHandler<T> extends BaseTypeHandler<T> {
    	
      private Class<T> type;
      private final T[] enums;
    
      public MyStatusTypeHandler(Class<T> type) {
    		
        if( type == null ) {
           throw new IllegalArgumentException("Type argument cannot be null");
        }
    		
        this.type = type;
        this.enums = type.getEnumConstants();
        if( this.enums == null ) {
           throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
        }
      }
    
      @Override
      public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        // TODO Auto-generated method stub
    		
      }
    
      @Override
      public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
    		
        int i = rs.getInt(columnName);
        if( rs.wasNull() ) return null;
    		
        Method method = null;
        try {
           method = type.getDeclaredMethod("getId", null);
        } catch (NoSuchMethodException | SecurityException e1) {
    			
           e1.printStackTrace();
        }
    		
        for( T t : enums ) {
           try {
               int id = (int) method.invoke(t, null);
               if( id == i ) return t;
           } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
                   | SecurityException e) {
    				
               e.printStackTrace();
           }
    			
        }
    		
            return null;
      }
    
      @Override
      public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
           // TODO Auto-generated method stub
           return null;
      }
    
      @Override
      public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
           // TODO Auto-generated method stub
           return null;
      }
    
    }
    
  2. add this type handler to mybatis config file

    <typeHandlers>
    <!-- For inner class, its class file name is : Billing$BillingStatus.class -->
    <typeHandler handler="example.spring.mybatis.MyStatusTypeHandler"
     javaType="example.model.Billing$BillingStatus"/>
    </typeHandlers>
    

Spcify type handler in sql

  <resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2">
       ...
        <result column="roundingMode" property="roundingMode"
         typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>

Enum in MySQL

MySQL has a data type Enum.

  CREATE TABLE shirts (
    name VARCHAR(40),
    size ENUM('small', 'medium', 'large')
  );

An enumeration value must be a quoted string literal.

The strings you specify as input values are automatically encoded as numbers.

The numbers are translated back to the corresponding strings in query results.

The size of an ENUM object is determined by the number of different enumeration values.

One byte is used for enumerations with up to 255 possible values.

Two bytes are used for enumerations having between 256 and 65,535 possible values.

  1. if order by enum field, the order is the order of enum constant, e.g. if enum definition order is a, c, b then it is a, c, b when order by instead of a, b, c
  2. if enum field definition is a, c, b, then a corresponds 1, c corresponds 2, b corresponds 3, empty string corresponds to 0, NULL maps to null. that is if we insert 1 as this enum field value, then mysql will save it as a.
  3. when define enum field, the enum value cannot be an expression/variable
  4. use numbers as enumeration values is not a good practice

Limitation

  1. For update/delete enum member, old data should be proceed first, then can update/delete. new/update/delete enum member requires alter table change column, hence this may be expensive on resources and time for large table since mysql needs rebuild the table and look through every record to check.
  2. We cannot use plain select to get all values of enum field
  3. Cannot add foreign key for enum field
  4. If there is another talbe use same enum, then we have to define it again or move this enum in a another new table and reference this new table
  5. enum is not standard sql

References

Oracle enum

Oracle javap

javap

Java Enum

MyBatis TypeHandler

MySQL Enum

8-reasons-why-mysqls-enum-data-type-is-evil

using-the-enum-data-type-to-increase-performance

Written on June 24, 2018