Skip to content

Instantly share code, notes, and snippets.

@arrayadd
Last active August 20, 2017 16:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arrayadd/e2ac06650ae7adc722dc77d890f0f15c to your computer and use it in GitHub Desktop.
Save arrayadd/e2ac06650ae7adc722dc77d890f0f15c to your computer and use it in GitHub Desktop.
一个java中的无奈实现

作为一个大部分时间都在使用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

在jdk8中已经不用显式声明final了,编译时候会隐形添加,但如果你试图修改变量值,则会报错, 因为上面提到问题并没有根本解决,只是一个小小的语法糖。


折腾了这么多,有什么优点呢?

既然java中做了这么多折腾,最后无奈使用了final来保持语义一致,好呆这内部类有些更好的值得的优点吧。

答案:可以解决java中没有多继承的问题。例如从网上找一个多继承用法示例

但话又说回来,就算是多继承实际中真能用到吗,我反正是没用过,要不用接口,要不就通过别的方式绕过了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment