聊聊Java 8的Lambda表达式和函数式接口
Java 8作为Java的一个里程碑版本,提供了很多特性,本文主要介绍Java 8的Lambda表达式及函数式接口的内容。
Lambda表达式
Java引入Lambda表达式是为了简化代码,使代码更加简洁优雅。因此,一般使用Lambda表达式的代码也是比较简短的。那么Lambda表达式的代码可以写的多简洁呢?我们看看下面的几个例子。
public void fun1() {
Thread t = new Thread(() -> System.out.println("Hello"));
t.start();
}
public void fun2() {
Integer[] arr = {1, 4, 3, 8, 6, 9, 2, 7, 0, 5};
Arrays.sort(arr, (x, y) -> x - y);
System.out.println(Arrays.toString(arr));
}
上面的代码是两个经典的Lambda表达式的使用方法,分别选取了线程创建和排序的代码。如果不用Lambda表达式,很多人使用匿名内部类完成会多很多行冗余代码,但是像上面这样() -> {}
的形式就简洁多了。
下面介绍一下Lambda表达式的几种常见的形式。
//没有参数,没有返回值
() -> System.out.println("hello")
//没有参数,返回值是10
() -> 10
//有一个参数
x -> x
//有两个参数,编译器自动判断参数类型
(x, y) -> x + y
//有两个int型参数
(int x, int y) -> x + y
//有多条语句,需要加上大括号
(x, y) -> {int sum = x + y; System.out.println(sum);}
根据上面的这些例子我们可以总结一下Lambda的特性:
-
可以不加参数类型,编译器会自动识别。
-
只有一个参数小括号加不加都可以,但如果不止一个参数就一定需要小括号。
-
如果主体代码只有一条语句,大括号加不加都可以,若超过一条语句则必须加上大括号。
函数式接口
说完了Lambda表达式,我们来讲讲Lambda表达式的实现基础——函数式接口。函数式接口首先肯定是一个接口,这个接口有且仅有一个抽象方法,但是可以有多个非抽象方法。
我们可以看一下Runnable接口的定义,@FunctionalInterface
修饰的是函数式接口,这里@FunctionalInterface
不是必须的,只是方便编译器进行识别。Runnable接口只有一个run()
方法,因此Runnable接口是一个非常典型的函数式接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
看到这里,会有细心的朋友发现一个奇怪的问题,为啥同样是函数式接口,Comparator接口的抽象方法却没有abstract
修饰,并且有两个方法都没有方法体。这里需要引入一个知识点,接口除了default
修饰的默认方法,其他都是默认由public abstract
修饰的抽象方法,因此其实接口里面的方法其实是省略这些修饰的。而由于boolean equals(Object obj)
这个方法是Object类的一个方法,接口默认继承Object类,所以这个方法是不算入接口的抽象方法计数的。
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
...
}
下面我们来看看Java 8提供的几个常见的函数式接口。
Supplier接口
@FunctionalInterface
public interface Supplier<T> {
T get();
}
先从最简单的Supplier接口说起,这个接口只有一个get()
方法,该方法没有参数,但是有返回值。下面是测试的demo,代码会打印test supplier
的字符串。
public void testSupplier() {
Supplier<String> supplier = () -> "test supplier";
System.out.println(supplier.get());
}
Consumer接口
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
Consumer接口和Supplier接口的思路完全不同,我们可以理解为Supplier是数据的提供者,而Consumer是数据消费者,需要我们提供数据由Consumer消费,另外Consumer还有一个addThen
方法可以控制消费者的执行顺序。
public void testConsumer() {
Consumer<String> name = n -> System.out.println(
"get name: " + n.split(",")[0]);
Consumer<String> phone = p -> System.out.println(
"get phone number: " + p.split(",")[1]);
name.andThen(phone).accept("zhangsan,13912345678");
}
代码会打印下面的字符串。
get name: zhangsan
get phone number: 13912345678
Predicate接口
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
Predicate是一个断言型接口,接受一个输入参数,返回一个布尔值。我们尝试实现一个demo,有两个Predicate对象,只要满足其中一个条件就返回true,代码最后会打印true。
public void testPredicate() {
Predicate<String> s1 = s -> "predicate".equals(s);
Predicate<String> s2 = s -> "consumer".equals(s);
System.out.println(s1.or(s2).test("predicate"));
}
Function接口
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
最后一个是函数型接口,接受参数且有返回值。下面演示一个复合函数的demo,f1的作用是求数字的2倍,f2的作用是拼接字符串和数字,最后会打印the result is 10
。
public void testFunction() {
Function<Integer, Integer> f1 = t -> 2 * t;
Function<Integer, String> f2 = t -> "the result is " + t;
System.out.println(f2.compose(f1).apply(5));
}