Java8新特性

Posted by Clear Blog on August 12, 2016

我觉得java8最大的卖点就是在纯java语言环境中支持函数式编程。 首先解释下什么是函数式编程,函数式编程一个特点就是, 函数本身可以作为一个参数传入另外一个函数,还允许返回一个函数。 最纯净,无副作用的函数式编程是不支持变量的,只要输入是确定的,输出就一定是确定的。

简单的说下我们常用的java8的新特性

lambda表达式和functional接口

lambda表达式(也称为闭包,闭包的概念一句话说白了,内部函数引用了外部函数的变量,然后返回内层函数)。 直接看代码

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

编译器会去自动的判断e的数据类型。

Streams

对集合(Collection)对象功能的增强, 它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation), 或者大批量数据操作 (bulk data operation)。 Stream 不是集合元素,它不是数据结构并不保存数据, 它是有关算法和计算的,它更像一个高级版本的 Iterator, 单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。 和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。 简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果, 或者导致一个副作用(side effect)。

1
2
3
4
5
6
7
List<String> wordList = Lists.newArrayList();
wordList.add("q23");
wordList.add("asD");
List<String> output = wordList.stream().
        map(String::toUpperCase).
        collect(Collectors.toList());
output.forEach(s -> System.out.println(s));

Optional

作为一个容器,它可能含有某值,或者不包含。使用它的目的是尽可能避免 NullPointerException。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
String strA = " abcd ", strB = null;
print(strA);
print("");
print(strB);
getLength(strA);
getLength("");
getLength(strB);
public static void print(String text) {
 // Java 8
 Optional.ofNullable(text).ifPresent(System.out::println);
 // Pre-Java 8
 if (text != null) {
 System.out.println(text);
 }
 }
public static int getLength(String text) {
 // Java 8
return Optional.ofNullable(text).map(String::length).orElse(-1);
 // Pre-Java 8
// return if (text != null) ? text.length() : -1;
 };
forEach

forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了, 你无法对一个 Stream 进行两次 terminal 运算。下面的代码是错误的:

1
2
stream.forEach(element -> doOneThing(element));
stream.forEach(element -> doAnotherThing(element));
map/flatMap

它的作用就是把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素。

1
2
3
4
5
6
7
8
9
//把所有的单词转换为大写
List<String> output = wordList.stream().
map(String::toUpperCase).
collect(Collectors.toList());
//整数 list 的平方数 {1, 4, 9, 16}
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().
map(n -> n * n).
collect(Collectors.toList());
filter

filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。

1
2
3
4
//偶数
Integer[] sixNums = {1, 2, 3, 4, 5, 6};
Integer[] evens =
Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
reduce

把 Stream 元素组合起来。 它提供一个起始值(种子),然后依照运算规则(BinaryOperator), 和前面 Stream 的第一个、第二个、第 n 个元素组合。 从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。

1
2
3
4
// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
 filter(x -> x.compareTo("Z") > 0).
 reduce("", String::concat);
limit/skip

limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素(它是由一个叫 subStream 的方法改名而来)。

sorted

对 Stream 的排序通过 sorted 进行, 它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后, 再排序,这能帮助程序明显缩短执行时间。

Match

Stream 有三个 match 方法,从语义上说: 1.allMatch:Stream 中全部元素符合传入的 predicate,返回 true 2.anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true 3.noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true

Stream.generate

通过实现 Supplier 接口,你可以自己来控制流的生成。

1
2
3
4
5
6
7
// 生成 10 个随机整数
Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
//Another way
IntStream.generate(() -> (int) (System.nanoTime() % 100)).
limit(10).forEach(System.out::println);

Stream 的特性归纳

  • 不是数据结构
  • 它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。
  • 它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream, 而不是从 source 删除那些元素。
  • 所有 Stream 的操作必须以 lambda 表达式为参数
  • 不支持索引访问
  • 很容易生成数组或者 List
  • 惰性化
  • 很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。
  • 并行能力
  • 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
  • 可以是无限的,集合有固定大小,Stream 则不必。 limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。