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.
==
instead of equals()
, which is fast and null-safeMore detailed:
Mind:
equals()
or hashCode()
, the only Object
method you can override is toString()
.Further:
name()
which is the identifier of the enum value, e.g. "RED" for Color.RED
.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!static valueOf(String)
method that converts a string to an enum value.static values()
method that lists all enum values.Do not model an enum when you don't know at compile-time how many values there will be at runtime.
1 | public enum Color |
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
Following would not compile:
public enum SpecificColor extends Color
{
....
}
Error message would be:
Syntax error on token "extends", implements expected
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 | public enum Trim |
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<
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
constructor.setAccessible(true)
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.
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!
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.
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.
ɔ⃝ Fritz Ritzberger, 2021-04-04