当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了 复制 的手段来解决这个问题。javap -v Test$1将这段代码的字节码反编译可以得到下面的内容:
我们看到在run方法中有一条指令:
bipush 10
这条指令表示将操作数10压栈,表示使用的是一个本地局部变量。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。
com.testinner.Test$1(com.testinner.Test, int);
我们看到匿名内部类Test$1的构造器含有两个参数,一个是指向外部类对象的引用,一个是int型变量,很显然,这里是将变量test方法中的形参b以参数的形式传进来对匿名内部类中的拷贝(变量b的拷贝)进行赋值初始化。
也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来拷贝进行初始化赋值。
从上面可以看出,在run方法中访问的变量b根本就不是test方法中的局部变量b。这样一来就解决了前面所说的生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量b和test方法中的变量b不是同一个变量,当在run方法中改变变量b的值的话,会出现什么情况?
对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量b限制为final变量,不允许对变量b进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。
到这里,想必大家应该清楚为何方法中的局部变量和形参都必须用final进行限定了。
(3)静态内部类有特殊的地方吗?
从前面可以知道,静态内部类是不依赖于外部类的,也就说可以在不创建外部类对象的情况下创建内部类的对象。另外,静态内部类是不持有指向外部类对象的引用的。编译下面代码:
package com.testinner;
public class Test {
public static void main(String[] args) {
}
}
class Outter {
public static int j = 2;
//内部类
static class Inner {
public void innerFunction() {
System.out.println("innerFunction...");
//访问外部类的静态成员
System.out.println(j);
}
}
}
反编译字节码文件Outter$Inner可以看到内部类的构造器参数是没有Outter this&0引用的,也就是说静态内部类的实例化不依赖外部类对象。
3、内部类的使用场景和好处主要有四点:
① 每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整
② 方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏
③ 方便编写事件驱动程序
④ 方便编写线程代码
4、常见的与内部类相关的笔试面试题① 根据注释填写(1),(2),(3)处的代码
package com.testinner;
public class Test {
public static void main(String[] args) {
// 初始化Bean1
Test.Bean1 bean1 = new Test().new Bean1(); // (1)
bean1.i ;
// 初始化Bean2
Test.Bean2 bean2 = new Test.Bean2(); // (2)
bean2.j ;
// 初始化Bean3
Bean.Bean3 bean3 = new Bean().new Bean3(); // (3)
bean3.k ;
}
class Bean1 {
public int i = 0;
}
static class Bean2 {
public int j = 0;
}
}
class Bean {
class Bean3 {
public int k = 0;
}
}
对于成员内部类,必须先产生外部类的实例化对象,才能产生内部类的实例化对象。而静态内部类不用产生外部类的实例化对象即可产生内部类的实例化对象。
创建静态内部类对象的一般形式为: 外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()
创建成员内部类对象的一般形式为: 外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名()
② 下面这段代码的输出结果是什么?
public class Test {
public static void main(String[] args) {
Outter outter = new Outter();
outter.new Inner().print();
}
}
class Outter {
private int a = 1;
class Inner {
private int a = 2;
public void print() {
int a = 3;
System.out.println("局部变量:" a);
System.out.println("内部类变量:" this.a);
System.out.println("外部类变量:" Outter.this.a);
}
}
}
输出:
局部变量:3
内部类变量:2
外部类变量:1
5、Jdk1.8的不同
看下面代码,Jdk1.8引入了effectively final事实上的final,局部变量和形参不再强制需要声明为final了,只要不修改的变量的值是事实上的final,就可以。如果修改变量的值,则提示下图错误,编译不过去。
欢迎小伙伴们留言交流~~