5

I have an enum for which each element has an opposite. I'd like an elegant way to encapsulate this inside each element of the enum. My preferred options is not legal as it uses forward references.

enum Direction {
    NORTH(SOUTH), SOUTH(NORTH), EAST(WEST), WEST(EAST);

    private final Direction opposite;

    Direction(Direction opposite) {
        this.opposite = opposite;
    }

    public Direction getOpposite() {
        return opposite;
    }
}

Using a supplier is also illegal.

enum Direction {
    NORTH(() -> SOUTH), SOUTH(() -> NORTH), EAST(() -> WEST), WEST(() -> EAST);

    private final Supplier<Direction> opposite;

    Direction(Supplier<Direction> opposite) {
        this.opposite = opposite;
    }

    public Direction getOpposite() {
        return opposite.get();
    }
}

Which leaves me with overriding the method:

enum Direction {
    NORTH{
        public Direction getOpposite() {
            return SOUTH;
        }
    }, 
    SOUTH{
        public Direction getOpposite() {
            return NORTH;
        }
    }, 
    EAST{
        public Direction getOpposite() {
            return WEST;
        }
    }, 
    WEST{
        public Direction getOpposite() {
            return EAST;
        }
    };

    public abstract Direction getOpposite();
}

Using a switch:

enum Direction {
    NORTH, SOUTH, EAST, WEST;

    public Direction getOpposite() {
        return switch(this) {
            case NORTH -> SOUTH;
            case SOUTH -> NORTH;
            case EAST -> WEST;
            case WEST -> EAST;
        }
    }
}

Or a map:

enum Direction {
    NORTH, SOUTH, EAST, WEST;

    private static final Map<Direction,Direction> OPPOSITES =
        Map.of(NORTH, SOUTH, SOUTH, NORTH, EAST, WEST, WEST, EAST);

    public Direction getOpposite() {
        OPPOSITES.get(this);
    }
}

None of the alternatives are as straightforward or readable as just listing the opposite as an argument.

Is there an elegant way to avoid the forward reference issue?

Pshemo
  • 122,468
  • 25
  • 185
  • 269
sprinter
  • 27,148
  • 6
  • 47
  • 78
  • Why not use multiole or statement inside `if..else`?? – Vishwa Ratna Aug 02 '20 at 01:36
  • Another alternative is to use `static{..}` initialization block to set up enum values after they will already exist like in https://stackoverflow.com/a/18883717 – Pshemo Aug 02 '20 at 01:42
  • The map option isn't even bad. The lookup time is great too. The question is kind of arbitrary and subjective since the criteria is `elegant`, whatever that means. – Jason Aug 02 '20 at 01:44
  • Personally I would use the Map and provide a function like public static Direction#inverse(Direction) and use the private static final Map and use the `guava` ImmutableMap as the Map implementation. – Jason Aug 02 '20 at 01:50
  • 1
    The advantage of a `switch` statement over a map is that if you later add another enum value, the compiler will tell you if you forget to update the cases. That won't happen with a map. – Ted Hopp Aug 02 '20 at 18:52

2 Answers2

1

Not sure if it is a little better:

enum Direction {
    NORTH("SOUTH"), SOUTH("NORTH"), EAST("WEST"), WEST("EAST");

    private final String opposite;

    Direction(String opposite) {
        this.opposite = opposite;
    }

    public Direction getOpposite() {
        return Direction.valueOf(opposite);
    }
}
vcycyv
  • 606
  • 7
  • 9
0

I suggest you change the order:

enum Direction {
    NORTH, EAST, WEST, SOUTH:
    ...
}

And use Direction.values():

public Direction opposite() {
    Direction[] array = values();

    return array[(ordinal() + 2) % array.length];
}
Allen D. Ball
  • 1,916
  • 1
  • 9
  • 16