1. 从程序设计语言的理论上:局部内部类(即:定义在方法中的内部类),由于本身就是在方法内部(可出现在形式参数定义处或者方法体处),因而访问方法中的局部变量(形式参数或局部变量)是天经地义的.是很自然的
  2. 为什么JAVA中要加上一条限制:只能访问final型的局部变量?
  3. JAVA语言的编译程序的设计者当然全实现:局部内部类能访问方法中的所有的局部变量(因为:从理论上这是很自然的要求),但是:编译技术是无法实现的或代价极高.
  4. 困难在何处?到底难在哪儿?
    局部变量的生命周期与局部内部类的对象的生命周期的不一致性!
  5. 设方法f被调用,从而在它的调用栈中生成了变量i,此时产生了一个局部内部类对象inner_object,它访问了该局部变量i .当方法f()运行结束后,局部变量i就已死亡了,不存在了.但:局部内部类对象inner_object还可能 一直存在(只能没有人再引用该对象时,它才会死亡),它不会随着方法f()运行结束死亡.这时:出现了一个"荒唐"结果:局部内部类对象inner_object要访问一个已不存在的局部变量i!
  6. 如何才能实现? 当变量是final时,通过将final局部变量"复制"一份,复制品直接作为局部内部中的数据成员.这样:当局部内部类访问局部变量时,其实真正访问的是这个局部变量的"复制品"(即:这个复制品就代表了那个局部变量).因此:当运行栈中的真正的局部变量死亡时,局部内部类对象仍可以访问局部变量(其实访问的是"复制品"),给人的感觉:好像是局部变量的"生命期"延长了.

那么:核心的问题是:怎么才能使得:访问"复制品"与访问真正的原始的局部变量,其语义效果是一样的呢?
当变量是final时,若是基本数据类型,由于其值不变,因而:其复制品与原始的量是一样.语义效果相同.(若:不是final,就无法保证:复制品与原始变量保持一致了,因为:在方法中改的是原始变量,而局部内部类中改的是复制品)

当变量是final时,若是引用类型,由于其引用值不变(即:永远指向同一个对象),因而:其复制品与原始的引用变量一样,永远指向同一个对象(由于是final,从而保证:只能指向这个对象,再不能指向其它对象),达到:局部内部类中访问的复制品与方法代码中访问的原始对象,永远都是同一个即:语义效果是一样的.否则:当方法中改原始变量,而局部内部类中改复制品时,就无法保证:复制品与原始变量保持一致了(因此:它们原本就应该是同一个变量.)

一句话:这个规定是一种无可奈何.也说明:程序设计语言的设计是受到实现技术的限制的.这就是一例. 因为:我就看到不少人都持这种观点:设计与想法是最重要的,实现的技术是无关紧要的,只要你作出设计与规定,都能实现.

现在我们来看,如果我要实现一个在一个方法中匿名调用ABSClass的例子:

    public static void test(final String s){
        ABSClass c = new ABSClass(){
           public void m(){
              int x = s.hashCode();
              System.out.println(x);
           }
        };
     //其它代码.
    }

从代码上看,在一个方法内部定义的内部类的方法访问外部方法内局部变量或方法参数,是非常自然的事,但内部类编译的时候如何获取这个变量,因为内部类除了它的生命周期是在方法内部,其它的方面它就是一个普通类。那么它外面的那个局部变量或方法参数怎么被内部类访问?编译器在实现时实际上是这样的:

为什么匿名内部类和局部内部类只能访问final变量

是变量的作用域的问题,因为匿名内部类是出现在一个方法的内部的,如果它要访问这个方法的参数或者方法中定义的变量,则这些参数和变量必须被修饰为final。因为虽然匿名内部类在方法的内部,但实际编译的时候,内部类编译成Outer.Inner,这说明内部类所处的位置和外部类中的方法处在同一个等级上,外部类中的方法中的变量或参数只是方法的局部变量,这些变量或参数的作用域只在这个方法内部有效。因为编译的时候内部类和方法在同一级别上,所以方法中的变量或参数只有为final,内部类才可以引用。

Java代码:

public class MyClass {
    public MyClass() {
        final int finalValue = 10;
        int not$Final = 20;
        MyInterface myInterface = new MyInterface() {
            public void functionWithoutPara() {
                //compile Error
                //System.out.println(noFinal);
                System.out.println(finalValue);
            }   

            public void functionWithPara(int num) {
                System.out.println("The parameter " + num
                        + " has been passed by the method");
            }   

        };
        myInterface.functionWithoutPara();
        myInterface.functionWithPara(not$Final);
        System.out.println(myInterface.getClass().getName());
    }   

    public static void main(String[] args) {
        new MyClass();   

    }   

}

二、为什么局部内部类只能访问final变量

简单的来说是作用域的问题。就好像方法外面做的事情并不能改变方法内才定义的变量,因为你并不知道方法里面这个时候已经存在了这个局部变量了没有。在这个内部类中方法里面的本地变量是失效的,也就是不在作用域内,所以是不能够访问的

但是为什么这里用final却又可以访问呢? 
因为Java采用了一种copy->local->variable的方式来实现,也就是说把定义为final的局部变量拷贝过来用,而引用的也可以拿过来用,只是不能重新赋值。从而造成了可以access->local->variable的假象,而这个时候由于不能重新赋值,所以一般不会造成不可预料的事情发生

三、如果定义一个局部内部类,并且局部内部类使用了一个在其外部定义的对象,为什么编译器会要求其参数引用是final呢?
注意:局部内部类,包括匿名内部类。

原因如下:

abstract class ABSClass{
        public abstract void print();

        public class Test2{
            public static void test(final String s){//一旦参数在匿名类内部使用,则必须是final
                ABSClass c=new ABSClass(){
                     public void print(){
                           System.out.println(s);
                     }
                };
                c.print();
            }
            public static void main(String[] args){
                test("Hello World!");
            }
        }
    }
  • JVM中每个进程都会有多个根,每个static变量,方法参数,局部变量,当然这都是指引用类型.基础类型是不能作为根的,根其实就是一个存储地址.垃圾回收器在工作时先从根开始遍历它引用的对象并标记它们,如此递归到最末梢,所有根都遍历后,没有被标记到的对象说明没有被引用,那么就是可以被回收的对象(有些对象有finalized方法,虽然没有引用,但JVM中有一个专门的队列引用它们直到finalized方法被执行后才从该队列中移除成为真正没有引用的对象,可以回收,这个与本主题讨论的无关,包括代的划分等以后再说明).这看起来很好.
  • 但是在内部类的回调方法中,s既不可能是静态变量,也不是方法中的临时变量,也不是方法参数,它不可能作为根,在内部类中也没有变量引用它,它的根在内部类外部的那个方法中,如果这时外面变量s重指向其它对象,则回调方法中的这个对象s就失去了引用,可能被回收,而由于内部类回调方法大多数在其它线程中执行,可能还要在回收后还会继续访问它.这将是什么结果?
  • 而使用final修饰符不仅会保持对象的引用不会改变,而且编译器还会持续维护这个对象在回调方法中的生命周期.所以这才是final变量和final参数的根本意义.

最新文章

  1. jQuery 元素的选中, 置顶、上移、下移、置底、删除
  2. SQLite核心函数一览
  3. Android Device Orientation
  4. JS当前日期相加相减
  5. String or binary data would be truncated. The statement has been terminated.
  6. Kaggle—Digit Recognizer竞赛
  7. hdu_5800_To My Girlfriend(变种背包)
  8. Arrays工具、二维数组以及LeetCode练习题
  9. Linux新手随手笔记1.3
  10. Jenkins+Jmeter持续集成笔记(五:问题优化)
  11. 不同版本的Chrom浏览器对应的ChromDriver的版本
  12. python基础学习笔记(一)
  13. 手机常用meta标签-有注释
  14. HTML第二课——css
  15. linux文件映射到windows(方便用虚拟机搭建linux服务器,用本地windows代码编辑)
  16. Shell脚本里的双冒号是什么意思
  17. memcached 学习笔记 3
  18. 【JAVA】学习笔记
  19. 通过systemd配置Docker
  20. Day 29 process&thread_1

热门文章

  1. (MariaDB)开窗函数用法
  2. BlockingQueue阻塞队列(解决多线程中数据安全问题 可用于抢票,秒杀)
  3. 原生js去掉所有的html标签,最终得到HTML标签中的所有内容
  4. Oracle中rownum的说明及使用技巧
  5. Java程序员的Golang入门指南(上)
  6. CountDownLatch使用
  7. Spark:聚类算法之LDA主题模型算法
  8. 2017京东校招面试回忆(已成功拿到offer)
  9. 自定义progressDialog(数据加载框)的实现
  10. Maven简介(Maven是什么)