接口与抽象类

在学习Java语言的过程中,你可能会产生疑问,为什么Java要在已有抽象类的情况下,再引入了接口(interface)的概念呢?举一个例子,有一个名为Comparable的接口用于比较,如果将其设计为一个抽象类,可以使用如下语法

abstract class Comparable {
    public abstract int compareTo(Object o);
}

这样一来,只要其他类想要使用compareTo方法,只需要extends这个类即可

class Test extends Comparable{
    @Override
    public int compareTo(Object o) {
        return 0;
    }
}

这样确实有助于用户的理解,但是遗憾的是,Java的设计者在设计Java语言时,选择了不支持多重继承机制,而在其他的与语言中,比如C++,允许一个类有多个超类,也就是多重继承,主要的原因是多重继承会让语法变的更加复杂(如C++中的虚基类,横向指针类型转换等),或者使运行的效率降低(如Eiffel)

实际上,使用接口可以提供多重继承的大多数特征,同时还能避免多重继承带来的不良后果

接口与回调

回调(callback)是一种非常常见的程序设计模式。在这种模式中,我们可以指定某个事件发生时采取的动作,回调并不是Java的专属,任何一种语言中都有回调的思想。简单地说,把一个函数作为参数传给另一个函数,那么这个函数就是一个回调函数

在Java中,有一个用于比较的Comparator接口,可以用于自定义sort方法,比如Arrays.sort(),Collections.sort(),这里我们用Arrays.sort()举例,对于数组来说,默认是升序排序,如果想要降序排序,就可以传入一个实现了Comparator接口的对象作为参数,这里,就是一个“回调”的过程,因为在排序前,compare方法并不会被调用,只有在调用sort方法时才会被调用

public static void main(String[] args) {
        Integer[] arr = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
        Arrays.sort(arr, new ReverseSort());
    }
  
    //静态内部类实现Comparator接口
    static class ReverseSort implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    }

 回到Java语言,如果我把一个方法作为另一个方法的参数,是不是就完成了回调的操作呢?这听起来非常完美,但是很遗憾,在Java中传递一个代码段很不容易,Java是一种面向对象语言,必须构造一个含有这个方法的类作为参数,这样会显得代码非常复杂,因为每次使用回调函数都要新建一个类。Java设计者并不想让Java支持类似其他语言的函数式编程,在多年的尝试后设计者们找到了一个适合Java的设计

Lambda表达式

语法

在上面的例子中,我们传入对象来进行降序排序,需要计算o2-o1,o2和o1是什么?它们都是Integer,而Java是一种强类型语言,我们还要加上类型

(Integer o1, Integer o2) -> { return o2 - o1; }

 这就是你看到的第一个lambda表达式,lambda表达式就是一个代码块。为什么取lambda这个名字呢?很多年前,有个数学家Alonzo Church想要形式化的表示能有效计算的数学函数。对于有些不知道该如何计算的函数,他使用了希腊字母lambda(λ)来标记参数。从那以后,带参数变量的表达式就被称为lambda表达式

lambda表达式的一种简单形式为:参数,箭头(->)以及表达式,特别的,对于可以用一个表达式完成的,甚至可以去掉花括号,并且隐式使用return语句,所以上面的例子可以简化为

(Integer o1, Integer o2) -> o2 - o1

如果Java可以推导出参数的类型,那么类型也是可以忽略的,在上面的例子中,lambda表达式将赋值给一个Integer比较器,所以编译器知道o1和o2必然是Integer,所以可以进一步简化为

(o1, o2) -> o2 - o1

要注意的是,如果lambda没有参数,小括号是必须的,就像下面这样

() -> { something to do }

无需指定lambda表达式的返回值,返回值的类型总是由上下文推导得出

函数式接口

lambda表达式有这么多好处,那么在什么地方来使用呢?答案是函数式接口,函数式接口(functional interface)就是有且仅有一个抽象方法,但可以有多个非抽象方法的接口

lambda表达式必须要借助函数式接口来初始化,单纯的lambda表达式没有任何意义,这里我们接着看到Arrays.sort方法,这个方法的第二个参数需要一个Comparator实例,而Comparator就是一个函数式接口,在源码中有@FunctionalInterface注解,所以我们可以使用如下形式来简化上面例子中的调用

Arrays.sort(arr, ((o1, o2) -> o2 - o1));

 这样的代码看起来非常简洁,在底层,Arrays.sort方法会接收一个实现了Comparator的某个类的对象。在这个对象上调用compare方法会执行这个lambda表达式的体。这些对象和类的管理完全取决于具体实现,与使用传统的方法,比如上面的静态内部类对比,这样可能高效很多

常用函数式接口

函数式接口参数返回值抽象方法描述其他方法
Runnablevoidrun运行一个无参无返回值的动作
Supplier<T>Tget提供一个T类型的值
Consumer<T>Tvoidaccpet处理T类型的值andThen
BiConsumer<T>T,Uvoidaccpet处理T和U类型的值andThen
Function<T,R>TRapplyT类型的函数compose,andThen,identity
BiFunction<T,U,R>T,URapplyT和U类型的函数andThen
UnaryOperator<T>TTapply类型T上的一元操作符compose,andThen,identity
BinaryOperator<T>T,TTapply类型T上的二元操作符andThen,maxBy,minBy
Predicate<T>Tbooleantest布尔值函数and,or,negate,isEqual
BiPredicate<T,U>T,Ubooleantest两个参数的布尔值函数and,or,negate