Java 8新特性之Java语言新特性

Lambda表达式和函数式接口

Lambda表达式(也称为闭包),它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。Java8以前,java开发者只能使用匿名类来代替Lambda表达式。

官方文档

匿名类和Lambda表达式

相比于匿名类,Lambda表达式更加的简洁。

代码如下:

Person类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Person {

public enum Sex {
MALE, FMALE
}

String name;
LocalDate birthday;
Sex gender;
String emailAddress;

public Person(String name) {
this.name=name;
this.gender=Sex.MALE;
this.emailAddress="emailAddress";
this.birthday=LocalDate.now();
}

public int getAge() {
// ...
return 0;
}

public void printPerson() {
//...
System.out.println("yes");
}
}

LambdaExpressions类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class LambdaExpressions {

public static void printPersons(List<Person> roster, CheckPerson
tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}

public static void main(String[] args) {

//自己添加的测试用例
LinkedList<Person> roster = new LinkedList<>();
Person person=new Person("max");
roster.add(person);

// 匿名类
printPersons(roster, new CheckPerson() {

@Override
public boolean test(Person p) {

return p.gender == Person.Sex.MALE && p.getAge() >= 0
&& p.getAge() <= 25;
}
});

// Lambda表达式
printPersons(roster, p -> p.gender == Person.Sex.MALE
&& p.getAge() >= 0 && p.getAge() <= 25);
}
}

// 函数接口
@FunctionalInterface
interface CheckPerson {
boolean test(Person p);
}

Lambda表达式语法

组成
Lambda表达式可由逗号分隔的参数列表、->符号和主体(单个语句或语句块)组成,例如:

1
2
3
p -> p.gender == Person.Sex.MALE 
&& p.getAge() >= 18
&& p.getAge() <= 25

在上面的代码中的参数p的类型由编译器推理得出的,你也可以显式指定该参数的类型,例如:

1
2
3
(Person p) -> p.gender == Person.Sex.MALE 
&& p.getAge() >= 18
&& p.getAge() <= 25

如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:

1
2
3
4
Arrays.asList("a","b","c").forEach(e->{
System.out.println(e);
System.out.println(e);
});

Lambda表达式有返回值,返回值的类型也有编译器推理得出。如果Lambda表达式中的语句只有一行,这可以不用使用return语句,return语句不是表达式; 在lambda表达式中,您必须用大括号({})括起来,下面两个代码片段效果相同:

1
Arrays.asList("a","b","c").sort((e1,e2)->e1.compareTo(e2));

1
2
3
4
5
6
7
8
9
10
11
Arrays.asList("a","b","c").sort((e1,e2)->{

return e1.compareTo(e2);
});
```

Lambda表达式可以应用类成员和局部变量(会将这些变量隐式的转换成final),在上面的匿名类和Lambda表达式中也用到了类成员。
```java
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );

Lambda的设计者们为了更好的兼容现有的功能,提出了函数式接口。

函数接口是指只有一个函数的接口,这样的接口可以隐式的转换为Lambda表达式。java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子。

在实践中函数接口非常的脆弱,只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义(上面也用到了函数接口):

1
2
3
4
@FunctionalInterface
interface CheckPerson {
boolean test(Person p);
}

注意:

默认方法静态方法是不会破坏函数接口的定义,下面代码是合法的:

1
2
3
4
5
6
7
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();

default void defaultMethod() {
}
}

接口的默认方法和静态方法

官方文档

Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。

默认方法:默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。

默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private interface Defaulable {
// Interfaces now allow default methods, the implementer may or
// may not implement (override) them.
default String notRequired() {
return "Default implementation";
}
}

private static class DefaultableImpl implements Defaulable {
}

private static class OverridableImpl implements Defaulable {
@Override
public String notRequired() {
return "Overridden implementation";
}
}

Defaulable接口使用关键字default定义了一个默认方法notRequired()。
DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法。
OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。

静态方法:接口可以定义静态方法。
代码如下:

1
2
3
4
5
6
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}

下面代码片段整合了默认方法和静态方法的使用场景:

1
2
3
4
5
6
7
public static void main( String[] args ) {
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
System.out.println( defaulable.notRequired() );

defaulable = DefaulableFactory.create( OverridableImpl::new );
System.out.println( defaulable.notRequired() );
}

这段代码的输出结果如下:

1
2
Default implementation
Overridden implementation

默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新的方法,如stream(),parallelStream(),forEach(),removeIf()等等。

方法引用

官方文档