flyEn'blog

Java8新特性

Java8新特性

1.Lambda表达式与Functional接口

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

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

增加函数式接口的概念。即具有一个方法的普通接口。可以被隐式转换为lambda表达式。增加了一种特殊的注解明确声明接口作为函数式接口的意图:@FunctionalInterface,默认方法与静态方法并不影响函数式接口的契约,可以任意使用。

2.接口的默认方法与静态方法

默认方法:默认方法与抽象方法的不同之处在于抽象方法必须要求实现,默认方法则没有这个要求。(如果有需要也可以覆盖这个默认实现)

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";
}
}

在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream(),parallelStream(),forEach(),removeIf(),……

3.方法引用

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() );
}
}

构造器引用

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

4.Stream

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 Streams  {
private enum Status {
OPEN, CLOSED
};

private static final class Task {
private final Status status;
private final Integer points;

Task( final Status status, final Integer points ) {
this.status = status;
this.points = points;
}

public Integer getPoints() {
return points;
}

public Status getStatus() {
return status;
}

@Override
public String toString() {
return String.format( "[%s, %d]", status, points );
}
}
}
1
2
3
4
5
final Collection< Task > tasks = Arrays.asList(
new Task( Status.OPEN, 5 ),
new Task( Status.OPEN, 13 ),
new Task( Status.CLOSED, 8 )
);

所有状态为OPEN的任务一共有多少分数?

1
2
3
4
5
6
7
8
// Calculate total points of all active tasks using sum()
final long totalPointsOfOpenTasks = tasks
.stream()
.filter( task -> task.getStatus() == Status.OPEN )
.mapToInt( Task::getPoints )
.sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );

第一,task集合被转换化为其相应的stream表示。然后,filter操作过滤掉状态为CLOSED的task。下一步,mapToInt操作通过Task::getPoints这种方式调用每个task实例的getPoints方法把Task的stream转化为Integer的stream。最后,用sum函数把所有的分数加起来,得到最终的结果。

.stream操作被分成了中间操作与最终操作这两种。
中间操作返回一个新的stream对象。中间操作总是采用惰性求值方式,运行一个像filter这样的中间操作实际上没有进行任何过滤,相反它在遍历元素时会产生了一个新的stream对象,这个新的stream对象包含原始stream中符合给定谓词的所有元素。

像forEach、sum这样的最终操作可能直接遍历stream,产生一个结果或副作用。当最终操作执行结束之后,stream管道被认为已经被消耗了,没有可能再被使用了。在大多数情况下,最终操作都是采用及早求值方式,及早完成底层数据源的遍历。

stream另一个有价值的地方是能够原生支持并行处理。

1
2
3
4
5
6
7
8
// Calculate total points of all tasks
final double totalPoints = tasks
.stream()
.parallel()
.map( task -> task.getPoints() ) // or map( Task::getPoints )
.reduce( 0, Integer::sum );

System.out.println( "Total points (all tasks): " + totalPoints );

对集合中的元素进行分组。

1
2
3
4
5
6
7
// Group tasks by their status
final Map< Status, List< Task > > map = tasks
.stream()
.collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );

>>> {CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

计算整个集合中每个task分数(或权重)的平均值来结束task的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Calculate the weight of each tasks (as percent of total points) 
final Collection< String > result = tasks
.stream() // Stream< String >
.mapToInt( Task::getPoints ) // IntStream
.asLongStream() // LongStream
.mapToDouble( points -> points / totalPoints ) // DoubleStream
.boxed() // Stream< Double >
.mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
.mapToObj( percentage -> percentage + "%" ) // Stream< String>
.collect( Collectors.toList() ); // List< String >

System.out.println( result );


>>> [19%, 50%, 30%]

[API]

生成流

steam():为集合创建串行流。
parallelStream():危机和创建并行流。

1
2
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

forEach

迭代流中的每个数据。

1
2
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

map

map 方法用于映射每个元素到对应的结果。

1
2
3
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

filter

filter 方法用于通过设置的条件过滤出元素。

1
2
3
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
int count = strings.stream().filter(string -> string.isEmpty()).count();

limit

limit 方法用于获取指定数量的流。

1
2
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

sorted

sorted 方法用于对流进行排序。

1
2
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);

并行(parallel)程序

parallelStream 是流并行处理程序的代替方法。

1
2
3
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
int count = strings.parallelStream().filter(string -> string.isEmpty()).count();

Collectors

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

1
2
3
4
5
6
ist<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);

统计

1
2
3
4
5
6
7
8
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);

IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();

System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());

流的操作类型分为两种:

  • Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
  • Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

构造流的几种常见方法

1
2
3
4
5
6
7
8
9
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();

需要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream:
IntStream、LongStream、DoubleStream。当然我们也可以用 Stream、Stream >、Stream,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。

流转换为其他数据类型

1
2
3
4
5
6
7
8
9
// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();

流的操作

1
2
3
4
5
6
7
8
9
10
接下来,当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。

Intermediate:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

Terminal:
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

Short-circuiting:
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/index.html

Optional

可以优雅的解决NullPointException的问题。

1
2
3
4
5
6
Optional<User> user = ……
if (user.isPresent()) {
return user.getOrders();
} else {
return Collections.emptyList();
}

Optional的三种构造方式:
Optional.of(obj)、Optional.ofNullable(obj)和明确的Optional.empty()

假定我们有一个实例 Optional user, 下面是几个普遍的, 应避免 if(user.isPresent()) { … } else { … } 几中应用方式。

存在即返回,无则提供默认值

1
return user.orElse(null);  //而不是 return user.isPresent() ? user.get() : null;

存在即返回,无则由函数来产生

1
return user.orElseGet(() -> fetchAUserFromDatabase()); //而不要 return user.isPresent() ? user: fetchAUserFromDatabase();

存在才对它做点什么

1
2
3
4
5
6
user.ifPresent(System.out::println);

//而不要下边那样
if (user.isPresent()) {
System.out.println(user.get());
}

map函数

1
2
3
4
5
6
7
8
return user.map(u -> u.getOrders()).orElse(Collections.emptyList())

//上面避免了我们类似 Java 8 之前的做法
if(user.isPresent()) {
return user.get().getOrders();
} else {
return Collections.emptyList();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
return user.map(u -> u.getUsername())
.map(name -> name.toUpperCase())
.orElse(null);

//以前
User user = .....
if(user != null) {
String name = user.getUsername();
if(name != null) {
return name.toUpperCase();
} else {
return null;
}
} else {
return null;
}

针对这方面 Groovy 提供了一种安全的属性/方法访问操作符 ?.

1
user?.getUsername()?.toUpperCase();

其他几个, filter() 把不符合条件的值变为 empty(), flatMap() 总是与 map() 方法成对的, orElseThrow() 在有值时直接返回, 无值时抛出想要的异常.

使用 Optional 时尽量不直接调用 Optional.get() 方法, Optional.isPresent() 更应该被视为一个私有方法, 应依赖于其他像 Optional.orElse(), Optional.orElseGet(), Optional.map() 等这样的方法.

函数式接口

1
2
3
4
5
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}
1
GreetingService greetService1 = message -> System.out.println("Hello " + message);

提醒:加不加 @FunctionalInterface 对于接口是不是函数式接口没有影响,该注解只是提醒编译器去检查该接口是否仅包含一个抽象方法

eval 函数可以写为如下格式:

1
2
3
private static void eval(List<Integer> list, Predicate<Integer> predicate) {
list.stream().filter(predicate).forEach(System.out::println);
}

或者直接可以不用定义 eval 函数,使用:

1
list.stream().filter(n -> n > 3).forEach(System.out::println);
Fork me on GitHub