Java 8 新特性 - lambda表达式, stream

lambda 表达式

匿名函数,简化代码。
简化前:

1
2
3
4
5
6
7
8
9
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
for (String item : list) {
System.out.println(item);
}

使用 lambda 表达式简化后:

1
2
list.sort((a, b) -> b.compareTo(a));
list.forEach(System.out::println);

函数接口

定义:函数接口是只有一个抽象方法的接口,用于作为Lambda表达式的类型.

以下6个是 java 定义的6个常用函数接口

函数名 参数 返回类型 示例
Predicate T boolean 判断合格
Consumer T void 日志记录
Function T R 获取对象成员
Supplier None T 工厂方法
UnaryOperator T T 小写转大写
BinaryOperator (T,T) T 两数之和

Stream

  • Stream是Java8中重要的特性之一,它是一组对集合的操作方法,使得程序员得以站在更高的抽象层次上对集合进行操作
  • Stream中提供并行操作,使得对集合进行一些并行操作变得更加容易

示例

给字符串列表加前缀,并且转为数组,期间不能修改原来列表。

通常:

1
2
3
4
5
List<String> cityList = ....;
String[] cityArrays = new String[cityList.size()];
for (int i = 0; i < cityList.size(); i++) {
cityArrays[i] = pre + cityList.get(i);
}

使用Stream:

1
2
3
4
List<String> cityList = ....;
String[] cityArray = cityList.stream()
.map(city -> pre + city) // 惰性求值
.toArray(String[]::new); // 及早求值

使用Stream简化了代码,也使得业务流程更加清晰。

惰性求值是一个中间过程,它的返回值还是Stream。
及早求值是一个结束过程,它的返回值是相应的结果类型。
惰性求值一般表示Stream流还在继续,因此一条流中可以有多个惰性求值,而及早求值一般表示一条Stream流结束,因此一条流中最多只有一次及早求值。

注意:如果一条Stream调用链中,没有及早求值,该调用链实际是不执行的。

stream 方法

构建流对象的方式

  • Stream.of()

    1
    2
    3
    4
    5
    6
     public class StreamBuilders {
    public static void main(String[] args) {
    Stream<String> stream = Stream.of("a", "b", "c", "d", "e", "f", "g");
    stream.forEach(p -> System.out.println(p));
    }
    }
  • Collection.stream()

    1
    2
    3
    4
    5
    6
    7
    public class StreamBuilders {
    public static void main(String[] args) {
    List<String> strings = Arrays.asList("a", "b", "c", "d", "e", "f", "g");
    Stream<String> stream = strings.stream();
    stream.forEach(p -> System.out.println(p));
    }
    }
  • Stream.generate()

    1
    2
    3
    4
    5
    6
    7
    8
    public class StreamBuilders {
    public static void main(String[] args) {
    Stream<Double> stream = Stream.generate(() -> {
    return Math.random();
    }).limit(5);
    stream.forEach(p -> System.out.println(p));
    }
    }
  • 使用 iterate()

    1
    2
    3
    4
    5
    6
    public class StreamBuilders {
    public static void main(String[] args) {
    Stream<Integer> stream = Stream.iterate(10, i -> i + 10).limit(5);
    stream.forEach(i -> System.out.println(i));
    }
    }
  • 使用 CharSequence.chars()

    1
    2
    3
    4
    5
    6
    public class StreamBuilders {
    public static void main(String[] args) {
    IntStream stream = "ABCDEFG_abcdefg".chars();
    stream.forEach(p -> System.out.println(p));
    }
    }

转换流为集合

  • stream.collect(Collectors.toList()) 把 Stream 转换为 List

    1
    2
    3
    4
    5
    6
    7
    8
    public class StreamBuilders {
    public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Stream<Integer> stream = list.stream();
    List<Integer> oddNumbersList = stream.filter(i -> i % 2 != 0).collect(Collectors.toList());
    System.out.print(oddNumbersList);
    }
    }
  • stream.collect(Collectors.toSet()) 把 Stream 转换为 Set

    1
    2
    3
    4
    5
    6
    7
    8
    public class StreamBuilders {
    public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Stream<Integer> stream = list.stream();
    List<Integer> oddNumbersSet = stream.filter(i -> i % 2 != 0).collect(Collectors.toSet());
    System.out.print(oddNumbersSet);
    }
    }
  • stream.toArray(EntryType[]::new) 把 Stream 转换为数组

    1
    2
    3
    4
    5
    6
    7
    8
    public class StreamBuilders {
    public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Stream<Integer> stream = list.stream();
    Integer[] oddNumbersArray = stream.filter(i -> i % 2 != 0).toArray(Integer[]::new);
    System.out.print(oddNumbersArray);
    }
    }

核心的流操作

我们先构建一个字符串集合,下面的例子都会基于这个集合进行操作。

1
List<String> provinceNames = Arrays.asList("湖南", "湖北", "河南", "河北", "广东", "广西", "北京", "南京");

中间操作

中间操作返回流对象,所以你可以把多个中间操作串成一行。

  • filter()

    • filter 方法接受一个断言对象,用来过滤流中的元素。
      1
      2
      3
      4
      provinceNames.stream().filter((p) -> p.startsWith("湖")).forEach(p -> System.out.print(p + ", "));

      // 输出 :
      // 湖南, 湖北,
  • map()

    • 这个中间操作使用给定的函数把流中的每个元素转换为另一个对象。下面的例子把每个字符串转化为另一个字符串(末尾多了一个人字)。但是你也可以把每个对象转换为其它类型的对象。
      1
      2
      3
      4
      provinceNames.stream().map(p -> p + "人, ").forEach(p -> System.out.print(p));

      // 输出 :
      // 湖南人, 湖北人, 河南人, 河北人, 广东人, 广西人, 北京人, 南京人,
  • sorted()

    • sorted 会对流中的元素进行排序,默认按自然序进行排序,除非你指定一个自定义的比较器(Comparator) 。
      1
      2
      3
      4
      provinceNames.stream().sorted().map(p -> p + "人, ").forEach(p -> System.out.print(p));

      // 输出 :
      // 北京人, 南京人, 广东人, 广西人, 河北人, 河南人, 湖北人, 湖南人,
需要注意的是 sorted 仅仅对流中的元素进行排序,而不会影响后面的集合,集合中的元素排序保持不变。

末端操作

末端操作返回某一类型的结果,而不是流对象。

  • forEach()

    • 该方法帮助迭代流中的元素并在元素上执行一些操作。这些操作可以是 lambda 表达式或者方法引用。
      1
      provinceNames.stream().forEach(System.out::println);
  • collect()

    • collect() 方法用来从流中抽取元素然后保存到一个集合或者数组中。
      1
      2
      3
      4
      List<String> provinceNamesOrdered = provinceNames.stream().sorted().collect(Collectors.toList());
      System.out.print(provinceNamesOrdered);
      // 输出 :
      // [北京, 南京, 广东, 广西, 河北, 河南, 湖北, 湖南]
  • match()

    • 各种匹配操作用来检查流中的元素是否指定断言条件。所有的匹配操作都是末端操作,它们返回布尔结果。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      boolean matchedResult = provinceNames.stream().anyMatch((s) -> s.startsWith("湖"));
      System.out.println(matchedResult);

      matchedResult = provinceNames.stream().allMatch((s) -> s.startsWith("湖"));
      System.out.println(matchedResult);

      matchedResult = provinceNames.stream().noneMatch((s) -> s.startsWith("湖"));
      System.out.println(matchedResult);

      // 输出 :
      // true
      // false
      // false
  • count()

    • count 末端操作返回流中元素个数。
      1
      2
      3
      4
      long totalMatched = provinceNames.stream().count();
      System.out.println(totalMatched);

      // 输出 : 8
  • reduce()

    • 该末端操作使用给定的函数对流中的元素进行归约。它是这样一个过程:每次迭代,将上一次的迭代结果(第一次时为 identity 的元素,如没有 identity 则为流中的第一个元素)与下一个元素一同执行一个二元函数。在 reduce 操作中,identity 是可选的,如果使用,则作为第一次迭代的第一个元素使用。归约的结果保存在 Optional 中。
      1
      2
      3
      4
      5
      Optional<String> reduced = provinceNames.stream().reduce((s1, s2) -> s1 + "#" + s2);
      reduced.ifPresent(System.out::println);

      // 输出 :
      // 湖南#湖北#河南#河北#广东#广西#北京#南京

短路操作

流操作通常会在流中满足某一断言的所有元素上进行操作,有时我们希望在迭代过程中遇到匹配的元素时就终止操作,在外部迭代中,你需要写 if-else 代码块,在内部迭代中,有现成的方法可以使用,下面是两个这样的方法的示例:

  • anyMatch()

    • 该操作只要遇到满足断言条件的元素就会返回 true,在此之后就不再处理任何其它的元素了。
      1
      2
      3
      4
      boolean matched = provinceNames.stream().anyMatch((s) -> s.startsWith("河"));
      System.out.println(matched);

      // 输出 : true
  • findFirst()

    • 该操作会返回流中的第一个元素,然后就不再处理其它元素了。
      1
      2
      3
      4
      String firstMatchedName = provinceNames.stream().filter((s) -> s.startsWith("广")).findFirst().get();
      System.out.println(firstMatchedName);

      // 输出 : 广东

Optional 接口

Optional 被定义为一个简单的容器,其值可能是null或者不是null
在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null
在Java 8中,不推荐你返回null而是返回Optional

1
2
3
4
5
6
String temp = "temp";
Optional<String> stringOptional = Optional.ofNullable(temp);
// 不为空则执行
stringOptional.ifPresent(System.out::println);
// 不为空返回值,为空则返回指定的值
stringOptional.orElse("tempElse");

将电话号码列表转化为以逗号隔开的字符串,如果为空则抛出异常

1
2
3
String phoneNumber = subList.stream()
.reduce((a, b) -> a + "," + b)
.orElseThrow(Exception::new);