JDK8 Lambda表达式之Stream常用方法和注意事项(五)

1,001 阅读10分钟

Stream 常用的方法

Stream流模型的操作很丰富,这里介绍一些常用的api.

流方法含义示例
filter(中间操作)该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。List vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());System.out.println(vegetarianMenu);
distinct(中间操作)返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流。List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
limit(中间操作)会返回一个不超过给定长度的流。List dishes = menu.stream().filter(d -> d.getCalories() > 300).limit(3).collect(toList());System.out.println(dishes);
skip(中间操作)返回一个扔掉了前n个元素的流。List dishes = menu.stream().filter(d -> d.getCalories() > 300).skip(2).collect(toList());System.out.println(dishes);
map(中间操作)接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)。List dishNames = menu.stream().map(Dish::getName).collect(toList());System.out.println(dishNames);
flatMap(中间操作)使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。List words = Arrays.asList("Goodbye", "World");List uniqueCharacters = words.stream().map(w -> w.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());System.out.println(uniqueCharacters);
sorted(中间操作)返回排序后的流List traderStr = menu.stream().map(transaction -> transaction.getName()).sorted().collect(toList());System.out.println(traderStr);
anyMatch(终端操作)可以回答“流中是否有一个元素能匹配给定的谓词”。if (menu.stream().anyMatch(Dish::isVegetarian)) {System.out.println("The menu is (somewhat) vegetarian friendly!!");
allMatch(终端操作)会看看流中的元素是否都能匹配给定的谓词。
noneMatch(终端操作)可以确保流中没有任何元素与给定的谓词匹配。boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);System.out.println(isHealthy);
findAny(终端操作)将返回当前流中的任意元素。Optional dish = menu.stream().filter(Dish::isVegetarian).findAny();System.out.println(dish);
findFirst(终端操作)有些流有一个出现顺序(encounter order)来指定流中项目出现的逻辑顺序(比如由List或排序好的数据列生成的流)。对于这种流,可能想要找到第一个元素。List someNumbers = Arrays.asList(1, 2, 3, 4, 5);Optional firstSquareDivisibleByThree = someNumbers.stream().map(x -> x * x).filter(x -> x % 3 == 0).findFirst();System.out.println(firstSquareDivisibleByThree);
forEach(终端操作)遍历流List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
collect(终端操作)收集器List traderStr = menu.stream().map(transaction -> transaction.getName()).sorted().collect(toList());
reduce(终端操作)归约reduce接受两个参数:一个初始值,这里是0;一个BinaryOperator来将两个元素结合起来产生一个新值,这里我们用的是lambda (a, b) -> a + b。List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);int sum = numbers.stream().reduce(0, (a, b) -> a + b);System.out.println(sum);
count(终端操作)返回此流中元素的计数。long evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0).count();System.out.println(evenNumbers);

100385620190831155904560877244928.png

  • 终结方法: 返回值类型不再是Stream类型的方法,不再支持链式调用.
  • 非终结方法: 返回值类型仍然是Stream类型的方法,支持链式调用.

Stream注意事项

  1. Stream 只能调用1次
  2. Stream 方法返回的是新的流
  3. Stream不调用终结方法, 中间的操作不执行

总结

  • 可以使用filter、distinct、skip和limit对流做筛选和切片。
  • 可以使用map和flatMap提取或转换流中的元素。
  • 可以使用findFirst和findAny方法查找流中的元素。可以用allMatch、noneMatch和anyMatch方法让流匹配给定的谓词。
  • 这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流。
  • 可以利用reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大元素。
  • filter和map等操作是无状态的,它们并不存储任何状态。reduce等操作要存储状态才能计算出一个值。sorted和distinct等操作也要存储状态,因为它们需要把流中的所有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。
  • 流有三种基本的原始类型特化:IntStream、DoubleStream和LongStream。它们的操作也有相应的特化。
  • 流不仅可以从集合创建,也可从值、数组、文件以及iterate与generate等特定方法创建。
  • •无限流是没有固定大小的流。

ForEach方法

public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one ,"赵信", "武则天", "虞姬","项羽","干将莫邪");
        one.stream().forEach(str->
            System.out.println(str)
        );
    }

Count 方法

 public static void main(String[] args) {
   List<String> one = new ArrayList<>();
   Collections.addAll(one ,"赵信", "武则天", "虞姬","项羽","干将莫邪");
    long count = one.stream().count();
    System.out.println(count);
 }

Filter

public void testFilter(){
        List<String> one = new ArrayList<>();
        Collections.addAll(one ,"赵信", "武则天", "虞姬","项羽","干将莫邪");
        one.stream().filter(str->
                str.length()==3
        ).forEach( e->
                System.out.println(e)
        );
    }

Limit

image20200810073831613.png

Limit 方法可以对流进行截取,根据条件获取前几个. 方法如下:

Stream<T> limit (long maxSize);

参数是一个long类型,如果集合当前长度大于参数则进行截取,否则不进行操作. 具体使用如下:

public void testLimit(){
        List<String> one = new ArrayList<>();
        Collections.addAll(one ,"赵信", "武则天", "虞姬","项羽","干将莫邪");
        one.stream().limit(3).forEach( e->
                System.out.println(e)
        );
    }

skip

image20200810073738688.png 如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流?:

Stream<T> skip (long n);

如果流的长度大于n,则跳过前n个; 否则将会得到一个长度为0的空流. 基本使用如下:

 public void testSkip(){
        List<String> one = new ArrayList<>();
        Collections.addAll(one ,"赵信", "武则天", "虞姬","项羽","干将莫邪");
        one.stream().skip(2).forEach( e->
                System.out.println(e)
        );
    }

Map

image20200810074548187.png

如果需要将流中的元素映射到另一个流中,可以使用map方法,

    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换成另一种R类型的流.基本使用如下:

public void testMap(){
       System.out.println("Output with simple list");
        List<String> vowels = Arrays.asList("A", "E", "I", "O", "U");
        vowels.stream().map(vowel -> vowel.toLowerCase())
                .forEach(value -> System.out.println(value));
        List<String> haiList = new ArrayList<>();
        haiList.add("hello");
        haiList.add("hai");
        haiList.add("hehe");
        haiList.add("hi");
        System.out.println("Output with nested List of List<String>");
        List<String> welcomeList = new ArrayList<>();
        welcomeList.add("You got it");
        welcomeList.add("Don't mention it");
        welcomeList.add("No worries.");
        welcomeList.add("Not a problem");
        List<List<String>> nestedList = Arrays.asList(haiList, welcomeList);
        nestedList.stream().map(list -> {
            return list.stream().map(value -> value.toUpperCase());
        }).forEach(value -> System.out.println(value));
    }

输出

Output with simple list
a
e
i
o
u
Output with nested List of List<String>
java.util.stream.ReferencePipeline$3@3b9a45b3
java.util.stream.ReferencePipeline$3@7699a589

FlatMap

FlatMap 能够将流的每个元素, 转换为其他对象的流。因此,每个对象可以被转换为零个,一个或多个其他对象,并以流的方式返回。之后,这些流的内容会被放入flatMap返回的流中。

public static void main(String[] args) {
        List<String> haiList = new ArrayList<>();
        haiList.add("hello");
        haiList.add("hai");
        haiList.add("hehe");
        haiList.add("hi");
        System.out.println("Output with nested List of List<String>");
        List<String> welcomeList = new ArrayList<>();
        welcomeList.add("You got it");
        welcomeList.add("Don't mention it");
        welcomeList.add("No worries.");
        welcomeList.add("Not a problem");
        List<List<String>> nestedList = Arrays.asList(haiList, welcomeList);
        nestedList.stream().flatMap(
                list -> list.stream())
                .map(value -> value.toUpperCase())
                .forEach(value -> System.out.println(value));
    }

输出

Output with nested List of List<String>
HELLO
HAI
HEHE
HI
YOU GOT IT
DON'T MENTION IT
NO WORRIES.
NOT A PROBLEM

map() vs flatMap()

  • map()和flatMap()方法都可以应用于Stream<T>Optional<T>。并且都返回StreamOptional<U>.
  • 区别在于,映射操作为每个输入值生成一个输出值,而flatMap操作为每个输入值生成任意数量(零个或多个)的值。在flatMap()中,每个输入始终是一个集合,可以是List或Set或Map。映射操作采用一个函数,该函数将为输入流中的每个值调用,并生成一个结果值,该结果值将发送到输出流。flatMap操作采用的功能在概念上想消耗一个值并产生任意数量的值。但是,在Java中,方法返回任意数量的值很麻烦,因为方法只能返回零或一个值。

Sorted

如果需要将数据排序,可以使用sorted方法

    Stream<T> sorted(Comparator<? super T> comparator);
    Stream<T> sorted();

基本使用:

public void testSorted(){
    List<String> one = new ArrayList<>();
    Collections.addAll(one ,"11", "22", "33","44","55");
    one.stream().sorted().forEach( e ->{
        System.out.println(e);
    });
}
 public void testSorted2(){
        List<Integer> one = new ArrayList<>();
        Collections.addAll(one ,33,22,11,44,66,12,29);
        one.stream().sorted((e1,e2)->{
           return e2 -e1;
        }).forEach( e ->{
            System.out.println(e);
        });
       /* one.stream().sorted((e1,e2)-> e2 -e1).forEach( e ->{
                System.out.println(e);
            });
        */
    }

distinct

image20200810080214048.png 如果需要去除重复的数据,可以使用distinct 方法,方法签名如下

  Stream<T> distinct();

Stream流中的distinct方法基本使用的代码如下:

@Test
public void testDistinct(){
    List<Integer> one = new ArrayList<>();
    Collections.addAll(one ,33,22,11,44,33,22,29);
    one.stream().distinct().forEach(e->{
        System.out.println(e);
    });
}
public void testDistinct2(){
        Stream<Person> personStream = Stream.of(
                new Person("貂蝉", 18),
                new Person("王昭君", 20),
                new Person("王昭君", 20),
                new Person("西施", 19),
                new Person("西施", 19),
                new Person("杨玉环", 20));
        //对象类型,需要重写对象的hashcode和equals方法
        personStream.distinct().forEach(e->{
            System.out.println(e);
        });
    }

Match

如果需要判断数据是否匹配指定的条件,可以使用match相关方法. 方法签名如下;

    boolean anyMatch(Predicate<? super T> predicate);
    boolean allMatch(Predicate<? super T> predicate);
    boolean noneMatch(Predicate<? super T> predicate);
 public void testMatch  (){
        List<Integer> one = new ArrayList<>();
        Collections.addAll(one ,33,22,11,44,38,16,29);
   			//allMatch 匹配所有,所有元素都需要满足条件
        boolean result = one.stream().allMatch(e -> e > 20);
   			//anyMatch 匹配某个元素,只要有一个元素满足条件接口
        boolean result2 = one.stream().anyMatch(e -> e > 20);
   			//noneMatch 匹配所有,所有元素都不需要满足条件
        boolean result3 = one.stream().noneMatch(e -> e > 20);
        System.out.println(result);
    }

Find

image20200810082021259.png 如果需要找到某些数据,可以使用find相关方法,方法签名如下

    Optional<T> findAny();
    Optional<T> findFirst();
public void testFind(){
        List<Integer> one = new ArrayList<>();
        Collections.addAll(one ,33,22,11,44,38,16,29);
        Optional<Integer> first = one.stream().findFirst();
        Optional<Integer> any = one.stream().findAny();
        System.out.println(first.get());
    }

max 和 min

image20200811072622300.png 如果需要获取最大和最小值,可以使用max和min方法,方法签名如下:

Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);

基本使用

stream流中的minmax相关方法基本使用代码如下:

   public void testMinAndMax() {
        List<Integer> one = new ArrayList<>();
        Collections.addAll(one ,33,22,11,44,38,16,29);
        Optional<Integer> max = one.stream().max((o1,o2)->o1-o2);
        Optional<Integer> min = one.stream().min((o1,o2)->o1-o2);
        System.out.println(max.get());
        System.out.println(min.get());
    }

reduce

image20200811073216303.png reduction 操作(也称为fold),通过反复的对一个输入序列的元素进行某种组合操作(如对数的集合求和、求最大值,或者将所有元素放入一个列表),最终将其组合为一个单一的概要信息。stream类包含多种形式的通用reduction操作,如reduce和collect,以及其他多种专用reduction形式:sum,max或者count。 方法签名如下:

    Optional<T> reduce(BinaryOperator<T> accumulator);
 		<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);
    T reduce(T identity, BinaryOperator<T> accumulator);

Stream 流中的reduce相关方法基本使用的代码如下:

public void testReduce() {
        List<Integer> one = new ArrayList<>();
        Collections.addAll(one ,4,5,3,9);
        // T reduce(T identity, BinaryOperator<T> accumulator);
        // T identity 默认值
        // BinaryOperator<T> accumulator: 对数据进行处理的方式
        // reduce 如何执行?
        //第一步 将默认值赋值给x,取出集合第一个值赋值给y A1=x+y1
        //第二步 将上一步的结果A1赋值给x,取几个第二个元素赋值给y A2=A1+y2
        //以此类推 ...
        Integer reduce = one.stream().reduce(0, (x, y) -> {
            // 打印具体执行过程
            System.out.println("x="+x+",y="+y);
            return x + y;
        });
        System.out.println("reduce="+reduce);

        //reduce可以用来获取最大值
        Integer reduce1 = one.stream().reduce(0, (x, y) -> {
            return x > y ? x : y;
        });
        System.out.println("reduce1="+reduce1);
    }

执行过程入下图所示: image20200811074004192.png

map和reduce组合使用

public void testMapReduce() {
        Stream<Person> personStream = Stream.of(
                new Person("貂蝉", 18),
                new Person("王昭君", 20),
                new Person("西施", 19),
                new Person("杨玉环", 20));
        //求出所有年龄的总和
        Integer sum = personStream.map(e ->
                e.getAge()
        ).reduce(0, (x, y) -> {
            return x + y;
        });
        Integer sum2 = personStream.map(e ->
                e.getAge()
        ).reduce(0, Integer::sum);

        //找出做大年龄
        Integer max1 = personStream.map(e ->
                e.getAge()
        ).reduce(0, (x, y) -> {
            return x > y ? x : y;
        });
        Integer max2 = personStream.map(e ->
                e.getAge()
        ).reduce(0, Math::max);

        //统计A出现的次数
        Integer count = Stream.of("a", "a", "b", "c", "d", "a").map(s -> {
            if (s == "a") {
                return 1;
            } else {
                return 0;
            }
        }).reduce(0, Integer::sum);
    }

mapToInt

如果需要将Stream<Integer> 中的integer类型数据转换成int类型,可以使用mapToInt方法,方法签名如下:

    IntStream mapToInt(ToIntFunction<? super T> mapper);

image20200811080823917.png Stream流中的mapToInt 相关方法基本使用代码如下:

 public void testNumericStream(){
        //Integer占用的内存不int多,在stream流操作中会自动装箱拆箱
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
        //把大于3的大于出来
        integerStream.filter(e->e>3).forEach(System.out::println);
        //IntStream mapToInt(ToIntFunction<? super T> mapper);
        //IntStream 操作的是int类型的数据,就节省内存,减少自动装箱拆箱
        IntStream intStream1 = integerStream.mapToInt(e -> {
            return e.intValue();
        });
        
        IntStream intStream2 = integerStream.mapToInt(Integer::intValue);
        intStream2.filter(e->e>3).forEach(System.out::println);
    }

concat

如果有两个流,希望合并成一个流,可以使用stream接口的静态方法concat:

    public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b);

备注: 这是一个静态方法,与java.lang.String 当中的concat方法不同

该方法的基本使用代码如下:

public void testConcat(){
        Stream<Integer> stream1= Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream2 = Stream.of(6, 4, 7, 8, 9);

        //合并和不能操作之前的流
        //合并后,元素可以重复
        Stream<Integer> concat = Stream.concat(stream1, stream2);
        concat.forEach(e->{
            System.out.println(e);
        });
    }