JAVA8中Lambda和Stream

Java8于2014年3月份发布,其主要更新的特性有:函数式接口、Lambda 表达式、集合的流式操作、注解的更新、安全性的增强、IO\NIO 的改进、完善的全球化功能等,本文将介绍Lambda表达式与集合流试操作。

函数式接口

Java8引入的一个重要的思想就是函数式编程。 提到函数式,必须提到函数式接口。在Java8中对函数式接口的注解是@FunctionalInterface,任何一个只有一个抽象方法的接口都默认为是函数式接口,@FunctionalInterface注解是非必须的。如果在一个接口上加上该注解,那么这个接口就只能有一个抽象方法。如下

@FunctionalInterface
public interface TestInterface { void test(); }

也可以省略注解

public interface TestInterface { void test(); }

几种常见的函数式接口比如说Function<T, R> 支持传入一个参数T并且返回R;
Predicate<T> 传入一个参数T返回一个boolean类型等等。

接口参数返回类型
Function<T,R>TR
Predicate<T>Tboolean
Supplier<T>NoneT
UnaryOperator<T>TT
Consumer<T>Tvoid
BinaryOperator<T>(T, T)T

Lambda

我们可以用Lambda表达式来实例化函数式接口,避免了内部类冗余的代码。

Lambda表达式的组成

Lambda表达式有多种形式,总结来说不外乎三部分组成:第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数,参数中可以指定类型,也可以省略类型(如果是单个参数的情况下括号可以省略,无参或者多个参数则不能省略括号);第二部分为一个箭头符号:-> 是lambda的运算符;第三部分为方法体,可以是表达式和代码块。如下

TestInterface t = () -> System.out.println("lambda");

如果有入参,第三部分是代码块,而且实现的接口有返回值的时候需要加上{}且要把结果return

public interface LongToInt { int toInt(Long i); }
LongToInt lti = i -> {
            if(i != null){
                return i.intValue();
            } else return 0;
        };

在Java8以前要想实例化接口使用匿名内部类来处理,代码显得啰嗦而不易读

new TestInterface(){
            public void test() {
                System.out.println("lambda");
            }
        }.test();

使用lambda表达式后就可以简化成一行代码

((TestInterface)() -> System.out.println("lambda")).test();

Lamdba中的方法引用

如果方法体只是简单的函数引用则可以直接使用 :: 引用,更大程度上简化我们的代码。Java8中的 :: 符号表示:符号左边的对象(List<Integer> res),调用符号右边的方法(add()),参数即为lamdba表达式的参数(Integer)。

List<Integer> res = Lists.newArrayList();
ints.forEach(res :: add);

Stream

java8中引用stream可以对集合进行过滤、排序、映射等操作。

生成Stream

Java8的stream分为两种,即并行和串行。可以通过Collection.parallelStream()生成一个并行流,可以通过Collection.stream()生成一个串行流(默认的stream开启的是串行流)。Stream通过StreamSupport.stream(Spliterator<T> spliterator, boolean parallel)方法中的parallel来判断是否生成并行流。

default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);}
default Stream<E> parallelStream() {return StreamSupport.stream(spliterator(), true);}

我们还可以将生成的stream通过stream.parallel()或stream.sequential()转换成并行或者串行。
通过方法名我们不难理解,串行流在一个线程依次执行,而并行流则是在多个线程同时执行。并行流的执行效率远远大于串行流。

Stream应用

一般在Java中使用stream有三步:第一步生成stream;第二步对stream进行转换;第三步聚合操作得到想要的结果。

Stream的中间转换操作

  1. distinct: 对stream中的元素进行去重操作。
  2. filter: 对stream中包含的元素使用给定的过滤函数进行过滤操作,而filter方法传的参数就是Predicate实例化,即可以. 使用上面提到的lambda表达式(新的stream中只包含Predicate中判定为true的元素)。
    map: 对stream中包含的元素使用给定的转换函数进行转换操作.这个方法有三个引申方法,分别是:mapToInt,mapToLong和mapToDouble,在map中执行转换函数,再转换新的的stream元素为Integer, Long, Double类型。转换函数类型为Function,用lambda表达式比较方便。
  3. flatMap:会把要求Function的返回值是stream即在map里面用stream的子元素再生成一个stream,把所有的子元素生成的stream压缩成一个stream。
  4. boxed: 将LongStream、IntStream、DoubleStream转换成对应类型的Stream<T>。
  5. limit: 对一个stream进行截断操作,获取其前n个元素,如果原stream中包含的元素个数小于n,那就获取其所有的元素;
  6. skip: 返回一个丢弃原stream的前n个元素后剩下元素组成的新stream,如果原stream中包含的元素个数小于n,那么返回空stream;

Stream的聚合操作

  1. collect: 将stream元素收集起来,可以通过Collectors.toList()实现将stream转换为List的操作。
  2. count: 得到stream的元素个数。
  3. findFirst: 得到第一个元素。
  4. max: 需要自定义一个Comparator来比较元素的大小,返回最大值。
  5. min: 需要自定义一个Comparator来比较元素的大小,返回最小值。
  6. allMatch: 判断元素是否全部符合Predicate标准。
  7. anyMatch: 判断元素是否有一个或者多个符合Predicate标准。
  8. noneMatch: 判断元素是否都不符合Predicate标准。
List<String> strList = Lists.newArrayList("11 ", "213 ", " 11", "23", "145", "15 ", "2 ", " 3", "");
List<Integer> ints = Lists.partition(strList, 3).stream().flatMapToInt(strs -> strs.stream().filter(str -> str != null && !Objects.equals(str, "")).mapToInt(str -> Integer.valueOf(str.trim()))).distinct().limit(5).skip(3).boxed().collect(Collectors.toList());

上面的例子是把一个List<String>,先通过Guava Lists将该list按每组3个拆分生成List<List<String>>再转换为Stream<List<String>>,再flatMapToInt,在map里面再将list转换为Stream<String>,过滤掉空字符串和null,再mapToInt去重,取前5个,再丢弃前三个元素,再转换成一个List<Integer>。

注:所有的聚合操作、foreach操作都会把stream关闭,如果一个stream被关闭后,不能再次对stream进行任何操作。

    原文作者:youngYmca
    原文地址: https://www.jianshu.com/p/d6f1905cc9f6
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞