vlambda博客
学习文章列表

JDK8(三)函数式接口的思考

函数式接口的思考

接口和抽象类的区别还在吗?抽象类还有意义吗?

  1. 接口中可以有抽象方法,默认方法,静态反方法;抽象类中可以有抽象方法,实例方法,静态方法。
  2. 接口中的属性是 public static final 的;抽象类中的属性则不是。
  3. 接口中的方法只能是 public 的,抽象了则不是
  4. 接口可以多实现,抽象类只能单继承

再看看 JDK 中对于抽象类和接口的应用,抽象类往往是实现接口中一些通用的方法,而子类只需要集成抽象类,实现个性化的方法即可。总而言之,接口定义了子类的行为,其中部分行为对于子类来说是一致的,这些行为适合放在抽象类中实现,对于子类个性化行为,应放在具体的子类中实现。

函数式接口的实例到底是什么?

函数式接口的实例,例如下面一段代码:

Function<String,String> function1 = (String a) -> a;

function1 是函数式接口的一个实例,这个实例的类型是什么?Function 只是一个接口,在 Java 中接口产生对象只能依附于其实现类,但是这儿我们并没有看见其实现类,但是为什么却有了一个实例?

System.out.println(function1.getClass());
System.out.println(function1.getClass().getInterfaces().length);
System.out.println(function1.getClass().getInterfaces()[0]);
System.out.println(function1.getClass().getSuperclass());

// 输出结果:
class com.lhf.ts.jdk.function.FunctionalTest$$Lambda$1/809762318
1
interface java.util.function.Function
class java.lang.Object

从结果中可以总结出:

  1. 函数式接口对象的类型是 JDK 自动生成的类型:FunctionalTest$$Lambda$1/809762318
  2. 函数式接口实例实现了一个接口,就是对应的函数式接口
  3. 函数式接口的实例的父类是 java.lang.Object。

第三点我觉得是比较重要的。因为所有函数式接口的实例类型都是 java.lang.Object 的子类,所以接口中的抽象方法如果是覆盖 java.lang.Object 的 public 方法,则不算做抽象方法。因为子类中必然包含了 java.lang.Object 中 public 方法的实现,如果子类自己没有实现,也默认继承了 Java.lang.Object 的实现,因此子类字节码中一定是有实现的。

静态方法和默认方法有什么问题吗?

JDK8 在接口中引入了静态方法和默认方法,这种设计思路使得 JDK8 中新增的特性完全兼容 JDK 以前版本的代码。以 java.util.List 接口为例,如果还是和以前一样,接口中不能有方法的实现,JDK8 如果在 java.util.List 接口中增加了 sort 方法,那么我们以前线上的代码如果某个类实现了 java.util.List 接口,那升级 JDK8 后编译直接报错,接口中的抽象方法子类中没有实现。

但是有没有发现一些问题

example1:

public interface MyInterface {
    default void test1(){
        System.out.println("MyInterface test1");
    }
}

public abstract class MyAbsClass{
    public void test1(){
        System.out.println("MyAbsClass test");
    }
}

public class TestClass extends MyAbsClass implements MyInterface {
    public static void main(String[] args) {
        TestClass test = new TestClass();
        test.test1();
    }
}

上例中,test 既是抽象类 MyAbsClass 的实例,又是接口 MyInterface 的实例,那调用 test.test1()方法,到底是调用抽象类的呢?还是调用接口的???

答案是:抽象类的。在 JDK 中,默认为类的调用级别比接口的高。

example2:

public interface MyInterface {
    default void test1(){
        System.out.println("MyInterface test1");
    }
}

public interface MyInterface2 {
    default void test1(){
        System.out.println("MyInterface2 test1");
    }
}

public class TestClass implements MyInterface,MyInterface2 {
    public static void main(String[] args) {
        TestClass test = new TestClass();
        test.test1();
    }
}

这种情况呢?都是接口级别的,那到底调用的是接口 1 的实现呢?还是接口 2 的实现呢?如果你将这段代码敲到 IDEA 里,就会发现,编译器检查出来不知道该调用哪个接口的,强制要求你在当前类中重写 test1()方法,因此调用的是当前类重写的 test1()方法。

函数式接口中的异常?

回想我们在 JDK8 之前的编程中,如果出现了未捕获的异常,异常会一层一层往上抛给方法的调用者。在函数式接口中一样,如果出现未捕获异常,则方法会从异常抛出的地方结束,将异常向上逐层抛给方法的调用者。

总结

函数式接口在运行中,其实 JDK 产生对应的 LambdaClass 类型,这个类型同样是 java.lang.Object 的子类型,而函数式接口中又仅有一个未实现的抽象方法,那么在这个类型中,将函数式接口实例指向的函数作为这个抽象方法的实现。后面 Stream API 中将会大量的使用到函数式接口,例如使用 lambda,方法引用或者构造方法引用去构造函数式接口的对象,其思想都是通过动态生成 Class 类型,利用字节码技术,将方法动态替换成使用时指定的行为,然后去调用这个行为实现的。

看完三件事JDK8(三)函数式接口的思考

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙JDK8(三)函数式接口的思考JDK8(三)函数式接口的思考

  1. 点赞,转发,你们的 『点赞和转发』,才是我创造的动力。

  2. 同时可以期待后续文章ing🚀