Illegal Forward Reference and Enums
JavaEnumsJava Problem Overview
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?
Java Solutions
Solution 1 - Java
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;
}
}
Solution 2 - Java
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();
}
Solution 3 - Java
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;
}
}
Solution 4 - Java
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 :)
Solution 5 - Java
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.
Solution 6 - Java
opposite
to return enum object
Add a method 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
switch
In earlier Java, 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");
}
}
}
Solution 7 - Java
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.
Solution 8 - Java
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);
}
}
Solution 9 - Java
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()];
}
}
Solution 10 - Java
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;
}
}
Solution 11 - Java
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();
}
}