所以我试图搜索答案,但是对于这个错误信息,我正在找到解决问题的相关答案。
在这里。 为什么这段代码:
案例1)
public class A {
private final String A;
private final String B;
private final String C = A + B;
public A(String A, String B) {
this.A = A;
this.B = B;
}
}
行<{1}}的表示这些错误:
private final String C = A + B;
但这就像一个魅力:
情况2)
java: variable A might not have been initialized
java: variable B might not have been initialized
或者这也像魅力一样:
案例3)
public class K {
private final String K;
private final String L;
private final String M = kPlusL();
public K(final String K, final String L) {
this.K = K;
this.L = L;
}
private String kPlusL() {
return K + L;
}
}
有人可以解释一下为什么好吗?我正在使用IntelliJ IDEA和Java 1.8.0_151。
所有三个案例都做了完全相同的事情(把两个字符串放在一起),但是一个是直接做,第二个和第三个是“间接”。
答案 0 :(得分:2)
当您尝试在第一种情况下初始化C
时,A
和B
此时仍然未初始化,因此private final String C = A + B;
将失败。
尝试在构造函数中初始化C
:
public class A {
private final String A;
private final String B;
private final String C;
public A(String A, String B) {
this.A = A;
this.B = B;
this.C = A + B;
}
}
以下是JLS
中的相关位对于局部变量或空白最终字段x的每次访问,必须在访问之前明确分配x,否则会发生编译时错误。
答案 1 :(得分:1)
在案例2 中,您使用M
设置kPlusL()
的值,这会在连接期间将null转换为字符串。因此它的价值为&#34; nullnull&#34;。
在 case 3 中,您继承了一个类,因此在子类实例化之前将调用超类构造函数。因此,O
将分配P
和Q
的值。
答案 2 :(得分:1)
除了编译器抱怨的第一种情况外,另外两种情况可以通过 java对象初始化细节轻松解释。
我建议你阅读这篇文章,你会找到所有答案。
https://www.javaworld.com/article/2076614/core-java/object-initialization-in-java.html?page=2
答案 3 :(得分:1)
JLS部分确实是相关的(因为你已经得到它们,而不是再次链接它们),字节代码更有趣。
对于第一个例子(稍加修改):
private String A;
private String B;
private final String C = A + B;
public FirstExample(String A, String B) {
this.A = A;
this.B = B;
}
System.out.println(new FirstExample("a", "b").C);
这将打印nullnull
,如果您查看生成的字节代码(仅相关部分),这是有意义的。根据JLS,这个btw是正确的,因为实例字段在构造函数体的其余部分之前被初始化。
getfield // Field A:Ljava/lang/String;
getfield // Field B:Ljava/lang/String;
// this is just concat the two Strings with java-9
invokedynamic // InvokeDynamic #0:makeConcatWithConstants
....
putfield // Field C:Ljava/lang/String;
putfield // Field A:Ljava/lang/String;
putfield // Field B:Ljava/lang/String;
要获得的是实例变量在构造函数的其余部分之前初始化(基本上C
之前A
和B
)。现在有理由将final
添加到A
和B
无法编译。
第二个例子更有趣。这些被称为Forward references,并且是规范所允许的(实际上说它们不不允许是正确的)例如,这个:
String x = y;
String y = "a";
不允许,但另一方面,这是:
String x = getIt();
String y = "a";
public String getIt() {
return y;
}
缺点是y
将首先使用null
而不是a
进行初始化;因此x
将为空。
最后一个例子再次与JLS
保持一致。超级构造函数首先运行,从而初始化这些字段;仅在之后Q
变量通过继承(已经使用构造函数的值)字段初始化,从而产生预期值。
答案 4 :(得分:0)
因为First,它定义A和B,并使用A和B的值定义和初始化C,然后转到构造函数并初始化A和B.所以您可以看到A和B没有值直到调用构造函数。 请看下面的代码:
public class A {
private final String A; // = Null
private final String B; // = Null
private final String C = A + B; // Error Cause There is no value for A and B
public A(String A, String B) {
this.A = A;
this.B = B;
}
}
只需将C = A + B
放在构造函数中。
答案 5 :(得分:0)
由于前两个案例非常清楚并且得到了回答,所以我想关注第三个你继承的案例。根据初始化序列在JLS:
中指定在对新创建的对象的引用之前返回为 结果,处理指示的构造函数以初始化new 使用以下过程对象:
- 将构造函数的参数分配给此构造函数调用的新创建的参数变量。
如果此构造函数以同一个类中的另一个构造函数的显式构造函数调用(第8.8.7.1节)开头(使用此方法),则使用这五个相同步骤计算参数并以递归方式处理该构造函数调用。如果该构造函数调用突然完成,则此过程突然完成,原因相同;否则,继续步骤5.
此构造函数不以同一类中另一个构造函数的显式构造函数调用开头(使用此方法)。如果此构造函数用于Object以外的类,则此构造函数将以超类构造函数的显式或隐式调用开始(使用super)。使用这五个相同的步骤评估参数并递归处理超类构造函数调用。如果该构造函数调用突然完成,则此过程突然完成,原因相同。否则,请继续执行步骤4.
- 为此类执行实例初始化程序和实例变量初始值设定项,将实例变量初始值设定项的值按从左到右的顺序分配给相应的实例变量,在这些顺序中,它们以文本方式显示在类的源代码中。如果执行任何这些初始值设定项导致异常,则不会处理其他初始化程序,并且此过程会突然完成同样的异常。否则,请继续执行步骤5.
- 执行此构造函数的其余部分。如果执行突然完成,则此过程突然完成,原因相同。否则,此过程正常完成。
醇>
示例12.5-1。链接中给出的实例创建评估清楚地解释了初始化顺序。
因此,根据您的第三个示例(请参阅第3点和第4点),执行顺序为:
O
中的实例变量初始化程序(因为您未指定任何初始化程序,它们将初始化为null)O
构造函数 - public O(final String O, final String P)
为O
和P
分配给定值Q
中的实例变量初始值设定项 - private final String Q = O + P;
,其中包含O和P并将结果分配给Q Q
构造函数 - public Q(final String O, final String P)
结束
因此,通过完成类Q
构造函数,该对象已正确初始化。