Self-relational enumeration with immutable parameters

Consider the following sscce

public enum Flippable A (Z), B (Y), Y (B), Z (A); private final Flippable opposite; private Flippable(Flippable opposite) { this.opposite = opposite; } public Flippable flip() { return opposite; } } 

This does not compile because Z and Y were not declared as allowed arguments to constructor A and B

Potential Solution 1: Hardcoded Methods

 public enum Flippable { A { public Flippable flip() { return Z; } }, B { public Flippable flip() { return Y; } }, Y { public Flippable flip() { return B; } }, Z { public Flippable flip() { return A; } }; public abstract Flippable flip(); } 

While functional, it seems stylistically rather rude. Although I can’t say why this will be a real problem.

Potential Solution 2: Static Boot

 public enum Flippable { A, B, Y, Z; private Flippable opposite; static { for(Flippable f : Flippable.values()) { switch(f) { case A: f.opposite = Z; break; case B: f.opposite = Y; break; case Y: f.opposite = B; break; case Z: f.opposite = A; break; } } } public Flippable flip() { return opposite; } } 

This is even more rude than the first decision, because the field is no longer final and vulnerable to reflection. Ultimately, this is an obscure concern, but suggests a bad code smell.

Is there a way to do this, essentially the same as in the first example, but compiles correctly?

+7
java enums self-reference
source share
6 answers

Again, perhaps not as beautiful as you were looking for ...

 public enum Flippable { A, B, Z, Y; static { A.opposite = Z; B.opposite = Y; Y.opposite = B; Z.opposite = A; } public Flippable flip() { return opposite; } private Flippable opposite; public static void main(String[] args) { for(Flippable f : Flippable.values()) { System.out.println(f + " flips to " + f.flip()); } } } 
+12
source share

Just match the opposites:

 import java.util.*; public enum Flippable { A, B, Y, Z; private static final Map<Flippable, Flippable> opposites; static { opposites = new EnumMap<Flippable, Flippable>(Flippable.class); opposites.put(A, Z); opposites.put(B, Y); opposites.put(Y, B); opposites.put(Z, A); // integrity check: for (Flippable f : Flippable.values()) { if (f.flip().flip() != f) { throw new IllegalStateException("Flippable " + f + " inconsistent."); } } } public Flippable flip() { return opposites.get(this); } public static void main(String[] args) { System.out.println(Flippable.A.flip()); } } 

EDIT: switched to EnumMap

+2
source share

As you can see, this is not possible due to constant constants, but you cannot initialize A until Z is initialized.

So this trick should work:

 public enum Flippable { A ("Z"), B ("Y"), Y ("B"), Z ("A"); private final String opposite; private Flippable(String opposite) { this.opposite = opposite; } public Flippable flip() { return valueOf(opposite); } } 
+1
source share

Good question. You might like this solution:

 public class Test { public enum Flippable { A, B, Y, Z; private Flippable opposite; static { final Flippable[] a = Flippable.values(); final int n = a.length; for (int i = 0; i < n; i++) a[i].opposite = a[n - i - 1]; } public Flippable flip() { return opposite; } } public static void main(final String[] args) { for (final Flippable f: Flippable.values()) { System.out.println(f + " opposite: " + f.flip()); } } } 

Result:

 $ javac Test.java && java Test A opposite: Z B opposite: Y Y opposite: B Z opposite: A $ 

If you want to keep the instance field "final" (which is certainly nice), you can index it at runtime:

 public class Test { public enum Flippable { A(3), B(2), Y(1), Z(0); private final int opposite; private Flippable(final int opposite) { this.opposite = opposite; } public Flippable flip() { return values()[opposite]; } } public static void main(final String[] args) { for (final Flippable f: Flippable.values()) { System.out.println(f + " opposite: " + f.flip()); } } } 

which also works.

0
source share

As long as the field remains private for enumeration, I am not sure whether its final status is really of great concern.

Nevertheless, if the opposite is defined in the constructor (it should not be!), The record takes the opposite in its field, assigning itself to the field of the opposite. All this should be an easily solvable final finale.

0
source share

I am using this solution:

 public enum Flippable { A, B, Y, Z; public Flippable flip() { switch (this) { case A: return Z; case B: return Y; case Y: return B; case Z: return A; default: return null; } } } 

Test:

 public class FlippableTest { @Test public void flip() throws Exception { for (Flippable flippable : Flippable.values()) { assertNotNull( flippable.flip() ); // ensure all values are mapped. } assertSame( Flippable.A.flip() , Flippable.Z); assertSame( Flippable.B.flip() , Flippable.Y); assertSame( Flippable.Y.flip() , Flippable.B); assertSame( Flippable.Z.flip() , Flippable.A); } } 

This version is even shorter but limited in flexibility:

 public enum Flippable { A, B, Y, Z; public Flippable flip() { return values()[ values().length - 1 - ordinal() ]; } } 
0
source share

All Articles