目录
函数接口
标准函数接口
自定义函数接口
函数接口的应用
Lambda表达式
Lambda表达式相比于匿名类的优点
方法引用
函数式接口是只包含了一个抽象方法的接口,这种接口的设计目的是为了支持Lambda表达式为代表的函数输入,在Java中有43个标准的默认函数接口。本文会首先介绍标准的函数接口,然后介绍Lambda表达式和方法引用的使用,最后会对比它与匿名类以及方法引用的优劣。
函数接口
标准函数接口
Java.util.Function中共有43个标准的函数接口,但是最核心的基础接口只有6个,其余接口都是基于这些基础接口扩展出来的。下面重点介绍这6个基础接口:
接口 | 函数签名 | 说明 | 范例方法 |
Function<T,R> | R apply(T) | 函数输入T类型变量,转换为R类型输出 | Arrays::asList |
UnaryOperator<T> | T apply(T) | 函数输入T类型变量,输出也是同类型变量 | String::toLowerCase |
BinaryOperator<T> | T apply(T t1, T t2) | 函数输入两个T类型变量,输出同类型变量 | BigInteger:add |
Predicate<T> | boolean test(T) | 函数输入T类型,输出布林值 | Collection::isEmpty |
Supplier<T> | T get() | 函数无输入,输出T类型 | Instant:now |
Consumer<T> | void accept(T) | 函数输入T类型,无输入类型 | System.out::println |
这6个基础接口各自有三种变体,分别可以作用于基本类型Int、long和double。他们的命名方式是在接口名称前面加上基本类型而得。比如IntPredicate的test方法要求输入类型为int。
另外,Function接口还有9种变体,用于结果类型为基本类型的情况。如果是基本类型转换为基本类型的情况命名格式为ScrToResult(LongToIntFunction),如果源类型为基础类型,结果类型为对象,命名格式为SrcToObj(DoubleToObjFunction)。
Function、Predicate和Consumer也有自己的二元参数版本(BiFunction<T, U, R>、BiPredicate(T, U)、BiConsumer(T, U)),然后Bifunction还有返回三个相关基本类型的变体(ToIntBiFunction、ToLongBiFunction、ToDoubleBiFunction),Consumer接口也有带两个参数的变体(一个对象引用、一个基本类型),ObjDoubleConsumer<T>、ObjIntConsumer<T>, ObjDoubleConsumer<T>。
最后还有一个Supplier接口的一种变体,BooleanSupplier接口。
自定义函数接口
除了以上标准的函数接口,程序员也可以通过@FunctionalInterface注释自行定义,如:
@FunctionalInterface
public interface CurrentTimePrinter {
void printCurrentTime();
}
可以看到,这个函数式接口的方法没有输入和输出,不属于标准函数接口的任何一种场景。文中作者强调了尽量通过标准函数接口来实现相关的方法,除非是以下几种特殊的情况才可以考虑自定义函数式接口:
- 确实没有符合要求的接口:这里有两种情况,一是输入和输出与标准接口不一致(如上面的例子),还有一种是需要抛出受检异常,这个功能并没有在标准接口方法中引入。
- 接口的名称能够起到功能提示的作用:拿Comparator<T>接口为例,它与ToIntFunction<T,T>接口在结构上是一致的,但是Java中还是单独增加了这个接口,他的名称能起到很好的提示作用。
- 需要有缺省的方法:Comparator<T>接口实现了大量的缺省方法,可以对比较器进行转换与合并。
- 有关联的严格契约:还是拿Comparator<T>接口为例,它对于如何构成一个有效的实例有严格的限制(比如需要实现compare方法),这是标准接口所没有的。
函数接口的应用
Lambda表达式
与其他语言一样,Java允许通过Lambda表达式创建函数接口实例,它有些类似于匿名类,但是更加的简洁:
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class Sample {
public static void main(String[] args) {
//Predicate接口实现
Predicate<String> whetherisEmpty = (str)->str.isEmpty();
System.out.println(whetherisEmpty.test(""));
//Predicate接口实现
Predicate<Integer> isEven = (num)->num%2==0;
System.out.println(isEven.test(4));
//Consumer接口,用于打印给定的字符串到控制台。
Consumer<String> printer = (str)-> System.out.println(str);
printer.accept("Hello world");
//定义一个Supplier接口,每次调用时返回一个新的随机字符串。
Supplier<String> randomwordSupplier = ()->{
String[] words ={"apple", "banana","orange","pear","grape"};
return words[(int)Math.random()*words.length];
};
//Function应用
Function<Person, String> personToString = (person)->{
return person.getName();
};
}
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {}
}
}
以上的案例都是独立创建函数式接口,然后调用接口的方法。当然也可以直接在以函数接口为参数的方法中直接引入Lambda表达式:
Collections.sort(words, (s1, s2)->compare(s1.length(), s2.length()));
这里编译器可以自动识别出Lambda的类型(Comparator<String>),s1和s2的类型(String),当然你也可以去注明这些类型,但是没有必要(除非Java的推断机制失效)。
Lambda表达式相比于匿名类的优点
匿名内部类是在Lambda表达式出现以前用来实现函数式接口嵌入的主要方法:
Collections.sort(words, new Comparator<String>(){
@Override
public int compare(String o1, String o2) {
return o1.length()-o2.length();
}
});
//注意接口可以通过匿名内部类的形式进行实例化,这在Java中是很常见的应用
相比于Lambda表达式,匿名类的实现有大量的样板代码,实现较为繁琐,但是如果实现的是非函数式接口(有多个方法),Lambda表达式就不起作用了。
方法引用
方法引用是比Lambda表达式更加简化的方法,相比于Lambda方法它进一步实现了参数的推断,因此使用的场景也更加有限(你要让程序可以推断出来)。比如书里的map.merge方法:
map.merge(key, 1, Integer::sum)
一般方法引用引用的是静态方法,但也有四种非静态方法的引用:
方法引用类型 | 范例 | Lambda表达式 | 说明 |
有限制非静态 | Instance.now()::isAfter | Instant then = Instance.now t->then.isAfter(t) | 有限制引用中,接收对象是在方法引用中临时指定的 |
无限制非静态 | String::toLowerCase | (str)->str.toLowerCase() | 无限制引用中,需要通过在该方法的声明函数前面额外添加一个参数来指定:names.stream().map(String::toLowerCase),这里names就是实例 |
类构造器 | Tree<K, V>::new | ()-> new Tree<K, V> | 普通的类构造器 |
数组构造器 | int[]::new | (len)-> new int[len] | 普通的数组构造器 |