Java 8:Stream流详细案例学习

Java 8:Stream流详细案例学习

前言

Stream流是jdk 1.8 引入的新特性,极大地方便了我们对集合的处理(特别是一些对于一些集合的查询操作)。而且减少了我们的代码量,而且在数据量大的时候支持并行处理提升效率,我们可以学习一下其中常用的方法和一些注意点以及原理。


常用方法

过滤方法

返回目录

1.1 filter(Predicate<? super T>):Stream

根据规则过滤元素,参数中返回true就会留在Stream流中。

public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A",23),
                new Person("B",22),
                new Person("C",28),
                new Person("D",25),
                new Person("E",13)
        );
        list.stream()
                .filter(person -> person.getAge() > 20)
                .forEach(person -> System.out.println(person));
    }
}
Person(name=A, age=23)
Person(name=B, age=22)
Person(name=C, age=28)
Person(name=D, age=25)

1.2 distinct():Stream

根据对象的equals方法去除重复元素

public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A",23),
                new Person("A",23),
                new Person("B",22),
                new Person("C",28),
                new Person("C",28),
                new Person("D",25),
                new Person("E",13)
        );
        list.stream()
                .distinct()
                .forEach(s -> System.out.println(s));
    }
}
Person(name=A, age=23)
Person(name=B, age=22)
Person(name=C, age=28)
Person(name=D, age=25)
Person(name=E, age=13)

1.3 limit(long maxSize):Stream

返回流中的n个元素,注意这个流是无序流还是有序流。

1.4 skip(long n):Stream

跳过流中的前n个元素,注意这个流是无序流还是有序流。

映射方法

返回目录

1.1 map(Function<? super T,? extends R>):Stream

给出一个对象T,然后根据逻辑返回对象R作为Stream流中的元素。

public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A",23),
                new Person("B",22),
                new Person("C",28),
                new Person("D",25),
                new Person("E",13)
        );
        list.stream()
                .map(Person::getName)
                .forEach(s -> System.out.println(s));
    }
}
A
B
C
D
E

排序方法

返回目录

1.1 sorted():Stream

需要元数据实现Comparable接口有着排序规则

public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A",23),
                new Person("B",22),
                new Person("C",28),
                new Person("C",28),
                new Person("D",25),
                new Person("E",13)
        );
        list.stream()
                .sorted()
                .forEach(s -> System.out.println(s));
    }
}

@Data
@Builder
class Person implements Comparable{
    private String name;
    private Integer age;

    @Override
    public int compareTo(Object o) {
        Person o1 = (Person) o;
        return this.getAge() - o1.getAge();
    }
}
Person(name=E, age=13)
Person(name=B, age=22)
Person(name=A, age=23)
Person(name=D, age=25)
Person(name=C, age=28)
Person(name=C, age=28)

1.2 sorted(Comparator<? super T> comparator):Stream

Stream流也提供了非常方便的使用方法,我们可以灵活的定义排序方法。

public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A",23),
                new Person("B",22),
                new Person("C",28),
                new Person("C",28),
                new Person("D",25),
                new Person("E",13)
        );
        list.stream()
                .sorted(Comparator.comparingInt(Person::getAge))
                .forEach(System.out::println);
    }
}
Person(name=E, age=13)
Person(name=B, age=22)
Person(name=A, age=23)
Person(name=D, age=25)
Person(name=C, age=28)
Person(name=C, age=28)

帮助Stream流调试方法

返回目录

1.1 peek(Consumer<? super T> action):Stream

这个方法主要用来调试Stream流,可以简单理解为只要流中遍历了一个元素我们就可以通过peek对这个元素进行操作(通常打印这个元素)

public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A", 23),
                new Person("B", 22),
                new Person("C", 28),
                new Person("C", 28),
                new Person("D", 25),
                new Person("E", 13)
        );
        list.stream()
                .peek(person -> System.out.println(person.getName().toUpperCase()))
                .peek(person -> System.out.println(person.getName().toLowerCase()))
                .peek(person -> System.out.println("-----------"))
                .count();
    }
}
A
a
-----------
B
b
-----------
C
c
-----------
C
c
-----------
D
d
-----------
E
e
-----------

遍历方法

返回目录

1.1 forEach(Consumer<? super T>):void

遍历但是不能保证遍历顺序,因为这个方法支持并行操作。

转换方法

返回目录

1.1 toArray():Object[]

直接返回一个Object数组

1.2 toArray(IntFunction<A[]> generator): A[]

返回一个指定对象的数组

public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A", 23),
                new Person("B", 22),
                new Person("C", 28),
                new Person("C", 28),
                new Person("D", 25),
                new Person("E", 13)
        );
        Person[] people = list.stream()
                .filter(person -> person.getAge() > 22)
                .toArray(Person[]::new);

        Stream.of(people).forEach(System.out::println);
    }
}
Person(name=A, age=23)
Person(name=C, age=28)
Person(name=C, age=28)
Person(name=D, age=25)

1.2 reduce(T identity, BinaryOperator accumulator):T

累加器,对流中的元素进行累加逻辑计算并且得出最终的结果

具体使用参考博客

1.3 collect(Supplier supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner): R

比较复杂的集合操作,三个参数的逻辑操作:

  • supplier:容器提供者,主要提供一个新的容器
  • accumulator:累加器,其中函数式方法中提供两个方法,一个是supplier提供的新容器,一个是Stream流中的元素,然后对Stream流中的元素进行过滤放到supplier提供的容器中
  • combiner:合并器,将累加器得到的元素进行二次过滤,然后放到一个新的容器中
public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A", 23),
                new Person("B", 22),
                new Person("C", 28),
                new Person("C", 28),
                new Person("D", 25),
                new Person("E", 13)
        );
         
    
        list.stream().collect(
                // 1. 创建新的容器一
                HashMap<String, List<Person>>::new,
                // 2. 向我们新的容器里面放置元素成为容器二
                (map1, person) -> {
                    String key = person.getName();
                    List<Person> pList = map1.getOrDefault(key, new ArrayList<>());
                    pList.add(person);
                    map1.put(key, pList);
                },
                // 3. 队容器二进行过滤然后放入新的容器三种
                (map2, map3) -> map3.putAll(map2)
        ).forEach((key, person) -> {
            System.out.println("Key: " + key);
            System.out.println("Value: " + person);
        });
    }
}
Key: A
Value: [Person(name=A, age=23)]
Key: B
Value: [Person(name=B, age=22)]
Key: C
Value: [Person(name=C, age=28), Person(name=C, age=28)]
Key: D
Value: [Person(name=D, age=25)]
Key: E
Value: [Person(name=E, age=13)]

1.4 collect(Collector<? super T, A, R> collector):<R, A> R

通常用作返回一个Collection,常常用来返回Map或者List

public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A", 23),
                new Person("B", 22),
                new Person("C", 28),
                new Person("C", 28),
                new Person("D", 25),
                new Person("E", 13)
        );

        // 直接返回List
        List<String> plist = list.stream().map(Person::getName)
                .collect(Collectors.toList());

        // 返回需要的Collection
        Set<String> set = list.stream().map(Person::getName)
                .collect(Collectors.toCollection(TreeSet::new));

        // 返回字符串
        String joined = list.stream()
                .map(Person::getName)
                .collect(Collectors.joining(", "));

        // 返回总和
        int total = list.stream()
                .collect(Collectors.summingInt(Person::getAge));

        // 返回一个分组Map
        Map<String, List<Person>> groupMap
                = list.stream()
                .collect(Collectors.groupingBy(Person::getName));

        // 返回一个分组Map,并且有其他逻辑分组操作
        Map<String, Integer> totalMap
                = list.stream()
                .collect(Collectors.groupingBy(Person::getName,
                        Collectors.summingInt(Person::getAge)));

        // 自己定义分组逻辑并且获得分组Map
        Map<Boolean, List<Person>> myLogicGroupMap =
                list.stream()
                        // 根据23岁分成两个组别
                        .collect(Collectors.partitioningBy(s -> s.getAge() >= 23));

    }
}

1.5 聚合函数 max() min() count()

判断匹配方法

返回目录

1.1 anyMatch(Predicate<? super T> predicate):boolean allMatch(Predicate<? super T> predicate):boolean

  • 只要元素中有一个元素满足条件就返回true
  • 需要所有元素满足条件就返回true
public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A", 23),
                new Person("B", 22),
                new Person("C", 28),
                new Person("C", 28),
                new Person("D", 25),
                new Person("E", 13)
        );
        // true
        boolean b = list.stream().anyMatch(person -> person.getAge() > 22);
        System.out.println(b);
        // false
        b = list.stream().allMatch(person -> person.getAge() > 22);
        System.out.println(b);
    }
}

构建生成Stream方法

返回目录

  • of(T …)
  • concat(Stream s1,Stream s2)
public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A", 23),
                new Person("B", 22),
                new Person("C", 28),
                new Person("C", 28),
                new Person("D", 25),
                new Person("E", 13)
        );

        Stream.concat(list.stream(), list.stream())
                .forEach(System.out::println);

        Stream.of(1, 2, 3, 4).forEach(System.out::println);
    }
}
Person(name=A, age=23)
Person(name=B, age=22)
Person(name=C, age=28)
Person(name=C, age=28)
Person(name=D, age=25)
Person(name=E, age=13)
Person(name=A, age=23)
Person(name=B, age=22)
Person(name=C, age=28)
Person(name=C, age=28)
Person(name=D, age=25)
Person(name=E, age=13)
1
2
3
4

Stream流的特殊特性

支持顺序和并行聚合操作

返回目录

1.1 例子

int sum = widgets.stream()
              .filter(w -> w.getColor() == RED)
              .mapToInt(w -> w.getWeight())
              .sum();

1.2 顺序操作

例子中先是根据顺序首先过滤完毕后得到结果集1,然后对结果集1进行转换得到结果集2,最后进行聚合得到求和结果集3.

1.3 并行聚合操作

在sum()函数中就是Stream流的聚合操作

流的懒加载特性

返回目录

Stream流的操作可以根据是否是终端操作分为两大类,流会根据这两种操作来决定是否执行流中元数据的处理。

  • 情况一:Stream流中没有终端方法执行,那么Stream流就不会执行

  • 情况二:Stream流中有终端方法执行,那么Stream流就会执行

流和集合之间的区别

返回目录

流和集合表面上看起来非常相似,但是两者关注的目标有所不同。集合更加关注高效的管理,比如我们直接可以通过集合对象list的get(n)方法直接获取其中指定位置的元素并且操作管理这个元素,但是stream对象就没有方法直接这么简单的获取数据源目标位置的元素,这里就体现了集合管理的更加高效。Stream流更加关注数据的查询,在查询和过滤转换上的操作来说,Stream的效率和代码量都体现出了巨大的优势,使用Stream就好像写文章,并且这个文章上下文练习非常紧密,这就是流之间的传递性。

以函数式接口作为流的方法需要数据源中不能有null值

返回目录

因为函数式接口往往需要指定一个对象然后进行处理,而Stream流中的元素就是直接对其中的元素进行处理,一旦数据源中出现了null元素,然后我们还使用了函数式接口对null元素进行了处理就会出现问题

public class StreamTest {
    @Test
    public void test01() {
        List<Person> list = Arrays.asList(
                new Person("A", 23),
                // 故意放入null元素
                null,
                null,
                new Person("C", 28),
                new Person("C", 28),
                new Person("D", 25),
                new Person("E", 13)
        );

        list.stream().
                // 这里map中的函数式几口会接收到null元素,会导致异常的发生
                map(Person::getAge).forEach(System.out::println);
    }
}
java.lang.NullPointerException
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
	at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)

流中资源的关闭

返回目录

  • 由数组、集合产生的流并不需要我们自己关闭,Stream流中继承了AutoCloseable接口会自动关闭资源流
  • 由I/O产生的流需要我们进行特殊的关闭,我们可以将资源声明在 try-with-resource中声明

流可以设置顺序或者并行执行

返回目录

流管道可以依次或者在执行平行 。 该执行模式是流的属性。 流与顺序或并行执行的最初的选择创建。 (例如, Collection.stream()创建了一个连续的流,并且Collection.parallelStream()创建了一个并联的一个。)的执行模式的这种选择可以通过被修改sequential()或parallel()方法,并且可以与被查询所述isParallel()方法。

并行流解析

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。串行流则相反,并行流的底层其实就是ForkJoin框架的一个实现。


小结

  • 如果对集合的操作仅仅只是查询过滤其中的元素,那么推荐使用Stream流,不仅减少了代码量而且提升了效率
  • 并行流的使用需要注意
    • 留意装箱。自动装箱和拆箱操作会大大降低性能。Java8中有原始类型流(IntStream、LongStream、DoubleStream)来避免这种操作
    • 有些操作本身在并行流上的性能就比顺序流差。特别是limit、findFirst等依赖于元素顺序的操作,它们在并行流上执行的代价非常大。
    • 在大数量数据的处理才去使用并行流,否则不要去使用
  • Stream流中关系到函数式接口作为参数的方法,需要注意数据源是否有null对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值