每日一技
使用Java时遇到一个问题,简单记录一下
class OutClass{
public void doSomeThing(){
String text = "HelloWorld";
class InnerClass {
InnerClass(){
System.out.println(text);
}
}
new InnerClass();
}
}
public class Main {
public static void main(String[] args){
new OutClass().doSomeThing();
}
}
上述代码意为从局部内部类中访问外部字段。
该代码在 Jdk-7u80(包括)及更低版本上编译时报错,报错内容为:java: 从内部类中访问本地变量text; 需要被声明为final类型 但在Jdk-8(包括)及更高版本上编译正常。
我以为是Java8支持了局部内部类访问非final字段,但发现并非如此。 在Java SE 7规范最终发行版JSR-000336 附件3 - Java语言规范 - 8.1.3节(完整版第187页)有规定:
Any local variable, formal parameter, or exception parameter used but not declared in an inner class must be declared final. 任何局部变量、形参或已使用但未在内部类中声明的异常参数都必须声明为 final。
这解释了为什么在Jdk-7u80之前的版本上编译失败。
在Java SE 8规范最终发行版JSR-000337 附件3 - Java语言规范 - 8.1.3节(第199页)将上述规定修改为:
Any local variable, formal parameter, or exception parameter used but not declared in an inner class must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted. 任何局部变量、形参或已使用但未在内部类中声明的异常参数都必须声明为 final 或是实际上的final(第 4.12.4 节),否则在尝试使用时会发生编译时错误。
在Java SE 8规范最终发行版JSR-000337 附件3 - Java语言规范 - 4.12.4节(第86页)对effectively final(实际上的final)的定义为:
*A local variable whose declaration lacks an initializer is effectively final if all of the following are true: • It is not declared final. • Whenever it occurs as the left-hand operand of an assignment operator, it is definitely unassigned and not definitely assigned before the assignment; that is, it is definitely unassigned and not definitely assigned after the right-hand operand of the assignment (§16 (Definite Assignment)). • It never occurs as the operand of a prefix or postfix increment or decrement operator.
翻译 如果以下所有条件都为真,则缺少声明初始化的局部变量是实际上的final变量: • 它没有被声明为final。 • 当它作为赋值运算符的左操作数出现时,它必须是未赋值的,并且在赋值之前没有赋值过。 换句话说,它肯定是未赋值的,并且不能在右手操作数赋值之后再赋值(第 16 节(定义赋值))。 • 它永远不会作为前缀或后缀递增或递减运算符的操作数出现。*
上述代码中的text变量,虽然没有被声明为final,但是满足final的条件。因此在Java中可以直接当做final变量来用。 综上,并不是Java 8之后支持了局部内部类访问非final字段,而是Java会根据字段的使用情况自行判断是否final,可以忽略显式定义final。
如果将代码改为:
class OutClass{
public void doSomeThing(){
String text = "HelloWorld";
class InnerClass {
InnerClass(){
text = "WorldHello";
System.out.println(text);
}
}
new InnerClass();
}
}
public class Main {
public static void main(String[] args){
new OutClass().doSomeThing();
}
因为text发生了改变,所以text不能为final变量,因此编译出错:java: 从内部类引用的本地变量必须是final变量或实际上的final变量