Self referential enum with immutable parameters

99封情书 提交于 2019-12-04 03:31:05
robert

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());
        }
    }
}

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;
}

}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!