前提:
匿名内部类
匿名内部类正如字面意思就是没有名字的类,但是在java中是不允许实例化一个完全没有类型的类的,所以匿名内部类是继承了某个类或者实现了某个接口的子类。在建立这个类的同时实例化这个类,所以这个类是能在建立的时候使用这么一次去创建一个对象,此外就无法在引用。
而且由于这个匿名内部类的对象实际是通过父类或者接口来持有的,也无法从外界调用类中新增的方法 ;同时这个类没有类名,所以也不允许有构造函数。
匿名内部类持有外部类的引用,这可能造成内存泄漏(例如在一个类中创建一个线程,实际创建了一个runnable接口的匿名内部类,这个匿名内部类会持有这个外部类对象的引用,所以即使这个对象可以被回收,但是创建出来的线程仍然没有停止的话,这个对象会一直被持有强引用,无法被回收)
匿名内部类使用的局部变量需要是final或者effectively final的(effectively final是指变量赋值之后不会再改变,即使没有显示加final)(因为局部变量是存在于栈上的,而匿名内部类的实例存在于堆上。虽然匿名内部类这个实例的引用是在栈上,但是这个实例本身还是在堆上的。为了避免匿名内部类在使用这个局部变量的时候,局部变量已经随着栈帧的弹出而销毁导致无法访问,匿名内部类复制了一份这个局部变量的值作为自己的字段,同时为了避免自己复制的值已经被更新,要求这个变量不能被修改)
1.函数式接口
有且仅有一个抽象方法的接口(静态方法、default方法、从object继承来的方法都不算)
可以用@FunctionalInterface注解(非必须,但是推荐)
其实也是给lambda表达式提供使用的
2.default函数
解决的主要问题:给接口添加方法,所有的实现类都会报错。通过添加default方法,可以给接口添加新的功能,但是完全不影响旧的实现类。(default方法只能在接口中使用)
default方法更像是一个普通的方法,可以被继承或重写,需要通过实现类实例调用,但是不能访问实例状态(因为接口没有实例)
同时java8也允许在接口中定义static函数
3.lambda表达式
lambda表达式实际是一个语法糖,是简洁的匿名函数的写法。可以说是java8最重要的一个新特性
lambda表达式不是匿名内部类的简写,替代匿名内部类只是lambda表达式的一个应用场景(匿名内部类本质是一个类创建一个对象,lambda表达式本质是一个表达函数的字面量)
它核心想要解决的问题就是把逻辑(方法)像数据一样传递
但是lambda表达式想表达的是一个函数逻辑,为了让lambda表达式能够准确无误地传递这个逻辑,不受其他方法的干扰的影响,lambda表达式用在简化匿名内部类的时候只能是"函数式接口"类型的时候使用,但是匿名内部类本身是没有这样的限制的。
interface TwoMethods {
void foo();
void bar();
}
TwoMethods t = () -> System.out.println("Hello");
因为假设有两个抽象方法的话,仅凭lambda表达式是无法判断具体是哪个方法的
通过lambda表达式,就可以非常整洁简单的传递想要的参数(对于使用函数接口的匿名内部类的地方来说实际是一个函数式接口的实例,但是我们只关注这个实例中对抽象方法重写的逻辑部分,所以对于new一个这个接口的实例这部分我们没有那么关注,更关注重写的这部分函数逻辑)
// 匿名内部类
Runnable r1 = new Runnable() {
public void run() {
System.out.println(this); // 打印:Outer$1@xxx
}
};
// Lambda
Runnable r2 = () -> System.out.println(this);
实现原理:
底层并不是在编译期生成新的.class文件,而是在运行时由JVM动态生成实现类(通过lambdaMeatfactory),并利用invokedynamic指令进行高效调用
public class lambdaDemo {
public static void main(String[] args) {
Runnable r = () -> System.out.println("Hello");
r.run();
}
}
通过javac编译又通过javap查看字节码得到

会看到有一个私有静态方法:private static void lambdamain0(),并且main方法中有一条invokedynamic指令,这个指令调用动态方法连接,#7指向常量池中BSM的引用,触发JVM的BSM(Bootstrap Mathod),通常是LambdaMetafactory.metafactory,来创建一个实现了Runnable接口的对象,并返回其引用
所以在编译期讲lambda题提取为私有静态方法,并在main中插入相关指令
这种方法动态生成一个实现Runnable的类,但这个类仅存在于JVM中,不会生成class文件写到磁盘(除非显示dump可以生成class文件)
主要是这几个,剩下的后面学习再补~OwO
补充:
抽象类和接口的区别:
两种都不能实例化
变量:抽象类可以定义各种变量,但是接口只能定义public static final的常量
构造器:抽象类可以有构造器,用于初始化状态,但是接口没有构造器
方法:抽象类可以有具体方法(protected、private、final、static等)
接口的具体方法只能是static和default,强制public
但是都不是必须有抽象方法
抽象类只能单继承,但是接口可以多实现
主要区别是语义上的区别,抽象类解决的是是什么问题,接口解决的是能实现什么功能的问题