JDK8(三)函数式接口的思考
函数式接口的思考
接口和抽象类的区别还在吗?抽象类还有意义吗?
-
接口中可以有抽象方法,默认方法,静态反方法;抽象类中可以有抽象方法,实例方法,静态方法。 -
接口中的属性是 public static final 的;抽象类中的属性则不是。 -
接口中的方法只能是 public 的,抽象了则不是 -
接口可以多实现,抽象类只能单继承
再看看 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
从结果中可以总结出:
-
函数式接口对象的类型是 JDK 自动生成的类型:FunctionalTest$$Lambda$1/809762318 -
函数式接口实例实现了一个接口,就是对应的函数式接口 -
函数式接口的实例的父类是 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 类型,利用字节码技术,将方法动态替换成使用时指定的行为,然后去调用这个行为实现的。
看完三件事
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
点赞,转发,你们的 『点赞和转发』,才是我创造的动力。
同时可以期待后续文章ing🚀