- 此问题在此处已有答案**:
Accessing Same Class' enum value inside an enum declaration within a lambda doesn't compile(2个答案)
16小时前关门了。
我正在尝试表示一个State Transition Diagram,我想用Java枚举来完成这个任务。我很清楚有很多其他的方法可以用Map<K, V>
或者用枚举中的静态初始化块来完成这个任务。但是,我正在尝试理解为什么会出现下面的情况。
这里有一个(极其)简化的例子,说明我正在尝试做的事情。
enum RPS0
{
ROCK(SCISSORS),
PAPER(ROCK),
SCISSORS(PAPER);
public final RPS0 winsAgainst;
RPS0(final RPS0 winsAgainst)
{
this.winsAgainst = winsAgainst;
}
}
显然,这是由于非法的前向引用而失败的。
ScratchPad.java:150: error: illegal forward reference
ROCK(SCISSORS),
^
这很好,我接受。尝试手动插入SCISSORS
需要Java尝试并设置SCISSORS
,这将触发设置PAPER
,这将触发设置ROCK
,导致无限循环。我很容易理解为什么这种直接引用是不可接受的,并且由于编译器错误而被禁止。
所以,我尝试着用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();
}
}
它失败了,基本上是相同的错误。
ScratchPad.java:169: error: illegal forward reference
ROCK(() -> SCISSORS),
^
我对此有点困扰,因为我真的觉得lambda应该允许它不失败,但不可否认,我对lambda的规则、作用域和边界的理解还不够,因此无法有更坚定的看法。
顺便说一句,我尝试了添加花括号和返回lambda,但这也没有帮助。
所以,我尝试了一个匿名类。
enum RPS2
{
ROCK
{
public RPS2 winsAgainst()
{
return SCISSORS;
}
},
PAPER
{
public RPS2 winsAgainst()
{
return ROCK;
}
},
SCISSORS
{
public RPS2 winsAgainst()
{
return PAPER;
}
};
public abstract RPS2 winsAgainst();
}
令人震惊的是,它起作用了。
System.out.println(RPS2.ROCK.winsAgainst()); //returns "SCISSORS"
于是,我想在Java Language Specification for Java 19中搜索答案,但我的搜索结果是什么也没有返回。(不区分大小写)对于像"非法"、"转发"、"引用"、"枚举"、"λ"、""、"****"、"*****"、"****************************************************************************************************************************************************************"匿名"等等。这里是我搜索的一些链接。也许我错过了一些回答我问题的链接?
- 匿名类
- λ
- 字段引用的限制
- 枚举常量自引用的限制
他们都没有回答我的问题。有人能帮我理解阻止我使用lambdas但允许匿名类的游戏规则吗?
EDIT-@DidierL指出了一个到another StackOverflow post的链接,它处理类似的事情。我认为这个问题的答案和我的答案是一样的。简而言之,匿名类有自己的"上下文",而lambda没有。因此,当lambda试图获取变量/方法/等的声明时,就像你内联它一样,就像我上面的RPS0示例一样。
这是令人沮丧的,但我认为,以及"迈克尔的回答都回答了我的问题完成。
EDIT 2-添加此片段以供我与@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();
}
4条答案
按热度按时间mctunoxg1#
我相信这源于JLS所称的"definite assignment"。
首先,概述第一个示例中枚举的初始化顺序可能会有所帮助。
1.某个其他类第一次引用枚举类,例如
RPS0.ROCK.winsAgainst()
1.“clinit”(调用静态初始化式)
1.枚举常量按声明顺序初始化
1.相当于
public static final RPS0 ROCK = new RPS0(SCISSORS);
1.相当于
public static final RPS0 PAPER = new RPS0(ROCK);
1.相当于
public static final RPS0 SCISSORS = new RPS0(PAPER);
1.枚举类现在已装入
1.表达式
RPS0.ROCK
是为#1中的引用类计算的1.在该示例上调用
winsAgainst
。在
ROCK
行上失败的原因是SCISSORS
没有被“明确赋值”,知道了初始化的顺序,我们可以看到它甚至比这更糟,它不仅是“不明确赋值”,而且是“绝对不赋值”,SCISSORS
(3.3)的赋值发生在ROCK
(3.1)之后。当
ROCK
试图访问它时,如果编译器允许,SCISSORS
将为null。如果我们引入一些间接方式,我们就能看到这一点。由于我们的构造函数没有直接引用字段,所以明确赋值问题现在就没有了。编译器没有检查构造函数表达式中的任何明确赋值。构造函数使用的是方法调用的结果,而不是字段。
我们所做的只是欺骗编译器允许一些将要失败的东西。
lambda的情况非常相似。值仍然没有被明确赋值。考虑一个枚举构造函数在
Supplier
上调用get
的情况。请回头参考上面的初始化顺序。在下面的示例中,ROCK
将在SCISSORS
被初始化之前尝试访问它。这是编译器试图避免的潜在错误。真的很烦人,因为你知道你 * 没有 * 以那种方式使用供应商,而这是唯一一次它可能还没有被分配。
抽象类工作的原因是因为构造函数中的expression-target现在已经完全消失了。再次回顾初始化的顺序。你应该能够看到对于任何调用
winsAgainst
的东西,例如#1中的例子,调用(#6)必须发生在所有枚举常量已经初始化(#3)之后。编译器可以保证这种访问是安全的。把我们知道的两件事放在一起--我们可以使用间接寻址来阻止编译器抱怨缺少明确赋值,以及
Supplier
可以延迟地提供值--我们可以创建一个替代解决方案:只要构造函数从不调用
Supplier.get
(包括构造函数本身调用的任何方法),就可以证明这是安全的。t9aqgxwy2#
这并不是真正的答案,但是只要用匿名类替换第一个lambda表达式,它就可以工作了。
mv1qrgav3#
https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.3.3
根据这里的规则,匿名类似乎是可以接受的,因为它是一个“不同的类”。
示例中有几个注解说“ok -发生在不同的类中”
2g32fytz4#
我不是Maven,所以我可能是错的,但这是我的理解。
一个非法的前向引用意味着你试图在一个变量被定义之前使用它。这就像说
"i = 10; int i;"
。前两个实际上使用的是传入的变量。RPS 0和RPS 1将一个未知变量SCISSORS赋给一个需要RPS 0/RPS 1变量的字段。这应该是预期的结果。
对于Anonymous类,它必须做一些不同的事情。Java必须重新排序定义,首先定义RPS 2示例,然后示例化它们。