Java 8新特性Lambda表达式Stream API用法

目录

一、基本用法

1、基本过滤

2、基本转换

3、基本的过滤和转换组合

二、中间操作

1、distinct

2、sorted

3、skip/limit

4、peek

5、mapToLong/mapToInt/mapToDouble

6、flatMap

三、终端操作

1、max/min

2、count

3、allMatch/anyMatch/noneMatch

4、findFirst/findAny

5、forEach

6、toArray

7、reduce

四、容器收集器

1、toSet

2、toCollection

3、toMap

4、字符串收集器

五、 分组

1、基本用法

2、分组计数、找最大/最小元素

3、分组数值统计

4、分组内的map

5、分组结果处理(filter/sort/skip/limit)

6、分区

五、函数式数据处理思维


一、基本用法

先创建学生类

public class Student {

    private String name;

    private Integer score;

    private String grade;
}

创建学生列表

Student stu1 = Student.builder().name("张三").grade("一年级").score(92).build();
Student stu2 = Student.builder().name("李四").grade("二年级").score(89).build();
Student stu3 = Student.builder().name("王五").grade("一年级").score(90).build();
Student stu4 = Student.builder().name("赵六").grade("二年级").score(91).build();
Student stu5 = Student.builder().name("陈七").grade("一年级").score(88).build();
Student stu6 = Student.builder().name("周八").grade("二年级").score(86).build();
Student stu7 = Student.builder().name("孙九").grade("一年级").score(99).build();

List  stuList = new ArrayList<>();
stuList.add(stu1);
stuList.add(stu2);
stuList.add(stu3);
stuList.add(stu4);
stuList.add(stu5);
stuList.add(stu6);
stuList.add(stu7);

1、基本过滤

返回学生列表中90分以上的同学

List collect =
		stuList.stream()
				.filter(s -> s.getScore() > 90)
				.collect(Collectors.toList());

collect.forEach(System.out::println);

输出结果:

Student(name=张三, score=92, grade=一年级)
Student(name=赵六, score=91, grade=二年级)
Student(name=孙九, score=99, grade=一年级)

2、基本转换

根据学生列表返回名称列表

List nameList =
		stuList.stream()
				.map(Student::getName)
				.collect(Collectors.toList());

nameList.forEach(System.out::println);

输出结果:

张三
李四
王五
赵六
陈七
周八
孙九

 

3、基本的过滤和转换组合

返回90分以上的学生名称列表

List collect =
		stuList.stream()
				.filter(s -> s.getScore() > 90)
				.map(Student::getName)
				.collect(Collectors.toList());

collect.forEach(System.out::println);

输出结果:

张三
赵六
孙九

二、中间操作

1、distinct

distinct返回一个新的Stream,过滤重复的元素,只留下唯一的元素,是否重复是根据equals方法来比较的。

List strList = new ArrayList<>();

strList.add("abc");
strList.add("bcd");
strList.add("cde");
strList.add("abc");

List collect =
		strList.stream()
				.distinct()
				.collect(Collectors.toList());

collect.forEach(System.out::println);

输出结果 :

abc
bcd
cde

2、sorted

有两个sorted方法:

Stream sorted(Comparator comparator);
Stream sorted();

1)第一个方法接受一个自定义的Comparator。 根据分数排序

List collect =
		stuList.stream()
				.sorted(Comparator.comparing(Student::getSore))
				.collect(Collectors.toList());

collect.forEach(System.out::println);

输出结果:

Student(name=周八, score=86, grade=二年级)
Student(name=陈七, score=88, grade=一年级)
Student(name=李四, score=89, grade=二年级)
Student(name=王五, score=90, grade=一年级)
Student(name=赵六, score=91, grade=二年级)
Student(name=张三, score=92, grade=一年级)
Student(name=孙九, score=99, grade=一年级)

2)第二个方法假定元素实现了Comparable接口

学生类实现Comparable接口

public class Student implements Comparable {

    private String name;

    private Integer sore;

    private String grade;

    @Override
    public int compareTo(Object obj) {

        Student stu1 = (Student) obj;

        if (stu1.getSore().equals(this.sore)){
            return 0;
        } else if (stu1.getSore() > this.sore){
            return -1;
        } else {
            return 1;
        }

    }
}

根据分数排序:

List collect1 = stuList.stream().sorted().collect(Collectors.toList());

collect1.forEach(System.out::println);

输出结果 :

Student(name=周八, score=86, grade=二年级)
Student(name=陈七, score=88, grade=一年级)
Student(name=李四, score=89, grade=二年级)
Student(name=王五, score=90, grade=一年级)
Student(name=赵六, score=91, grade=二年级)
Student(name=张三, score=92, grade=一年级)
Student(name=孙九, score=99, grade=一年级)

3、skip/limit

Stream skip(long n);
Stream limit(long maxSize);

skip跳过流中的n个元素,如果流中元素不足n个,返回一个空流,limit限制流的长度为maxSize。

比如,将学生列表按照分数从高到低排序,分数一样的按名称排序,返回第3名到第5名,代码为:

List collect =
		stuList.stream()
				.sorted( Comparator.comparing(Student::getScore)
						.reversed()
						.thenComparing(Student::getName))
				.skip(2)
				.limit(3)
				.collect(Collectors.toList());

collect.forEach(System.out::println);

输出结果:

Student(name=王五, score=90, grade=一年级)
Student(name=赵六, score=90, grade=二年级)
Student(name=李四, score=89, grade=二年级)

skip和limit都是有状态的中间操作。对前n个元素,skip的操作就是过滤,对后面的元素,skip就是传递给流水线中的下一个操作。limit的一个特点是:它不需要处理流中的所有元素,只要处理的元素个数达到maxSize,后面的元素就不需要处理了,这种可以提前结束的操作称为短路操作。 

4、peek

Stream peek(Consumer action);

它返回的流与之前的流是一样的,没有变化,但它提供了一个Consumer,会将流中的每一个元素传给该Consumer。这个方法的主要目的是支持调试,可以使用该方法观察在流水线中流转的元素,比如:

List collect = stuList.stream()
	.peek(System.out::println).map(Student::getName).collect(Collectors.toList());
System.out.println("====================================================");
collect.forEach(System.out::println);

输出结果:

Student(name=张三, score=92, grade=一年级)
Student(name=李四, score=89, grade=二年级)
Student(name=王五, score=90, grade=一年级)
Student(name=赵六, score=90, grade=二年级)
Student(name=陈七, score=88, grade=一年级)
Student(name=周八, score=86, grade=二年级)
Student(name=孙九, score=99, grade=一年级)
====================================================
张三
李四
王五
赵六
陈七
周八
孙九

5、mapToLong/mapToInt/mapToDouble

map函数接受的参数是一个Function,为避免装箱/拆箱,提高性能,Stream还有如下返回基本类型特定流的方法:

IntStream mapToInt(ToIntFunction mapper);
LongStream mapToLong(ToLongFunction mapper);
DoubleStream mapToDouble(ToDoubleFunction mapper);

DoubleStream/IntStream/LongStream是基本类型特定的流,有一些专门的更为高效的方法。比如,求学生列表的分数总和,代码为:

int sum = stuList.stream().mapToInt(Student::getScore).sum();

6、flatMap

 Stream flatMap(Function> mapper);

它接受一个函数mapper,对流中的每一个元素,mapper会将该元素转换为一个流Stream,然后把新生成流的每一个元素传递给下一个操作。比如:

List strList = new ArrayList<>();

strList.add("abc,bcd");
strList.add("cde,def");

List collect =
		strList.stream()
				.flatMap(s -> Arrays.stream(s.split(",")))
				.collect(Collectors.toList());

collect.forEach(System.out::println);

 这里的mapper将一行字符串按空白符分隔为了一个单词流,Arrays.stream可以将一个数组转换为一个流,输出为:

abc
bcd
cde
def

可以看出,实际上,flatMap完成了一个1到n的映射。

三、终端操作

中间操作不触发实际的执行,返回值是Stream,而终端操作触发执行,返回一个具体的值,除了collect, Stream API的终端操作还有max、min、count、allMatch、anyMatch、noneMatch、findFirst、findAny、forEach、toArray、reduce等,我们逐个介绍。

1、max/min

Optional max(Comparator comparator);
Optional min(Comparator comparator);

它们返回流中的最大值/最小值,它们的返回值类型是Optional,而不是T。java.util.Optional是Java 8引入的一个新类,它是一个泛型容器类,内部只有一个类型为T的单一变量value,可能为null,也可能不为null。Optional有什么用呢?它用于准确地传递程序的语义,它清楚地表明,其代表的值可能为null,程序员应该进行适当的处理。

Optional max = stuList.stream().max(Comparator.comparing(Student::getScore));
Student student = max.get();
System.out.println(student);

输出结果:

Student(name=孙九, score=99, grade=一年级)

2、count

返回流中元素的个数

long count = stuList.stream().filter(stu -> stu.getScore() > 90).count();

3、allMatch/anyMatch/noneMatch

这几个函数都接受一个谓词Predicate,返回一个boolean值,用于判定流中的元素是否满足一定的条件。它们的区别是:

  • allMatch:只有在流中所有元素都满足条件的情况下才返回true。
  • anyMatch:只要流中有一个元素满足条件就返回true。
  • noneMatch:只有流中所有元素都不满足条件才返回true。

如果流为空,那么这几个函数的返回值都是true。比如,判断是不是所有学生都及格了(不小于60分),代码可以为:

boolean b = stuList.stream().allMatch(stu -> stu.getScore() > 60);

这几个操作都是短路操作,不一定需要处理所有元素就能得出结果,比如,对于all-Match,只要有一个元素不满足条件,就能返回false。

4、findFirst/findAny

Optional findFirst();
Optional findAny();

它们的返回类型都是Optional,如果流为空,返回Optional.empty()。findFirst返回第一个元素,而findAny返回任一元素,它们都是短路操作。随便找一个不及格的学生,代码可以为:

Optional any = stuList.stream().filter(stu -> stu.getScore() < 60).findAny();

if (any.isPresent()){
	System.out.println( any.get().getName() + "拖出去,打一顿");
}

5、forEach

void forEach(Consumer action);
void forEachOrdered(Consumer action);

它们都接受一个Consumer,对流中的每一个元素,传递元素给Consumer。区别在于:在并行流中,forEach不保证处理的顺序,而forEachOrdered会保证按照流中元素的出现顺序进行处理。

stuList.stream().filter(stu -> stu.getScore() > 90).forEach(System.out::println);

6、toArray

Object[] toArray();
 A[] toArray(IntFunction generator);

不带参数的toArray返回的数组类型为Object[],这通常不是期望的结果,如果希望得到正确类型的数组,需要传递一个类型为IntFunction的generator。generator接受的参数是流的元素个数,它应该返回对应大小的正确类型的数组。比如,获取90分以上的学生数组,代码可以为:

Student[] students =
		stuList.stream()
				.filter(stu -> stu.getScore() > 90)
				.toArray(Student[]::new);

Arrays.stream(students).forEach(System.out::println);

输出结果:

Student(name=张三, score=92, grade=一年级)
Student(name=孙九, score=99, grade=一年级)

7、reduce

reduce代表归约或者叫折叠,它是max/min/count的更为通用的函数,将流中的元素归约为一个值。有三个reduce函数:

Optional reduce(BinaryOperator accumulator);
 U reduce(U identity,
                 BiFunction accumulator,
                 BinaryOperator combiner);
 R collect(Supplier supplier,
                 BiConsumer accumulator,
                 BiConsumer combiner);

reduce函数虽然更为通用,但比较费解,难以使用,一般情况下应该优先使用其他函数。collect函数比reduce函数更为通用、强大和易用,关于它,我们稍后再详细介绍。

四、容器收集器

对于collect方法,前面只是演示了其最基本的应用,它还有很多强大的功能,比如与toList类似的容器收集器还有toSet、toCollection、toMap等

1、toSet

toSet的使用与toList类似,只是它可以排重,就不举例了。toList背后的容器是ArrayList, toSet背后的容器是HashSet

2、toCollection

toCollection是一个通用的容器收集器,可以用于任何Collection接口的实现类。比如,如果希望排重但又希望保留出现的顺序,可以使用LinkedHashSet, Collector可以这么创建:

List strList = new ArrayList<>();
strList.add("abc");
strList.add("bcd");
strList.add("cde");
strList.add("abc");

LinkedHashSet collect = strList.stream()
        .filter(s -> s.length() > 2)
        .collect(Collectors.toCollection(LinkedHashSet::new));

collect.forEach(System.out::println);

输出结果:

abc
bcd
cde

3、toMap

Collector> toMap(Function keyMapper,
                                    Function valueMapper);

Collector> toMap(Function keyMapper,
                                    Function valueMapper,
                                    BinaryOperator mergeFunction);

Collector toMap(Function keyMapper,
                                Function valueMapper,
                                BinaryOperator mergeFunction,
                                Supplier mapSupplier)

 1)第一个方法示例代码:

Map collect = stuList.stream()
        .collect(Collectors.toMap(Student::getName, s -> s));

System.out.println(collect);

注:键如果有重复的,会抛出异常的  

s->s是valueMapper,表示值就是元素本身。这个函数用得比较多,接口Function定义了一个静态函数identity表示它。也就是说,上面的代码可以替换为:

Map collect = stuList.stream()
        .collect(Collectors.toMap(Student::getName, Function.identity()));

System.out.println(collect);

输出结果:

{
    李四 = Student(name = 李四, score = 89, grade = 二年级),
    张三 = Student(name = 张三, score = 92, grade = 一年级), 
    王五 = Student(name = 王五, score = 90, grade = 一年级), 
    周八 = Student(name = 周八, score = 86, grade = 二年级), 
    陈七 = Student(name = 陈七, score = 88, grade = 一年级), 
    赵六 = Student(name = 赵六, score = 91, grade = 二年级), 
    孙九 = Student(name = 孙九, score = 99, grade = 一年级)
}

2)处理键重复问题:相比前面的toMap,多了一个mapSupplier,它是Map的工厂方法,对于前面的两个toMap,其mapSupplier其实是HashMap::new。我们知道,HashMap是没有任何顺序的,如果希望保持元素出现的顺序,可以替换为LinkedHashMap,如果希望收集的结果排序,可以使用TreeMap。

List strList = new ArrayList<>();
strList.add("abc");
strList.add("bcd");
strList.add("cde");
strList.add("abc");

Map collect = 
        strList.stream().collect(Collectors.toMap(Function.identity(), s -> s.length(), (oldValue, value) -> value));
System.out.println(collect);

相比前面的toMap,它接受一个额外的参数mergeFunction,它用于处理冲突,在收集一个新元素时,如果新元素的键已经存在了,系统会将新元素的值与键对应的旧值一起传递给mergeFunction得到一个值,然后用这个值给键赋值。 

也可以进行处理:

Map collect =
        strList.stream().collect(Collectors.toMap(Function.identity(), s -> s.length(), (oldValue, value) -> oldValue + value));
System.out.println(collect);

输出结果:

{bcd=3, abc=6, cde=3}

3)第三个方法相比前面的toMap,多了一个mapSupplier,它是Map的工厂方法,对于前面的两个toMap,其mapSupplier其实是HashMap::new。我们知道,HashMap是没有任何顺序的,如果希望保持元素出现的顺序,可以替换为LinkedHashMap,如果希望收集的结果排序,可以使用TreeMap。

Map collect =
        strList.stream().collect(Collectors.toMap(Function.identity(), s -> s.length(), (oldValue, value) -> value, LinkedHashMap::new));
System.out.println(collect);

4、字符串收集器

public static Collector joining();

public static Collector joining(CharSequence delimiter);

public static Collector joining(CharSequence delimiter,
                                                             CharSequence prefix,
                                                             CharSequence suffix);

第一个就是简单地把元素连接起来,第二个支持一个分隔符,还可以给整个结果字符串加前缀和后缀,比如:

List strList = new ArrayList<>();
strList.add("abc");
strList.add("bcd");
strList.add("cde");
strList.add("abc");

String collect = strList.stream().collect(Collectors.joining(",", "[", "]"));
System.out.println(collect);

 输出结果:

[abc,bcd,cde,abc]

五、 分组

1、基本用法

Map> collect = stuList.stream()
        .collect(Collectors.groupingBy(Student::getGrade));

System.out.println(collect);

 输出结果:

{
    一年级=[
        Student(name=张三, score=92, grade=一年级), 
        Student(name=王五, score=90, grade=一年级), 
        Student(name=陈七, score=88, grade=一年级), 
        Student(name=孙九, score=99, grade=一年级)], 
    二年级=[
        Student(name=李四, score=89, grade=二年级), 
        Student(name=赵六, score=91, grade=二年级), 
        Student(name=周八, score=86, grade=二年级)
    ]
}

2、分组计数、找最大/最小元素

// 计数
public static  Collector counting();
// 计算最小值
public static  Collector> minBy(Comparator comparator);
// 计算最大值
public static  Collector> maxBy(Comparator comparator);

1)计数代码示例:

Map collect = stuList.stream()
        .collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));

System.out.println(collect);

输出结果:

{一年级=4, 二年级=3}

2)获取最大值

Map> collect = stuList.stream()
        .collect(Collectors.groupingBy(Student::getGrade, Collectors.maxBy(Comparator.comparing(Student::getScore))));

System.out.println(collect);

 需要说明的是,这个分组收集结果是Optional,而不是Student,这是因为maxBy处理的流可能是空流,但对我们的例子,这是不可能的。为了直接得到Student,可以使用Collectors的另一个收集器collectingAndThen,在得到Optional后调用Optional的get方法,如下所示:

Map collect = stuList.stream()
        .collect(
                Collectors.groupingBy(
                        Student::getGrade,
                        Collectors.collectingAndThen(
                                Collectors.maxBy(Comparator.comparing(Student::getScore)),
                                Optional::get)));

System.out.println(collect);

输出结果:

{
    一年级=Student(name=孙九, score=99, grade=一年级),
    二年级=Student(name=赵六, score=91, grade=二年级)
}

 关于collectingAndThen,我们稍后再进一步讨论。

3、分组数值统计

除了基本的分组计数,还经常需要进行一些分组数值统计,比如求学生分数的和、平均分、最高分、最低分等、针对int、long和double类型,Collectors提供了专门的收集器,比如:

// 求平均值,double和long也有类似方法
public static  Collector averagingInt(ToIntFunction mapper);

// 求和,double和long也有类似方法
public static  Collector summingInt(ToIntFunction mapper);

// 求多种汇总信息,double和long也有类似方法
// IntSummaryStatistics包括个数、最大值、最小值、和、平均数等多种信息
public static  Collector summarizingInt(ToIntFunction mapper);

 比如,按年级统计学生分数信息,代码可以为:

Map collect = stuList.stream()
        .collect(Collectors.groupingBy(Student::getGrade, Collectors.summarizingInt(Student::getScore)));

System.out.println(collect);

输出结果:

{
    一年级=IntSummaryStatistics{count=4, sum=369, min=88, average=92.250000, max=99}, 
    二年级=IntSummaryStatistics{count=3, sum=266, min=86, average=88.666667, max=91}
}

4、分组内的map

对于每个分组内的元素,我们感兴趣的可能不是元素本身,而是它的某部分信息。在StreamAPI中,Stream有map方法,可以将元素进行转换,Collectors也为分组元素提供了函数mapping,如下所示:

public static  Collector mapping(
        Function mapper,Collector downstream);

 对学生按年级分组,得到学生名称列表,代码可以为:

Map> collect = stuList.stream().collect(
        Collectors.groupingBy(
                Student::getGrade, Collectors.mapping(
                        Student::getName, Collectors.toList())));

System.out.println(collect);

 输出结果:

{
    一年级=[张三, 王五, 陈七, 孙九], 
    二年级=[李四, 赵六, 周八]
}

5、分组结果处理(filter/sort/skip/limit)

对分组后的元素,我们可以计数,找最大/最小元素,计算一些数值特征,还可以转换(map)后再收集,那可不可以像Stream API一样,排序(sort)、过滤(filter)、限制返回元素(skip/limit)呢?Collector没有专门的收集器,但有一个通用的方法:

public static Collector collectingAndThen(Collector downstream,
                                                                Function finisher); 

 这个方法接受一个下游收集器downstream和一个finisher,返回一个收集器,也就是说,它在下游收集器的结果上又调用了finisher。利用这个finisher,我们可以实现多种功能,下面看一些例子。收集完再排序,可以定义如下方法:

public  Collector> collectingAndSort(Collector> downstream, Comparator comparator){
    Collector> tListCollector = Collectors.collectingAndThen(downstream, (r) -> {
        r.sort(comparator);
        return r;
    });
    return tListCollector;
}

将学生按年级分组,分组内的学生按照分数由高到低进行排序,利用这个方法,代码可以为:

class DemoApplicationTests {

    @Test
    void contextLoads2() {
        //  生成学生列表
        List stuList = this.getStuList();

        Map> collect = stuList.stream().collect(
                Collectors.groupingBy(
                        Student::getGrade, this.collectingAndSort(
                                Collectors.toList(),
                                Comparator.comparing(Student::getScore).reversed())));

        System.out.println(collect);
    }

    public  Collector> collectingAndSort(Collector> downstream, Comparator comparator){
        Collector> tListCollector = Collectors.collectingAndThen(downstream, (r) -> {
            r.sort(comparator);
            return r;
        });
        return tListCollector;
    }

    List getStuList() {
        Student stu1 = Student.builder().name("张三").grade("一年级").score(92).build();
        Student stu2 = Student.builder().name("李四").grade("二年级").score(89).build();
        Student stu3 = Student.builder().name("王五").grade("一年级").score(90).build();
        Student stu4 = Student.builder().name("赵六").grade("二年级").score(91).build();
        Student stu5 = Student.builder().name("陈七").grade("一年级").score(88).build();
        Student stu6 = Student.builder().name("周八").grade("二年级").score(86).build();
        Student stu7 = Student.builder().name("孙九").grade("一年级").score(99).build();

        List  stuList = new ArrayList<>();
        stuList.add(stu1);
        stuList.add(stu2);
        stuList.add(stu3);
        stuList.add(stu4);
        stuList.add(stu5);
        stuList.add(stu6);
        stuList.add(stu7);

        return stuList;
    }

}

6、分区

分组的一个特殊情况是分区,就是将流按true/false分为两个组,Collectors有专门的分区函数:

public static  Collector>> partitioningBy(Predicate predicate);

public static  Collector> partitioningBy(Predicate predicate, Collector downstream);

 第一个函数的下游收集器为toList(),第二个函数可以指定一个下游收集器。比如,将学生按照是否及格(大于等于60分)分为两组,代码可以为:

Map> collect = stuList.stream().collect(Collectors.partitioningBy(s -> s.getScore() >= 60));
System.out.println(collect);

输出结果:

{
    false=[], 
    true=[
        Student(name=张三, score=92, grade=一年级),
        Student(name=李四, score=89, grade=二年级), 
        Student(name=王五, score=90, grade=一年级), 
        Student(name=赵六, score=91, grade=二年级), 
        Student(name=陈七, score=88, grade=一年级), 
        Student(name=周八, score=86, grade=二年级), 
        Student(name=孙九, score=99, grade=一年级)
    ]
}

 按是否及格分组后,计算每个分组的平均分,代码可以为:

Map collect = stuList.stream().collect(
        Collectors.partitioningBy(
                s -> s.getScore() >= 60, Collectors.averagingInt(Student::getScore)));

System.out.println(collect);

输出结果:

{false=0.0, true=90.71428571428571}

五、函数式数据处理思维

可以看出,使用Stream API处理数据集合,与直接使用容器类API处理数据的思路是完全不一样的。流定义了很多数据处理的基本函数,对于一个具体的数据处理问题,解决的主要思路就是组合利用这些基本函数,以声明式的方式简洁地实现期望的功能,这种思路就是函数式数据处理思维,相比直接利用容器类API的命令式思维,思考的层次更高。

你可能感兴趣的:(开发经验)