3

I am trying to access an enum value inside the declaration of another enum value in the same class. Following is the way I found to get that done .

Java enum- Cannot reference a field before it is defined

which compiles just fine but since it involves a lot of formal code, I tried to replace the interface implementations with a Lambda but it doesn's compile with an error such as

Cannot reference a field before it is defined

Following is the lambda replacement I did.

package test;
public enum Baz {

yin(() -> {
    return Baz.yang;//doesnt compile. ->Cannot reference a field before it is defined
}),
yang(new OppositeHolder() {
    @Override
    public Baz getOpposite() {
        return yin;
    }
}),
good(new OppositeHolder() {
    @Override
    public Baz getOpposite() {
        return evil;//BUT THIS COMPILES JUST FINE, EVEN THOUGH IT ACCESSES evil WHICH HAS BEEN DECLARED LATER.
    }
}),
evil(new OppositeHolder() {
    @Override
    public Baz getOpposite() {
        return good;
    }
});

private final OppositeHolder oppositeHolder;

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

private static interface OppositeHolder {
    public Baz getOpposite();
}

}

Is there any way I could do this with the help of lambda expressions, since I could be seeing a lot of enums in this class and i don't really want to repeat the boiler plate code.

EDIT I know that the error means that I cannot access an Enum that has been declared later. Also, I cannot really have the enum name stored as a string since that would be a maintenance nightmare for me. Also, I am curious why the example in the link compiles but mine does not.

Didier L
  • 18,905
  • 10
  • 61
  • 103
Vishal Tyagi
  • 560
  • 9
  • 26
  • 1
    Enum constants should be in UPPER_SNAKE_CASE. – MC Emperor Mar 14 '19 at 13:13
  • 2
    In the version that works, it's because the arguments to the enum instances are objects of totally separate classes, but lambda expressions are not exactly the same thing, as they share the context in which they are defined. They are not just syntactic sugar for anonymous classes. – DodgyCodeException Mar 14 '19 at 13:31

2 Answers2

3

This cannot be done by simply replacing anonymous interface implementations by corresponding lambda expressions, because they are not the same thing.

A lambda body does not have its own context. This is defined in the Java Language Specification, § 15.27.2:

Unlike code appearing in anonymous class declarations, the meaning of names and the this and super keywords appearing in a lambda body, along with the accessibility of referenced declarations, are the same as in the surrounding context (except that lambda parameters introduce new names).

(Emphasis mine)

That means you have effectively the same problem as the OP of your linked question.


I doubt if I would follow your approach. I personally prefer the code from this answer.

enum Baz {

    YIN,
    YANG,
    GOOD,
    EVIL;

    private static final Map<Baz, Baz> OPPOSITES;
    static {
        Map<Baz, Baz> m = new EnumMap<>(Baz.class);
        m.put(YIN, YANG);
        m.put(YANG, YIN);
        m.put(GOOD, EVIL);
        m.put(EVIL, GOOD);
        OPPOSITES = Collections.unmodifiableMap(m);
    }

    public Baz opposite() {
        return Objects.requireNonNull(OPPOSITES.get(this));
    }
}
MC Emperor
  • 22,334
  • 15
  • 80
  • 130
0

the issue is that your using variable before they are defined even if it's a lambda call. A workaround can be to use valueOf

public enum Baz {

        yin(() -> Baz.valueOf("yang")),
        yang(() -> Baz.valueOf("yin")),
        good(() -> Baz.valueOf("evil")),
        evil(() -> Baz.valueOf("good"))
        ;

        private final OppositeHolder oppositeHolder;

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

        private static interface OppositeHolder {
            public Baz getOpposite();
        }
    }

    public static void main(String... args){
        System.out.println(Baz.yin);
        System.out.println(Baz.yin.oppositeHolder.getOpposite());

    }

output is

yin
yang
Wisthler
  • 577
  • 3
  • 13
  • In this case you don't even need the OppositeHolder. You could just pass the opposite name as String to the constructor and look up the value there. Usually I would say using constants would be better but since `valueOf` would throw an exception at application start if it can't find an enum, I think this is fine. – kapex Mar 14 '19 at 12:45
  • I am sorry but the link that i have mentioned does compile just fine even though it does use the enums before they are declared. Would be helpful if you could go through the link first. – Vishal Tyagi Mar 14 '19 at 12:49
  • Also, i can't really have the enums in strings there because of maintainablity issues. – Vishal Tyagi Mar 14 '19 at 13:01
  • 1
    @VishalTyagi as explained in a previous comment, annonymous class and lambda are not fully equivalent. Something about initialization order. Now, based on your question, the point was to have the same result using lambda which is shorter to write. That's what I did. If it does not match you real problem, you should then update your question to match that problem. – Wisthler Mar 14 '19 at 14:48