Pregunta

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 doesn't compile, because Z and Y haven't been declared to be allowed to be arguments of A and B's constructor.

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, this seems stylistically quite gross. Though I can't put a finger on why this would be a real problem.

Potential solution 2: static loading

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 gross than the first solution, as the field is no longer final, and is vulnerable to reflection. Ultimately that is an obscure worry, but suggests a bad code smell.

Is there a way to do this that is essentially the same as the first example, but compiles properly?

¿Fue útil?

Solución

Again perhaps not as pretty 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());
        }
    }
}

Otros consejos

As you can see it's not possible due to enum constants are static and you could not initialize A until Z is not 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);
  }
}

Just map 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

Nice question. Perhaps you will 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 could index into the array 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.

As long as the field remains private to the enum, I am not sure whether the final status of it is truly of much concern.

That said, if an opposite is defined in the constructor (it doesn't have to be!), the entry takes said opposite into its own field while assigning itself to the field of the opposite. This should all be easily resolvable throujhgh finals.

I'm 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 is limited in flexibility:

public enum Flippable {

    A, B, Y, Z;

    public Flippable flip() {
            return values()[ values().length - 1 - ordinal() ];
    }

}

The code below compiles fine, and meets all of the OP's requirements, but fails at runtime with Exception in thread "main" java.lang.ExceptionInInitializerError. It is left here to rot as an example of what not to do for all those who stumble upon it.

public enum Flippable {
A, B, Y, Z;

private final Flippable opposite;

private Flippable() {
    this.opposite = getOpposite(this);
    verifyIntegrity();
}

private final Flippable getOpposite(Flippable f) {
    switch (f) {
        case A: return Z;
        case B: return Y;
        case Y: return B;
        case Z: return A;
        default:
            throw new IllegalStateException("Flippable not found.");
    }
}

private void verifyIntegrity() {
    // integrity check:
    Arrays.stream(Flippable.values())
    .forEach(f -> {
        if(!f.flip().flip().equals(f)) {
            throw new IllegalStateException("Flippable " + f + " is inconsistent.");
        }
    });
}

public Flippable flip() {
    return opposite;
}

}

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top