抽象类回顾
jdk1.8 之前,接口里只能做方法定义不能有方法的实现,因此通常会在抽象类里面实现默认的方法,一般这个默认的方法是抽象后公用的方法,不需要每一个继承者都去实现,只需调用即可,就像下面这样:
1// 定义
2public abstract class AFormula {
3 abstract double calculate(int a);
4 // 平方根
5 double sqrt(int a) {
6 return Math.sqrt(a);
7 }
8}
9
10// 使用
11@Test
12public void test_00() {
13 AFormula aFormula = new AFormula() {
14 @Override
15 double calculate(int a) {
16 return a * a;
17 }
18 };
19 // 求平方: 4
20 System.out.println(aFormula.calculate(2));
21 // 求开方: 1.4142135623730951
22 System.out.println(aFormula.sqrt(2));
23}接口回顾
在 jdk1.8 里面,不仅可以定义接口,还可以在接口中提供默认的实现。这一个小小的改变让整个抽象设计都随着改变了。
1// 定义, default 关键字必须
2public interface IFormula {
3 double calculate(int a);
4 // 平方根
5 default double sqrt(int a) {
6 return Math.sqrt(a);
7 }
8}
9
10// 使用
11@Test
12public void test_01() {
13 IFormula formula = new IFormula() {
14 @Override
15 public double calculate(int a) {
16 return a * a;
17 }
18 };
19 System.out.println(formula.calculate(2));
20 System.out.println(formula.sqrt(2));
21}
22
23@Test
24public void test_02() {
25 // 入参 a 和 实现
26 // a 是一个入参名称, 可以其他任何名字
27 // -> a * a: 箭头指向是具体的实现
28 // 但是这样不太适合加日志了
29 IFormula formula = a -> a * a;
30 System.out.println(formula.calculate(2));
31 System.out.println(formula.sqrt(2));
32}Lambda 表达式
因为接口中可以增加默认的方法实现,那么肯定是因为要简化开发才出现的这个设计。所以 List、Set 等等接口中会看到有默认的方法实现:
1List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
2
3// Collections 工具类提供了静态方法 sort 方法
4// 入参是一个 List 集合,和一个 Comparator 比较器,以便对给定的 List 集合进行排序
5// 这里创建了一个匿名内部类作为入参,这种类似的操作在我们日常的工作中随处可见。
6Collections.sort(names, new Comparator<String>() {
7 @Override
8 public int compare(String a, String b) {
9 return b.compareTo(a);
10 }
11});
12
13// Java 8 中不再推荐这种写法,而是推荐使用 Lambda 表达式
14// 这段同样功能的代码块,简短干净了许多
15Collections.sort(names, (String a, String b) -> {
16 return b.compareTo(a);
17});
18
19// 还可以更加简短
20// java.util.List 集合现在已经添加了 sort 方法
21// 而且 Java 编译器能够根据类型推断机制判断出参数类型,所以连入参的类型都可以省略了
22names.sort((a, b) -> b.compareTo(a));
23
24// java.util.List.sort
25default void sort(Comparator<? super E> c) {
26 Object[] a = this.toArray();
27 Arrays.sort(a, (Comparator) c);
28 ListIterator<E> i = this.listIterator();
29 for (Object e : a) {
30 i.next();
31 i.set((E) e);
32 }
33}
34
35// 以为这就结束了吗,其实还可以更短
36// 得益于 Comparator 接口中还提供了 static 默认方法
37// 也就是说接口中不是只可有 default 默认实现,还可以有静态方法
38names.sort(Comparator.reverseOrder());
39
40// java.util.Comparator
41public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
42 return Collections.reverseOrder();
43}函数式接口(Functional Interfaces)
官方介绍:
How does lambda expressions fit into Java's type system? Each lambda corresponds to a given type, specified by an interface. A so called functional interface must contain exactly one abstract method declaration. Each lambda expression of that type will be matched to this abstract method. Since default methods are not abstract you're free to add default methods to your functional interface.
通过上文的例子可以看到通过 Lambda 可以开发出同样功能的逻辑但是代码却很简单,那么 Jvm 是如何进行类型推断并且找到对应的方法呢?
通过官文介绍以及我们使用发现,并不是每个接口都可以缩写成 Lambda 表达式的开发方式。其实是只有那些函数式接口才能缩写成 Lambda 表示式。
所谓函数式接口就是只包含一个抽象方法的声明。针对该接口类型的所有 Lambda 表达式都会与这个抽象方法匹配。
需要注意,以下方法不计入抽象方法数量中:
- default 方法:在接口中添加 default 修饰的方法并不算抽象方法。
- static 方法:静态方法也不算抽象方法。
- Object 类的公共方法:接口中对 java.lang.Object 类公共方法(如 equals、hashCode、toString)的覆盖声明不算抽象方法,因为任何实现类最终都会继承 Object 的实现。
总结:为了保证一个接口明确的被定义为一个函数式接口,我们需要为该接口添加注解:@FunctionalInterface。这样,一旦你添加了第二个抽象方法,编译器会立刻抛出错误提示。
当然,即使不添加该注解,只要接口满足仅有一个抽象方法的条件,它依然是一个函数式接口,注解主要起编译期校验和明确意图的作用)。
以 java.util.Comparator 接口为例,它在 Java 8 中被标记了 @FunctionalInterface 注解。查看其源码你会发现,它内部不仅包含了多个 default 方法(如 reversed())和静态方法(如 comparing()),甚至还显式声明了 boolean equals(Object obj);。但由于 default 方法和静态方法不算抽象方法,而 equals 属于对 Object 公共方法的覆盖也不计入数量,因此 Comparator 实际上仅有一个核心抽象方法 int compare(T o1, T o2),完全符合函数式接口的定义。当我们在排序时传入 Lambda 表达式,JVM 就会自动推断并将该表达式的逻辑映射到这个唯一的 compare 方法上。
接下来写一段代码来加深印象:
1// 定义含有注解 @FunctionalInterface 的接口
2@FunctionalInterface
3public interface IConverter<F, T> {
4 T convert(F from);
5}
6
7// 使用
8// 传统方式
9IConverter<String, Integer> converter01 = new IConverter<String, Integer>() {
10@Override
11public Integer convert(String from) {
12 return Integer.valueOf(from);
13}
14
15// 简化一下
16IConverter<String, Integer> converter02 = (from) -> {
17 return Integer.valueOf(from);
18};
19
20// 继续简化
21IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
22
23// 还能再简化,不过这属于方法引用的内容了,下文会提到
24IConverter<Integer, String> converter04 = String::valueOf;