java8新特性

lambda 表达式

在 Java 中一个方法的参数只能是一个类或者是基本类型,Java 不支持将函数作为方法的参数,也不支持一个方法的返回值是一个函数。在 JavaScript 中可以有一下的代码:

1
2
3
function addOne(addNum){
addNum();
}

在 Java 8 中新添加的 lambda 表达式中,允许 Java 进行函数式编程。在类似 JavaScript 中,lambda 表达式的类型是函数,但是在 Java 中,lambda 表达式是对象,必须依附于一类特别的对象类型 函数式接口(Functional Interface)

函数式接口

Java 8 中新增了一个 Functional Interface 注解,对于一个函数式接口有着一下特性:

  • 如果一个接口有且仅有一个抽象方法,那么这个接口可以称作是函数式接口。
  • 在一个接口上声明 @Functional Interface 注解,那么编译器就会按照函数式接口来要求这个接口。
  • 一个接口只有一个抽象方法,并且没有声明 @Functional Interface 注解,编译器会默认这个接口是函数式接口。

对于一个函数式接口有着三种实例化的方式,lambda 表达式,方法引用,构造器引用。

1
2
Note that instances of functional interfaces can be created with
lambda expressions, method references, or constructor references

另外对于函数式接口, Java 8 中允许一个接口中有实例方法。如果一个接口中继承了一个 Object 类中的方法,那么这个方法不会计算在仅有的抽象方法中。

1
2
3
If an interface declares an abstract method overriding one of the
public methods of {@code java.lang.Object}, that also does
<em>not</em> count toward the interface's abstract method count

比如,下面的接口虽然有着两个抽象方法,但是其中一个 toString 方法重写自 Object 类,所以这个接口也是符合函数式接口的:

1
2
3
4
5
6
@FunctionalInterface
public interface MyInterface {
void addOne();

String toString();
}

常用的函数式接口:Consumer,Fuction

Consumer 接口

接受一个参数,不返回结果,抽象方法 void accept(T t);

Function 接口

接受一个参数,返回一个结果,抽象方法 R apply(T t);

有两个默认方法,compose,addThen

compose 方法接收一个 Function 的参数,返回一个 Function。返回的 Function 的 apply 方法的参数是接收 Function 执行 apply 方法后的结果。用于在当前 Function 之前执行一个 Function

addThen 方法接受一个 Function 参数,返回一个 Function。返回的 Function 的 apply 方法参数是原本 Function 执行 apply 方法后的结果。用于在当前 Function 之前执行一个 Function

BiFucntion 接口

接受两个参数,返回一个结果,抽象方法 R apply(T t, U u);

有一个默认方法,addThen(Function<? super R, > extends V> after); 返回值是 BiFunction

Predicate 接口

接收一个参数,返回一个 boolean 类型的结果,抽象方法 boolean test(T t);

lambda 表达式入门

一个 lambda 表达式的大致结构

1
(arguments) -> {body}

在左边的括号中填写参数,并以 -> 作为分割,右边实现具体的操作。

  • 一个 Lambda 表达式可以有零个或多个参数
  • 参数的类型既可以明确声明,也可以根据上下文来推断。列如 (int a ) 与 (a) 效果相同。
  • 所有的参数需要包含在箭头的左边圆括号内,参数与参数之间用逗号相隔。
  • 空圆括号代表参数为空,不可省略

使用 Consumer 接口,传入一个参数,不返回结果

本质上 lambda 是对于匿名内部类的简写,比如对于一个 List 的数据,实现循环通常可以有两中方式,一种是使用普通的 for 循环,另一种是使用增强的 for 循环。Java 8 中对于 List 类型,新增了一个 forEach 方法,方法的参数是需要传入一个 Consumer 接口的实例对象,可以使用匿名内部类来实现:

1
2
3
4
5
6
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer){
System.out.println(integer);
}
});

虽然这样可以实现遍历 list,但是似乎相比于一般的 for 循环更为复杂了。而在 Idea 中也会标记这个方法是可以使用 lambda 表达式来简化。点开 Consumer 接口,可以发现,这个接口是一个函数式接口。

1
list.forEach(integer -> System.out.println(integer));

这样通过 lambda 表达式极大的简化了代码,也提高了可读性。

使用 Function 接口,传入一个参数,返回一个结果

Map 接口中定义了 computeIfAbsent() 方法,用于存储,Map 中不存在传入的键时,对应的值。

使用 Function 接口,在 map 中不存在键为 “test” 的时候,在 Map 中存储一个数据。

1
2
3
4
5
6
7
Map<String,Integer> map = new HashMap<>();
int i = map.computeIfAbsent("test", new Function<String,Integer>(){
@Override
public String apply(String s){
return s.length();
}
});

使用 lambda 表达式

1
2
Map<String,Integer> map = new HashMap<>();
int i = map.computeIfAbsent("test", s -> s,length());

在 lambda 表达式出现以前,java 中都是值传递,对于一个方法,预先定义行为,调用的时候传入值。

1
2
3
4
5
public int addOne(int v){
return v + 1;
}

int res = addOne(1);

而 lambda 表达式的出现,允许 Java 进行行为传递。对于一个方法,将行为作为 ’参数‘ 传递进去。

对于上面加一的函数,lambda 表达的方法

1
2
3
4
5
public int add(int v, Function<Integer,Integer> function){
return function.apply(v);
}

int res = add(1, v -> v +1);

这样通过一个方法,可以实现多个不同的操作,只需要在调用时传入对应的操作。提升抽象层次,API 重用性更好,更加的灵活。