2021-01-21

3 篇文章 0 订阅

函数式接口的思考

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

  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类型,利用字节码技术,将方法动态替换成使用时指定的行为,然后去调用这个行为实现的。

 

图片

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值