This question already has an answer here:
-
What's the advantage of a Java enum versus a class with public static final fields?
14 answers
I was trying to understand how Java enum really works and I have come to the conclusion that it is very similar to a normal Java class that has its constructor declared private.
I have just come to this conclusion and it isn't based on much thinking, but Id like to know whether I miss anything.
So below is an implementation of a simple Java enum and an equivalent Java class.
public enum Direction {
ENUM_UP(0, -1),
ENUM_DOWN(0, 1),
ENUM_RIGHT(1, 0),
ENUM_LEFT(-1, 0);
private int x;
private int y;
private Direction(int x, int y){
this.x = x;
this.y = y;
}
public int getEnumX(){
return x;
}
public int getEnumY(){
return y;
}
}
What is the difference in meaning between the code above and below?
public class Direction{
public static final Direction UP = new Direction(0, -1) ;
public static final Direction DOWN = new Direction(0, 1) ;
public static final Direction LEFT = new Direction(-1, 0) ;
public static final Direction RIGHT = new Direction(1, 0) ;
private int x ;
private int y ;
private Direction(int x, int y){
this.x = x ;
this.y = y ;
}
public int getX(){
return x;
}
public int getY(){
return y;
}
}
Differences:
- Enums extend
java.lang.Enum
and gain all of its nice features:
- Automatic singleton behaviour through correct serialization
- Automatic human-readable
.toString
method on enum values without the need to duplicate your enum names
.name
and .ordinal
special-purpose methods
- Usable in high-performance bitset-based
EnumSet
and EnumMap
classes
- Enums are treated by the language specially:
- Enums use a special syntax which simplifies instance creation without writing dozens of
public static final
fields
- Enums can be used in
switch
statements
- Enums cannot be instantiated outside the enumeration list except by using reflection
- Enums cannot be extended outside the enumeration list
- Java automatically compiles extra stuff into enums:
public static (Enum)[] values();
public static (Enum) valueOf(java.lang.String);
private static final (Enum)[] $VALUES;
(values()
returns a clone of this)
Most of these can be emulated with a suitably designed class, but Enum
just makes it really easy to create a class with this set of particularly desirable properties.
To answer the question: essentially, there's no difference between the two approaches. However, enum construct provides you with some additional supporting methods like values()
, valueOf()
, etc. which you'd have to write on your own with the class-with-private-constructor approach.
But yeah, I like how Java enums are mostly just like any other classes in Java, they can have fields, behaviors, etc. But to me what separates enums from the plain classes is the idea that enums are classes/types whose instances/members are predetermined. Unlike usual classes where you can create any number of instances from, enums only limit creation to known instances. Yes, as you've illustrated, you can also do this with classes with private constructors, but enums just make this more intuitive.
Take a look at this blogpage, it describes how Java enum
s are compiled into bytecode. You'll see that there's a small addition compared to your second code sample, which is an array of Direction
objects called VALUES
. This array holds all possible values for your enum, so you won't be able to do
new Direction(2, 2)
(for example using reflection) and then use that as a valid Direction
value.
Plus, as @Eng.Fouad correctly explains, you don't have values()
, valueOf()
and ordinal()
.
As people have pointed out you lose values()
, valueOf()
and ordinal()
. You can replicate this behaviour fairly easily using a combination of a Map
and a List
.
public class Direction {
public static final Direction UP = build("UP", 0, -1);
public static final Direction DOWN = build("DOWN", 0, 1);
public static final Direction LEFT = build("LEFT", -1, 0);
public static final Direction RIGHT = build("RIGHT", 1, 0);
private static final Map<String, Direction> VALUES_MAP = new LinkedHashMap<>();
private static final List<Direction> VALUES_LIST = new ArrayList<>();
private final int x;
private final int y;
private final String name;
public Direction(int x, int y, String name) {
this.x = x;
this.y = y;
this.name = name;
}
private static Direction build(final String name, final int x, final int y) {
final Direction direction = new Direction(x, y, name);
VALUES_MAP.put(name, direction);
VALUES_LIST.add(direction);
return direction;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public static Direction[] values() {
return VALUES_LIST.toArray(new Direction[VALUES_LIST.size()]);
}
public static Direction valueOf(final String direction) {
if (direction == null) {
throw new NullPointerException();
}
final Direction dir = VALUES_MAP.get(direction);
if (dir == null) {
throw new IllegalArgumentException();
}
return dir;
}
public int ordinal() {
return VALUES_LIST.indexOf(this);
}
@Override
public int hashCode() {
int hash = 7;
hash = 29 * hash + name.hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Direction other = (Direction) obj;
return name.equals(other.name);
}
@Override
public String toString() {
return name;
}
}
As you can see; the code becomes very clunky very quickly.
I'm not sure if there is a way for replicate a switch
statement with this class; so you will lose that.
The main difference is the each enum
class implicitly extends Enum<E extends Enum<E>>
class. This leads to that:
enum
objects have such methods as name()
and ordinal()
enum
objects have special toString()
, hashCode()
, equals()
and compareTo()
implementations
enum
objects are suitable for switch
operator.
All mentioned above is not applicable for your version of Direction
class. This is the "meaning" difference.