Loading... ## 接口与抽象类 在学习Java语言的过程中,你可能会产生疑问,为什么Java要在已有抽象类的情况下,再引入了接口(interface)的概念呢?举一个例子,有一个名为Comparable的接口用于比较,如果将其设计为一个抽象类,可以使用如下语法 ```java abstract class Comparable { public abstract int compareTo(Object o); } ``` 这样一来,只要其他类想要使用compareTo方法,只需要extends这个类即可 ```java 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方法时才会被调用 ```java 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是一种强类型语言,我们还要加上类型 ```java (Integer o1, Integer o2) -> { return o2 - o1; } ``` 这就是你看到的第一个lambda表达式,lambda表达式就是一个代码块。为什么取lambda这个名字呢?很多年前,有个数学家Alonzo Church想要形式化的表示能有效计算的数学函数。对于有些不知道该如何计算的函数,他使用了希腊字母lambda(λ)来标记参数。从那以后,带参数变量的表达式就被称为lambda表达式 lambda表达式的一种简单形式为:参数,箭头(->)以及表达式,特别的,对于可以用一个表达式完成的,甚至可以去掉花括号,并且隐式使用return语句,所以上面的例子可以简化为 ```java (Integer o1, Integer o2) -> o2 - o1 ``` 如果Java可以推导出参数的类型,那么类型也是可以忽略的,在上面的例子中,lambda表达式将赋值给一个Integer比较器,所以编译器知道o1和o2必然是Integer,所以可以进一步简化为 ```java (o1, o2) -> o2 - o1 ``` 要注意的是,如果lambda没有参数,小括号是必须的,就像下面这样 ```java () -> { something to do } ``` 无需指定lambda表达式的返回值,返回值的类型总是由上下文推导得出 ### 函数式接口 lambda表达式有这么多好处,那么在什么地方来使用呢?答案是函数式接口,函数式接口(functional interface)就是有且仅有一个抽象方法,但可以有多个非抽象方法的接口 lambda表达式必须要借助函数式接口来初始化,单纯的lambda表达式没有任何意义,这里我们接着看到Arrays.sort方法,这个方法的第二个参数需要一个Comparator实例,而Comparator就是一个函数式接口,在源码中有@FunctionalInterface注解,所以我们可以使用如下形式来简化上面例子中的调用 ```java Arrays.sort(arr, ((o1, o2) -> o2 - o1)); ``` 这样的代码看起来非常简洁,在底层,Arrays.sort方法会接收一个实现了Comparator<Integer>的某个类的对象。在这个对象上调用compare方法会执行这个lambda表达式的体。这些对象和类的管理完全取决于具体实现,与使用传统的方法,比如上面的静态内部类对比,这样可能高效很多 常用函数式接口 | 函数式接口 | 参数 | 返回值 | 抽象方法 | 描述 | 其他方法 | | :---------------------: | :--: | :-----: | :------------------------: | :------------------: | :----------------------: | | Runnable | void | run | 运行一个无参无返回值的动作 | | | | Supplier<T> | T | get | 提供一个T类型的值 | | | | Consumer<T> | T | void | accpet | 处理T类型的值 | andThen | | BiConsumer<T> | T,U | void | accpet | 处理T和U类型的值 | andThen | | Function<T,R> | T | R | apply | T类型的函数 | compose,andThen,identity | | BiFunction<T,U,R> | T,U | R | apply | T和U类型的函数 | andThen | | UnaryOperator<T> | T | T | apply | 类型T上的一元操作符 | compose,andThen,identity | | BinaryOperator<T> | T,T | T | apply | 类型T上的二元操作符 | andThen,maxBy,minBy | | Predicate<T> | T | boolean | test | 布尔值函数 | and,or,negate,isEqual | | BiPredicate<T,U> | T,U | boolean | test | 两个参数的布尔值函数 | and,or,negate | © 允许规范转载 打赏 赞赏作者 微信 赞