你知道匿名内部类、Lambda表达式为嘛只能使用外部final的变量吗?

简介: 你知道匿名内部类、Lambda表达式为嘛只能使用外部final的变量吗?

前言


各位小伙伴大家好,我是A哥。各位都知道,匿名内部类在使用的时候需要使用外部的变量,该变量必须被final修饰,否则编译报错。实际使用中,有时候确实还给我们造成了不少麻烦,可大家可曾想过这是为什么吗?

正文



在了解原因之前,我们最好先了解一下javascript的一个概念:js闭包。然而Java内部类其实就是闭包:包含指向外部类的指针。


比如这个结构:就是典型的闭包


class Outer {
    private class Inner {
        private int y = 100;
        public int innerAdd() {
            return x + y;
        }
    }
    private int x = 100;
}


附内存结构图:


image.png


匿名内部类的例子如下:


interface AnnoInterface {
    int add();
}
class Outer {
    public static void main(String[] args) {
        int x = 100; //此处在java8里 final不用写,但其实是final的
        AnnoInterface annoInterface = new AnnoInterface() {
            int y = 100;
            @Override
            public int add() {
                //x = 10; //因为x是final的 无法更改
                return x + y;
            }
        };
        x = 10; //这句显然,也是会引起编译报错的
    }
}


如上,若x不是final的,那就报错了。但是jdk运用类型推断可以不用写,但是我建议还是写上吧。


至于为什么一定得是final的呢?这个就得从两个方面阐述原因:


        1.final修饰的变量有什么特别?


        2.为什么需要final修饰的这个特点?


用final修饰的成员变量表示常量,存在内存中的常量区(常量区位于堆区),放在常量区里面,所以效率上相对来说会高那么一点。


为什么匿名内部类用的变量必须final呢?


从Java设计的角度来说,单纯的就为了保护数据安全和代码稳定。因为Java通过类的封装规范了类与类之间的访问权限,而内部类却打破了这种规范,它可以直接访问自身所在的外部类里私有成员,而且自身还可以创建相同的成员,从作用域角度看,内部类的新成员修改了什么值,外部方法也是不知道,因为程序的运行由外而内的,所以外部根本无法确定内部这时到底有没有这个东西。综上所述,选择final来修饰外部方法的成员,让其引用地址保持不变、值也不能被改变保证了外部类的稳定性。当然还有关于变量生命周期的阐述,以后会再给出详解~


顺便插一句:如果用final修饰方法,您将获取至少如下两个好处(所以建议咱们的serviceImpl都可以final化):


     1.方法锁定,防止任何子类修改其含义和语意


     2.高效,jvm在调用final方法时会转入内嵌机制进行inline优化(inline优化是指:在编译的时候直接调用方法代码替换,也就是内嵌,而不是在运行时调用方法。所以其实,private方法,默认就是final的,会使用内嵌机制调用。so,能private的就private掉吧),大大提高执行效率。

class A {
    public final int c;
    public A(int s) {
        c = s;
    }
    public static void main(String[] args) {
        A a = new A(2);
        System.out.println(a.c); //打印2
        a = new A(3);
        System.out.println(a.c); //打印3
    }
}


如上,同样是c,一个打印2,一个打印3,难道final的c是可以被修改的?其实这里并不是这样的,因为你new出来的A对象是两个,然后这个c是成员变量属于对象的,所以地址值是不一样的,所以根本就不是同一个,谈何不变呢?


如果你写成public static final int c,那这样就会编译报错了,而只能像下面这样初始化才行,不能在构造函数初始化了


class A {
    public static final int c;
    static {
        c = 1;
    }
}

fianl 修饰符修改的变量如果不对其进行初始化编译器会报错的。***但是***有两种情况可以不对其进行初始化。


第一种情况是在静态代码块中初始化。(当然这要求成员变量也是静态的)


第二种情况是在构造方法中进行初始化。


使用场景


匿名内部类的使用也是非常非常多的,所以理解为什么,能够更好的使用内部类,从而可以更优美的去规划自己的代码结构


总结


任何一向规定、规范都不是凭空制定而且也不可能随便下定义的。虽然final在我们平时使用中给我们带来了不少麻烦,但是我们应该也能想到,它给我们的程序带来了安全保证,所以各位同学还是可以理解的哈



相关文章
|
1月前
学习 static 定义静态变量的用法
学习 static 定义静态变量的用法。
45 13
|
3月前
|
安全 Java
synchronized修饰普通方法和静态方法的区别
在Java多线程中,`synchronized`关键字用于同步机制,确保线程安全。修饰普通方法时,同步基于对象实例,仅同步访问同一对象的方法调用,不同对象间互不影响。修饰静态方法时,则基于类级别,所有对象访问同一静态方法时会互相阻塞。前者适合保护对象状态,后者适用于类级资源同步。需注意,修饰静态方法可能会影响并发性能。实际应用中应根据需求选择适当的同步策略,以平衡程序正确性和效率。
|
Java 编译器
【Java面试】为什么匿名内部类只能访问外部类的final类型局部变量?
【Java面试】为什么匿名内部类只能访问外部类的final类型局部变量?
152 0
|
6月前
|
C++
C++——类和对象(初始化列表、匿名对象、static成员、类的隐式类型转换和explicit关键字、内部类)
C++——类和对象(初始化列表、匿名对象、static成员、类的隐式类型转换和explicit关键字、内部类)
|
6月前
|
存储 设计模式 算法
[C++] static静态成员变量/函数的用法
[C++] static静态成员变量/函数的用法
82 1
|
6月前
|
Java
【Java基础】类名、抽象类、接口名的参数传递及成员内部类、局部内部类和匿名内部类的使用
【Java基础】类名、抽象类、接口名的参数传递及成员内部类、局部内部类和匿名内部类的使用
76 0
|
存储 Java
浅谈匿名内部类和局部内部类只能访问final变量
浅谈匿名内部类和局部内部类只能访问final变量
81 0
|
安全 搜索推荐 Java
6.1 使用局部内部类优化代码:局部内部类的定义与用法
6.1 使用局部内部类优化代码:局部内部类的定义与用法
99 0
|
Java
3.4 内部类的类型:匿名内部类
3.4 内部类的类型:匿名内部类
66 0
定义了一个类A,S是类外的一个函数,通过A.S=S进行赋值
假设类 A 已经定义好了,现在可以通过 A.S = S 的方式将函数 S 赋值给类 A。这样做的效果是,将 S 函数作为类 A 的一个属性,并且可以通过该属性来调用函数 S。 下面是一个简单的例子: