I'm programming a game in java which is made up of a grid of tiles. I wan开发者_运维百科't to be able to inuitively define the edges of the tiles and how they relate to each other, e.g. to get the opposite edge of a tile, I want to be able to just type TOP.opposite()
. However, when using enums to define these edges I end up having to forward reference at least two of them in the contstructor:
public enum Edge {
TOP(Edge.BOTTOM), //illegal forward reference
BOTTOM(Edge.TOP),
LEFT(Edge.RIGHT), //illegal forward reference
RIGHT(Edge.LEFT);
private Edge opposite;
private Edge(Edge opp){
this.opposite = opp;
}
public Edge opposite(){
return this.opposite;
}
}
Is there any way of getting round this problem using enums which is just as simple?
You can do this which is not as intuitive.
public enum Edge {
TOP, BOTTOM, LEFT, RIGHT;
private Edge opposite;
static {
TOP.opposite = BOTTOM;
BOTTOM.opposite = TOP;
LEFT.opposite = RIGHT;
RIGHT.opposite = LEFT;
}
public Edge opposite(){
return this.opposite;
}
}
enum Edge {
TOP {
@Override
public Edge opposite() {
return BOTTOM;
}
},
BOTTOM {
@Override
public Edge opposite() {
return TOP;
}
},
LEFT {
@Override
public Edge opposite() {
return RIGHT;
}
},
RIGHT {
@Override
public Edge opposite() {
return LEFT;
}
};
public abstract Edge opposite();
}
public enum Edge {
TOP,
BOTTOM(Edge.TOP),
LEFT,
RIGHT(Edge.LEFT);
private Edge opposite;
private Edge() {
}
private Edge(Edge opp) {
this.opposite = opp;
opp.opposite = this;
}
public Edge opposite() {
return this.opposite;
}
}
You can also make use of an static innerclass inside the enum:
public enum EnumTest
{
NORTH( Orientation.VERTICAL ),
SOUTH( Orientation.VERTICAL ),
EAST( Orientation.HORIZONTAL ),
WEST( Orientation.HORIZONTAL );
private static class Orientation
{
private static final String VERTICAL = null;
private static final String HORIZONTAL = null;
}
}
Stolen from here :)
Here's another way
public enum Edge {
TOP("BOTTOM"),
BOTTOM("TOP"),
LEFT("RIGHT"),
RIGHT("LEFT");
private String opposite;
private Edge(String opposite){
this.opposite = opposite;
}
public Edge opposite(){
return valueOf(opposite);
}
}
Peter Lawrey's solution is however more efficient and compiletime safe.
Add a method opposite
to return enum object
You could just define a method, opposite()
.
In modern Java, switch expression
In modern Java, we can use a switch expression. The compiler ensures that we have covered all possible cases.
enum Edge
{
TOP, BOTTOM, LEFT, RIGHT;
public Edge opposite ( )
{
return switch ( this )
{
case TOP -> BOTTOM;
case BOTTOM -> TOP;
case LEFT -> RIGHT;
case RIGHT -> LEFT;
};
}
}
Usage:
System.out.println( Edge.TOP.opposite() );
BOTTOM
In earlier Java, switch
In older Java, use syntax seen in the following code.
Notice the need for a default
case, in case you ever add an element to the enum or you inadvertently delete a case from the switch
.
public enum Edge {
TOP,
BOTTOM,
LEFT,
RIGHT;
public Edge opposite() {
switch (this) {
case TOP:
return BOTTOM;
case BOTTOM:
return TOP;
case LEFT:
return RIGHT;
case RIGHT:
return LEFT;
default:
throw new RuntimeException("Oh dear");
}
}
}
You can create a static Map
where key is the original enum and the value the opposite edge. Initialize it in a static block and the return the mapping from the opposite()
method.
private static Map<Edge, Edge> oppostiteMapping;
static {
oppositeMapping = new EnumMap<Edge, Edge>();
oppositeMapping.put(TOP, BOTTOM);
...
}
public Edge opposite() {
return oppositeMapping.get(this);
}
EDIT: as proposed in comment better to use EnumMap, so I upgraded accordingly
Btw. this approach is generally useful when you create something like static fromString()
method etc.
You could use an internal Map instead to define these associations. This works if at the point of initializing the Map, you already have all enum values created:
public enum Edge {
TOP,
BOTTOM,
LEFT,
RIGHT;
private static final Map<Edge, Edge> opposites =
new EnumMap<Edge, Edge>(Edge.class);
static {
opposites.put(TOP, BOTTOM);
opposites.put(BOTTOM, TOP);
opposites.put(LEFT, RIGHT);
opposites.put(RIGHT, LEFT);
}
public Edge opposite(){
return opposites.get(this);
}
}
My method is by using ordinal. This is a simple example, but for a much more complex example see below.
public enum Edge {
// Don't change the order! This class uses ordinal() in an arithmetic context.
TOP, // = 0
LEFT, // = 1
RIGHT, // = 2
BOTTOM; // = 3
public Edge other() {
return values()[3 - ordinal()];
}
}
Although using ordinal is discouraged for being fragile, using ordinal in the same enum as it's defined in is less fragile, and it's further mitigated here with a comment. Though the example above is quite trivial, the next example is less so. Compare the original way and the way using ordinal:
From 98 lines:
public enum Axes {
NONE,
HORIZONTAL,
VERTICAL,
BOTH;
public Axes add(Axes axes) {
switch (axes) {
case HORIZONTAL:
if (this == NONE)
return HORIZONTAL;
if (this == VERTICAL)
return BOTH;
break;
case VERTICAL:
if (this == NONE)
return VERTICAL;
if (this == HORIZONTAL)
return BOTH;
break;
case BOTH:
return BOTH;
default:
throw new AssertionError(axes);
}
return this;
}
public Axes remove(Axes axes) {
switch (axes) {
case HORIZONTAL:
if (this == HORIZONTAL)
return NONE;
if (this == BOTH)
return VERTICAL;
break;
case VERTICAL:
if (this == VERTICAL)
return NONE;
if (this == BOTH)
return HORIZONTAL;
break;
case BOTH:
return NONE;
default:
throw new AssertionError(axes);
}
return this;
}
public Axes toggle(Axes axes) {
switch (axes) {
case NONE:
return this;
case HORIZONTAL:
switch (this) {
case NONE:
return HORIZONTAL;
case HORIZONTAL:
return NONE;
case VERTICAL:
return BOTH;
case BOTH:
return VERTICAL;
default:
throw new AssertionError(axes);
}
case VERTICAL:
switch (this) {
case NONE:
return VERTICAL;
case HORIZONTAL:
return BOTH;
case VERTICAL:
return NONE;
case BOTH:
return HORIZONTAL;
default:
throw new AssertionError(axes);
}
case BOTH:
switch (this) {
case NONE:
return BOTH;
case HORIZONTAL:
return VERTICAL;
case VERTICAL:
return HORIZONTAL;
case BOTH:
return NONE;
default:
throw new AssertionError(axes);
}
default:
throw new AssertionError(axes);
}
}
}
to 19 lines:
public enum Axes {
// Don't change the order! This class uses ordinal() as a 2-bit bitmask.
NONE, // = 0 = 0b00
HORIZONTAL, // = 1 = 0b01
VERTICAL, // = 2 = 0b10
BOTH; // = 3 = 0b11
public Axes add(Axes axes) {
return values()[ordinal() | axes.ordinal()];
}
public Axes remove(Axes axes) {
return values()[ordinal() & ~axes.ordinal()];
}
public Axes toggle(Axes axes) {
return values()[ordinal() ^ axes.ordinal()];
}
}
I preferred this:
public enum Edge {
TOP,
BOTTOM,
LEFT,
RIGHT;
private Link link;
private Link getLink() {
if (link == null) {
link = Link.valueOf(name());
}
return link;
}
public Edge opposite() {
return getLink().opposite();
}
}
public enum Link {
TOP(Edge.BOTTOM),
BOTTOM(Edge.TOP),
LEFT(Edge.RIGHT),
RIGHT(Edge.LEFT);
private Edge opposite;
private Link(Edge opp) {
this.opposite = opp;
}
public Edge opposite() {
return this.opposite;
}
}
With Java 8 lambdas:
public enum Edge {
TOP(() -> Edge.BOTTOM),
BOTTOM(() -> Edge.TOP),
LEFT(() -> Edge.RIGHT),
RIGHT(() -> Edge.LEFT);
private Supplier<Edge> opposite;
private Edge(Supplier<Edge> opposite) {
this.opposite = opposite;
}
public Edge opposite() {
return opposite.get();
}
}
精彩评论