作为一个大部分时间都在使用java的工程师,final这个关键字几乎无时无刻都能在代码里面看到,尤其是在使用多线程内部类的时候,一部分时间都在处理变量中的final问题。非常烦人,可一旦按照正常习惯去掉final,IDE中又常常会提示红色下划线错误,为什么匿名内部类使用其所在方法的局部变量式需要额外添加final关键字?
public void doWork(){
final int num=12;//基本数据类型
final List<String> list=new ArrayList();//引用数据类型
new Thread() {
public void run() {
System.out.println(num);
System.out.println(list.size());
}
}.start();
}
内部类首先是一个类,所以它的生命周期跟普通类一样从GC Roots对象出发,没有路径可达时候,就会被标记回收,进而消亡(HotSpot虚拟机内部使用的是可达性分析算法,不是像Python一样大部分使用引用计数算法)。 而上例中,方法的生命周期则随着方法的调用结束,栈桢弹出消亡。 先不说物理时间上两者是否相同,就但凭两者的生命周期过程来看,就不相关,自然各走各的,不一样。 拿上面示例来说,可以让内部类所启动的那个线程休眠几秒,这时候doWrok方法虽然运行结束了,而这个内部类就还没结束(其run方法还没运行完,可能run方法中的this还在引用这个类)。
既然生命周期都不一样,那么就会导致一个奇观:方法都运行结束了,其变量(上例中的num,list)居然还存活着。 那怎么解决这个问题呢?最直观想到就是内部类复制一份其所在方法的局部变量,这样虽然方法结束了,但我内部类有复制的,所以不矛盾。
上面的复制方法,虽然可以解决一个问题,但又引入一个新问题:就是对用户来说,我看到的是同一个变量,结果我在内部类中修改这个复制来的变量的值,居然方法外面不受影响(对于list这种引用类型来说我们修改值当然看不出来,但可以修改引用,使其指向一个新的引用)。
怎么解决这个语义不一致问题呢,答案就是加入final关键字。我不让你修改了,这样语义上和你平常使用习惯一直,尽管机制上使用了复制来解决,不是同一个。(基本类型是值复制,引用类型是引用复制)。 到这里就完全解释了开头提到的final的来源。
可以看出,这final,是一种为了解决一个问题带来的新问题的无奈实现。
在jdk8中已经不用显式声明final了,编译时候会隐形添加,但如果你试图修改变量值,则会报错, 因为上面提到问题并没有根本解决,只是一个小小的语法糖。
既然java中做了这么多折腾,最后无奈使用了final来保持语义一致,好呆这内部类有些更好的值得的优点吧。
答案:可以解决java中没有多继承的问题。例如从网上找一个多继承用法示例
但话又说回来,就算是多继承实际中真能用到吗,我反正是没用过,要不用接口,要不就通过别的方式绕过了。