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
- When to Not Use
- Example
- No Inheritance
- Functional Enums
- Singleton
- Constants in Enums
- Fields of Enum Values are not Static
Facts
- You can not extend an enum, it doesn't support inheritance.
- An enum can not extend a class. The compiler will derive it from the JRE class Enum.
- You can not add or construct an enum value programmatically at runtime.
- Enum values are
perfect singletons,
thus you can compare them using
==
instead ofequals()
, 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()
orhashCode()
, the onlyObject
method you can override istoString()
. - 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" forColor.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.
Keine Kommentare:
Kommentar veröffentlichen