I am trying to represent a State Transition Diagram, and I want to do this with a Java enum. I am well aware that there are many other ways to accomplish this with Map<K, V>
or maybe with a static initialization block in my enum. However, I am trying to understand why the following occurs.
Here is a(n extremely) simplified example of what I am trying to do.
enum RPS0
{
ROCK(SCISSORS),
PAPER(ROCK),
SCISSORS(PAPER);
public final RPS0 winsAgainst;
RPS0(final RPS0 winsAgainst)
{
this.winsAgainst = winsAgainst;
}
}
Obviously, this fails due to an illegal forward reference.
ScratchPad.java:150: error: illegal forward reference
ROCK(SCISSORS),
^
That's fine, I accept that. Trying to manually insert SCISSORS
there would require Java to try and setup SCISSORS
, which would then trigger setting up PAPER
, which would then trigger setting up ROCK
, leading to an infinite loop. I can easily understand then why this direct reference is not acceptable, and is prohibited with a compiler error.
So, I experimented and tried to do the same with lambdas.
enum RPS1
{
ROCK(() -> SCISSORS),
PAPER(() -> ROCK),
SCISSORS(() -> PAPER);
private final Supplier<RPS1> winsAgainst;
RPS1(final Supplier<RPS1> winsAgainst)
{
this.winsAgainst = winsAgainst;
}
public RPS1 winsAgainst()
{
return this.winsAgainst.get();
}
}
It failed with basically the same error.
ScratchPad.java:169: error: illegal forward reference
ROCK(() -> SCISSORS),
^
I was a little more bothered by this, since I really felt like the lambda should have allowed it to not fail. But admittedly, I didn't understand nearly enough about the rules, scoping, and boundaries of lambdas to have a firmer opinion.
By the way, I experimented with adding curly braces and a return to the lambda, but that didn't help either.
So, I tried with an anonymous class.
enum RPS2
{
ROCK
{
public RPS2 winsAgainst()
{
return SCISSORS;
}
},
PAPER
{
public RPS2 winsAgainst()
{
return ROCK;
}
},
SCISSORS
{
public RPS2 winsAgainst()
{
return PAPER;
}
};
public abstract RPS2 winsAgainst();
}
Shockingly enough, it worked.
System.out.println(RPS2.ROCK.winsAgainst()); //returns "SCISSORS"
So then, I thought to search the Java Language Specification for Java 19 for answers, but my searches ended up returning nothing. I tried doing Ctrl+F searches (case-insensitive) for relevant phrases like "Illegal", "Forward", "Reference", "Enum", "Lambda", "Anonymous" and more. Here are some of the links I searched. Maybe I missed something in them that answers my question?
None of them answered my question. Could someone help me understand the rules in play that prevented me from using lambdas but allowed anonymous classes?
EDIT - @DidierL pointed out a link to another StackOverflow post that deals with something similar. I think the answer given to that question is the same answer to mine. In short, an anonymous class has its own "context", while a lambda does not. Therefore, when the lambda attempts to fetch declarations of variables/methods/etc., it would be the same as if you did it inline, like my RPS0 example above.
It's frustrating, but I think that, as well as @Michael's answer have both answered my question to completion.
EDIT 2 - Adding this snippet for my discussion with @Michael.
enum RPS4
{
ROCK
{
public RPS4 winsAgainst()
{
return SCISSORS;
}
},
PAPER
{
public RPS4 winsAgainst()
{
return ROCK;
}
},
SCISSORS
{
public RPS4 winsAgainst()
{
return PAPER;
}
},
;
public final RPS4 winsAgainst;
RPS4()
{
this.winsAgainst = this.winsAgainst();
}
public abstract RPS4 winsAgainst();
}