13

I have an enum like the one below, but eclipse says that there are errors in the first definition of each opposite pair.

public enum Baz{
  yin(yang),    //Cannot reference a field before it is defined
  yang(yin),
  good(evil),   //Cannot reference a field before it is defined
  evil(good);

  public final Baz opposite;

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

What I want to accomplish is being able to use Baz.something.opposite to get the opposite object of Baz.something. Is there a possible workaround for this? Maybe an empty placeholder for yang and bad before yin and good are defined in this example?

Zaq
  • 1,348
  • 1
  • 11
  • 19

8 Answers8

14

With the switch statement:

public enum Baz{
  yin,
  yang,
  good,
  evil;

  public Baz getOpposite() {
    switch (this) {
        case yin: return yang;
        case yang: return yin;
        case good: return evil;
        case evil: return good;
    }
    throw new AssertionError();
}

Or deferred initialization:

public enum Baz{
  yin,
  yang,
  good,
  evil;

  public Baz opposite;

  static {
    yin.opposite = yang;
    yang.opposite = yin;
    good.opposite = evil;
    evil.opposite = good;
  }
}

You might wish to make the mutable field private and provide a getter.

meriton
  • 68,356
  • 14
  • 108
  • 175
13

You could try something like:

public enum Baz{
  yin("yang"),    
  yang("yin"),
  good("evil"),   
  evil("good");

  private String opposite;

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

  public Baz getOpposite(){
     return Baz.valueOf(opposite);
  }
}

and then reference it as

Baz.something.getOpposite()

That should accomplish what you are looking to do by looking up the enum value by it's string representation. I don't think you can get it to work with the recursive reference to Baz.

jcern
  • 7,798
  • 4
  • 39
  • 47
  • +1 You're right, you can't get it to work using the actual objects because `evil` won't be defined until after `good` is already initialized, but switching them doesn't work because then `good` isn't defined when `evil` is initialized. Good idea to use the names :) – Brian Sep 25 '12 at 23:08
  • 2
    Replacing typed enum values with untyped strings is not a good solution. The whole point of enums is to gave type safety to constants, and strings do not give you that. Plus, there is a much better solutions - doing this in a static initializer block, as @meriton points out in his answer. – Erwin Bolwidt Dec 20 '16 at 13:00
4

How about an EnumMap?

public enum Baz {
  yin,
  yang,
  good,
  evil;
  private static final Map<Baz, Baz> opposites = new EnumMap<Baz, Baz>(Baz.class);

  static {
    opposites.put(yin, yang);
    opposites.put(yang, yin);
    opposites.put(good, evil);
    opposites.put(evil, good);
  }

  public Baz getOpposite() {
    return opposites.get(this);
  }
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
3

You can also use abstract methods to delay, which has benefits of type safety over the accepted answer.

public enum Baz {

    yin(new OppositeHolder() {
        @Override
        protected Baz getOpposite() {
            return yang;
        }
    }),
    yang(new OppositeHolder() {
        @Override
        protected Baz getOpposite() {
            return yin;
        }
    }),
    good(new OppositeHolder() {
        @Override
        protected Baz getOpposite() {
            return evil;
        }
    }),
    evil(new OppositeHolder() {
        @Override
        protected Baz getOpposite() {
            return good;
        }
    });

    private final OppositeHolder oppositeHolder;

    private Baz(OppositeHolder oppositeHolder) {
        this.oppositeHolder = oppositeHolder;
    }

    protected Baz getOpposite() {
        return oppositeHolder.getOpposite();
    }

    private abstract static class OppositeHolder {
        protected abstract Baz getOpposite();
    }

}

And test code, because I needed it....

import org.junit.Test;
import static org.junit.Assert.fail;

public class BazTest {

    @Test
    public void doTest() {
        for (Baz baz : Baz.values()) {
            System.out.println("Baz " + baz + " has opposite: " + baz.getOpposite());
            if (baz.getOpposite() == null) {
                fail("Opposite is null");
            }
        }
    }
}
Pool
  • 11,999
  • 14
  • 68
  • 78
  • 1
    This answer is incorrect: The obfuscation with the anonymous class does not delay reading the static field, specifically, `Baz.yin.oppositeHolder.opposite` is `null`. All this does is confuse the compiler enough that it no longer warns about you reading a static final field before it is assigned. – meriton May 06 '15 at 20:12
  • Quite true, I meant to edit this but forgot about it. Updated now with a working version. – Pool May 07 '15 at 21:19
  • ... and how would you ensure that setOpposite is invoked before somebody reads the opposite? – meriton May 07 '15 at 21:54
  • Updated. There are many ways to achieve this effect. It is frowned on to call an instance method within a constructor, but I've left this way for brevity. – Pool May 09 '15 at 09:50
  • ... and got it wrong: The static field is read before it is assigned, in particular, `Baz.yin.getOpposite()` returns `null` ... – meriton May 09 '15 at 13:14
  • I really should test the code, I know it can work because I used such a pattern, but didn't have the code handy to copy. Fixed with working test added. – Pool May 10 '15 at 13:54
1

One more alternative :) using a map. It's quite verbose, but this way you can define each pair only once, the other direction is inferred.

enum Baz {

    YIN, YANG, GOOD, EVIL;

    private static final Map<Baz, Baz> opposites = new EnumMap<>(Baz.class);

    static {
        opposites.put(YIN, YANG);
        opposites.put(GOOD, EVIL);

        for (Entry<Baz, Baz> entry : opposites.entrySet()) {
            opposites.put(entry.getValue(), entry.getKey());
        }
    }

    public Baz opposite() {
        return opposites.get(this);
    }
}

Personally, I like meriton's second example the best.

Natix
  • 14,017
  • 7
  • 54
  • 69
0

And yet another possible implementation (similar to some of the other solutions, but with a HashMap).

import java.util.Map;
import java.util.HashMap;

public enum Baz {
 yin,
 yang,
 good,
 evil;

 private static Map<Baz, Baz> opposites = new HashMap<Baz, Baz>();
 static {
    opposites.put(yin, yang);
    opposites.put(yang, yin);
    opposites.put(good, evil);
    opposites.put(evil, good);
 }


 public Baz getOpposite() {
  return  opposites.get(this);
 }

}
GreyBeardedGeek
  • 29,460
  • 2
  • 47
  • 67
0

And then there is the totally OTT solution.

public enum Baz {
  yin,
  yang,
  good,
  evil,
  right,
  wrong,
  black,
  white;

  private static class AutoReversingMap<K extends Enum<K>> extends EnumMap<K, K> {
    public AutoReversingMap(Class<K> keys) {
      super(keys);
    }

    // Make put do both the forward and the reverse.
    public K put(K key, K value) {
      super.put(key, value);
      super.put(value, key);
      // Better to return null here than a misleading real return of one of the supers.
      return null;
    }
  }
  private static final Map<Baz, Baz> opposites = new AutoReversingMap<Baz>(Baz.class);

  static {
    // Assume even and odd ones are opposites.
    for (int i = 0; i < Baz.values().length; i += 2) {
      opposites.put(Baz.values()[i], Baz.values()[i + 1]);
    }
  }

  public Baz getOpposite() {
    return opposites.get(this);
  }
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • Yeah, over-the-top may be right... This is a great and thorough answer, but I think simpler answer may be better in this case. (also, I may add a value that is its own opposite, in which case, this isn't ideal) – Zaq Sep 25 '12 at 23:57
  • I totally agree. This was more for a bit of fun. I think @meriton's second suggestion is the neatest. – OldCurmudgeon Sep 26 '12 at 00:01
0

Years later, the shortest and most hacky solution

public enum Baz {
    YIN, // Use uppercase for enum names!
    YANG,
    GOOD,
    EVIL;

    public Baz opposite() {
        return values()[ordinal() ^ 1];
    }
}

It relies on the assumption that each member has an opposite and that they're arranged pairwise. It replaces the field by a method in the hope, that the JVM will optimize the whole overhead away. This is reasonable on desktop, less reasonable on Android.

To eliminate the overhead, I could use the static initializer as many other solutions here.

maaartinus
  • 44,714
  • 32
  • 161
  • 320
  • It isn't years later, only a few months. This is a good answer and I like its simplicity. I prefer lowercase on Java enum instances because they are practically the same as static final properties. Would the JVM really optimize out `opposite`? I'm not sure how much the JVM optimizes out. – Zaq May 21 '14 at 21:14
  • @user1494396 Simple short method call get *always* inlined whenever the JVM thinks it's performance-relevant. The call to `values()` normally [includes cloning](http://stackoverflow.com/a/1163121/581205), but this is the easiest case for [escape analysis](http://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html#escapeAnalysis). So I'd hope that nothing but field load, xor and static field load remain. This is still 3x as much work as with a field, but I guess that finding a real world example, where it can be measured is hard. – maaartinus May 21 '14 at 22:19
  • 1
    @user1494396 What else should be written in uppercase if not enums? The coding conventions says that constants should be, but it doesn't explain what a constant is. Maybe a [compile-time constant](http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.28), maybe a final (static?) variable pointing to an immutable object? – maaartinus May 21 '14 at 22:28
  • 1
    Honestly, static variables and enum values should be always in uppercase. – alexander Jan 30 '18 at 17:21