lambda 表达式

java8的最大变化就是引入了lambda表达式,一个紧凑的传递行为的方式。

最简单的Lambda表达式可以用逗号分隔的参数列表->符号和功能语句块来表示。->前面的是参数,->后面的处理的流程。

示例如下:

1
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

注意到编译器会根据上下文来推测参数的类型,或者也可以显示地指定参数类型,只需要将类型包在括号里。举个例子:

1
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

如果Lambda的功能语句块太复杂,我们可以用大括号包起来,跟普通的Java方法一样,如下:

1
2
3
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );

Lambda表达式可能会引用类的成员或者局部变量(会被隐式地转变成final类型),下面两种写法的效果是一样的:

1
2
3
4
5
6
7
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );

final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );

Lambda表达式可能会有返回值,编译器会根据上下文推断返回值的类型。如果lambda的语句块只有一行,不需要return关键字。下面两个写法是等价的:

1
2
3
4
5
6
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );

为了让现有功能和lambda 表达式友好兼容,有了函数接口的概念,函数接口是一种只有一个方法的接口。函数接口可以隐式的转换成lambda表达式。

如下定义了一个函数式接口

1
2
3
4
5
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}

那么就可以使用Lambda表达式来表示该接口的一个实现.

1
GreetingService greetService1 = message -> System.out.println("Hello " + message);

方法引用

方法引用提供了一个很有用的语义来直接访问类或者实例的已经存在的方法或者构造方法。结合Lambda表达式,方法引用使语法结构紧凑简明。不需要复杂的引用。

下面以Car 类做示例说明支持的4种方法引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static class Car {
public static Car create( final Supplier< Car > supplier ) {
return supplier.get();
}

public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}

public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}

public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}

第一种方法引用是构造方法引用,语法是:Class::new

对于泛型来说语法是:Class<T >::new,注意构造方法没有参数。

1
2
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );

第二种方法引用是静态方法引用,语法是:Class::static_method注意这个静态方法只支持一个类型为Car的参数。

1
cars.forEach( Car::collide );

第三种方法引用是类实例的方法引用,语法是:Class::method注意方法没有参数。

1
cars.forEach( Car::repair );

最后一种方法引用是引用特殊类的方法,语法是:instance::method,注意只接受Car类型的一个参数。

1
2
final Car police = Car.create( Car::new );
cars.forEach( police::follow );

Optional

Optional 是核心类库新设计的一个数据类型,用来替换null值。人们常用null值来表示值不存在,Optional对象可以更好的表达这个概念,使用null表示值最大的问题在于NullPointerException。一旦引用一个存储null值的变量,程序会立即崩溃。

Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。

使用工厂方法of,可以从某个值创建出一个Optional对象,Optional对象相当于值的容器,该值可以通过get方法提取。

1
Optional<String> a = Optional.of("a");

Optional 对象也可能为空,因此还有一个对应的工厂方法empty(创建一个空值在Optional中)。另外一个工厂方法ofNullable则可以将一个空值转换为Optional对象。还有一个方法isPresent 表示一个Optional对象里是否有值。

1
2
3
Optinoal emptyOptional = Optional.empty();
Optional alsoEmpty = Optional.ofNullable(null);
Boolean b = a.isPresent();

T orElse(T other) 方法表示如果存在值,则返回值,否则返回other。

1
Integer value1 = a.orElse(new Integer(0));

Stream

Stream API引入了函数式编程。Stream 是用函数式编程方式在集合类上进行复杂操作的工具。首先注意stream()方法的调用,返回了内部迭代中的相应接口:Stream。

例如要计算来自北京的艺术家人数

1
long count = allArtists.steeam().filter(artist->artist.isForm("Beijing").count());

上面分为两步,分别是1找出所有来自北京的艺术家2计算人数。每个操作都对应stream接口的一个方法。为了找出来自北京的,需要对stream对象进行过滤:使用了filter。count()方法计算给定Stream里包含多少个对象。

Stream里的一些方法虽然是普通的Java方法,但返回的Stream对象却不是一个新集合。而是创建新集合的配方。

1
allArtists.stream().filter(artist->artist.isFrom("Beijing") );//只过滤不计数

这行代码并没有做什么实际性的工作,filter只刻画了Stream,但是没有产生新的集合,像filter这样只描述Stream最终不产生新集合的方法叫做惰性求值方法,而像count这样最终会从Stream产生值的方法叫作及早求值方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
流的特点:
 元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元
素(如`ArrayList` 与 `LinkedList`)。但流的目的在于表达计算,比如 `filter`、`sorted`和`map`。集合讲的是数据,流讲的是计算。
 源——流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
 数据处理操作——流的数据处理功能支持类似于数据库的操作,如`filter`、`map`、`reduce`、`find`、`match`、`sort`等。流操作可以顺序执行,也可并行执行。
 流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。流水线的操作可以看作对数据源进行数据库式查询。
 内部迭代——与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

流与集合的区别:
1、集合是一个内存中的数据结构,它包含数据结构中目前所有的值—— 集合中的每个元素都得先算出来才能添加到集合中。
2、流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计算的。


--------------------------------------------
流的操作:
1、中间操作:中间操作会返回另一个流,比如`filter、map、sort、distinct`等操作
2、终端操作: 终端操作会从流的流水线生成结果,比如`forEach、count、collect`
--------------------------------------------

在过滤器中加入一条print语句,来输出艺术家的名字,就能看出不同。

1
allArtiest.stream().filter(artiest->{System.out.println(artiest.getName());}) // 由于使用了惰性求值,没有输出艺术家的名字。

如果将同样的输出语句加入一个拥有终止操作的流,如一个计数操作,艺术家的名字就会被输出。

常用的流操作主要有:

collect(toList()) 方法由Stream里的值生成一个列表,是一个及早求值操作。

Stream的of方法使用一组初始值生成新的Stream。

1
List<String> collected =Stream.of("a","b","c").collect(Collectors.toList());

上面是使用collect(toList())方法从Stream中生成一个列表。由于很多Stream操作都是惰性操作,因此调用Stream上一系列方法后,还需要最后再调用一个类似collect的及早求值方法。

map 函数可以将一种类型的值转换成另外一种新的类型。

filter用来遍历元素并检查其中的元素。

flatmap 可以用Stream替换值,然后将多个Stream连接成一个Stream.