Java8新特性(一):函数式接口与Lambda表达式

Lambda表达式

Java8中引入了Lambda表达式,Lambda表达式其实就是匿名函数。匿名函数就是没有方法名的函数。可以看下面一个例子,假如定义一个打印Hello,world的线程,之前可能这样写

1
2
3
4
5
6
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello,world");
}
});

通过匿名内部类的方式,将打印Hello,world告诉Thread,在Java8中,引入Lambda表达式大大简化了代码,可以直接将行为进行传递

1
Thread thread2 = new Thread(() -> System.out.println("Hello,world"));

Lambda表达式的定义

1
(int x, int y) -> x + y;

其中(int x, int y)是Lambda表达式的参数,->箭头后面的x+y是函数的主体部分。返回值为x+y。
在上面的线程打印例子中,

1
() -> System.out.println("Hello,world")

这段代码也是Lambda表达式,输入参数为空,返回类型也是void。

函数式接口

函数式接口(Functional Interface)就是指在那些只有一个抽象方法的接口(不考虑default方法)。可以使用@FunctionalInterface来标注一个接口使它成为函数式接口。比如Runnable接口,就是一个函数式接口,它恰好只有一个抽象方法run()。

Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。

现在定义一个函数式接口

1
2
3
4
@FunctionalInterface
public interface Addable {
int add(int x, int y);
}

定义一个静态方法

1
2
3
public static int plusTwoInts(Addable addable, int x, int y) {
return addable.add(x, y);
}

使用匿名内部类实现加法

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Addable addable1 = new Addable() {
@Override
public int add(int x, int y) {
return x + y;
}
};
//result1 = 1 + 5 = 6
int result1 = plusTwoInts(addable1, 1, 5);
}

使用Lambda表达式

1
2
3
Addable addable2 = (x, y) -> x + y;
//result2 = 5 + 6 = 11
int result2 = plusTwoInts(addable2, 5, 6);

相比匿名内部类简洁许多了,还可以更简单呢。

直接将函数式接口的具体实现作为参数传递

1
2
//result3 = 7 + 8 = 15
int result3 = plusTwoInts((x, y) -> x + y, 7, 8);

Java8 API中的函数式接口

接口 参数 返回类型 函数描述符 抽象方法 场景
Predicate T boolean T -> boolean boolean test(T t); 判断一条语句表达式的布尔值
Consumer T void T -> void void accept(T t); 执行接受参数的行为,不返回任何值
Function T R T -> R R apply(T t); 执行接受参数的行为并返回其他类型的结果R
Supplier None T () -> T T get(); 提供获取一个T类型
UnaryOperator T T T -> T 继承自Function 接受一个T类型的参数并返回相同类型的参数
BinaryOperator T, T T (T, T) -> T 继承自BiFunction 接受两个T类型的参数,并返回一个T类型的参数
BiPredicate L, R boolean (L, R) -> boolean boolean test(T t, U u); 接受两个参数,返回一个布尔值
BiConsumer T, U void (T, U) -> void void accept(T t, U u); 执行接受两个参数的行为,不返回任何值
BiFunction T, U R (T, U) -> R R apply(T t, U u); 执行接受两个参数的行为,并返回R类型的结果

Java8 API所提供的函数式接口在java.util.function包中,由于提供的函数式接口均是泛型接口,比如Predicate,Consumer,Function等,由于Java语言的局限性,泛型类型只能与引用类型绑定,而不能与基本类型(int,double,boolean……)绑定,因此使用泛型时,在引用类型与基本类型之间转换会有自动装箱的操作,这样会对性能产生一定的影响。而Java8在设计的时候已经考虑到了这一点,所以提供了一些额外的接口,在输入和输出的类型是基本类型时避免了自动装箱的操作。

举例来说,IntPredicate接口在接受int类型的参数输入时就不会去自动装箱,而使用Predicate接口接受int类型参数输入会产生额外的性能开销。类似的有ToDoubleFunction接口,这种是输出double基本类型时避免自动装箱,还有IntToDoubleFunction接口,在输入int类型输出double类型时避免了自动装箱。

类型检查与类型推断

在Java7中引入了菱形操作符,使得编译器可以自动推断出类型参数。

1
2
List<String> strList = new ArrayList<String>();
List<String> strList2 = new ArrayList<>();

Lambda表达式可以根据上下文推断出类型,比如
(int x, int y) -> x + y; 可以写成(x, y) -> x + y;

函数接口也会根据泛型参数类型进行判断。

1
Predicate<Integer> biggerThan10 = x -> x > 10;

方法引用

方法引用可以重复地使用现有的方法定义,而不用每次使用时重新去定义。

1
Printable printable = (String s) -> System.out.println(s);

这段代码等同于

1
Printable printable = System.out::println;

方法引用在以下三种情况时可以使用

  • 指向静态方法的方法引用
    比如String的静态方法valueOf()

  • 指向任意类型实例方法的方法引用
    比如String的实例length()

  • 指向现有对象的实例方法的方法引用
    比如已有对象student的实例方法getName()

------ 本文结束 ------

版权声明


BillyYccc's blog by Billy Yuan is licensed under a Creative Commons BY-NC-SA 4.0 International License.
本文原创于BillyYccc's Blog,转载请注明原作者及出处!