JDK8新特性–Stream流

Stream流

前言

Stream流初体验

什么是Stream流呢?

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算。
  • 数据源:流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作:类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

来个小案例,初步体验一下Stream流

package com.features.Stream;

// JDK8新特性 --- Stream
// Stream流来处理集合类的问题

import java.util.Arrays;
import java.util.List;

// 打印所有姓张的且名字是三位数的所有人的信息
// 打印所有姓李的且名字是两位数的所有人的信息
public class StreamTest02 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("张三", "李四", "李五", "张飒飒");
        System.out.println("姓张的且名字是三位数的人:");
        list.stream()
                .filter(s -> s.startsWith("张"))
                .filter(s -> s.length() == 3)
                .forEach(System.out::println);
        System.out.println("姓李的且名字是两位数的人:");
        list.stream()
                .filter(s -> s.startsWith("李"))
                .filter(s -> s.length()==2)
                .forEach(System.out::println);
    }
}

复制代码

结果:

姓张的且名字是三位数的人:
张飒飒
姓李的且名字是两位数的人:
李四
李五
复制代码

Stream流采用链式写法,其中:

  • stream 获取流
  • filter 过滤
  • forEach 打印

我们点进去Stream的源码可以发现,它包含了以下这些方法,Stream流的操作和这些方法密切相关

image.png

image.png

当然,在使用Stream流之前,我们要先学会怎么去获取它

Stream流的获取

Stream的获取方式:通过collection接口获取

    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
复制代码

因为java.util.Collection接口中加入了default方法stream,也就是说所有的实现了Collection接口下的类都可以通过steam方法来获取Stream流。

public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.stream();
        Set<String> set = new HashSet<>();
        set.stream();
        Vector<String> vector = new Vector<>();
        vector.stream();
        Map map = new HashMap<String,Integer>();
        // map.stream(); 报错
        // map 没有实现collection接口,但是它可以通过下面这种方式来获取对应的key,value集合
        map.keySet().stream(); // key集合
        map.values().stream(); // value集合
        map.entrySet().stream(); // entry集合
        Hashtable<String, Integer> hashtable = new Hashtable<>();
        //hashtable.stream();  报错
        // hashtable和map同理
        hashtable.keySet().stream(); // key集合
        hashtable.values().stream(); // value集合
        hashtable.entrySet().stream(); // entry集合
复制代码

注意:

  1. list,set,vector实现了collection接口,map和hashtable没有
  2. map和hashtable要想获取Stream流,就必须把key和value分开,就可以得到对应的Stream流

Stream的获取方式:通过Stream的of方法

    @SafeVarargs
    @SuppressWarnings("varargs") // Creating a stream from an array is safe
    public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }
复制代码

在实际开发中我们不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of

    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("1", "2", "3", "4", "5");
        System.out.println("String类型的数组:");
        stream1.forEach(System.out::print);
        String[] s = {"a","b","c","d","e"};
        Stream<String> stream2 = Stream.of(s);
        System.out.println("\n"+"String类型的数组:");
        stream2.forEach(System.out::print);
        Integer[] i = {1,2,3,4,5,6,7};
        Stream<Integer> stream3 = Stream.of(i);
        System.out.println("\n"+"Integer类型的数组:");
        stream3.forEach(System.out::print);
        int[] a = {1,2,3,4,5,6,7};
        Stream<int[]> stream4 = Stream.of(a);
        System.out.println("\n"+"int类型的数组:");
        // 得到异常结果 --> 基本数据类型的数组不能正确获取到stream流
        stream4.forEach(System.out::print);
    }
复制代码

注意:基本数据类型的数组不能正确获取到stream流

学会了如何获取Stream流,我们现在来看一下他们的用法

Stream流的方法详解

forEach

遍历流中的数据

 void forEach(Consumer<? super T> action);
复制代码

使用:

    public static void main(String[] args) {
        Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
                .forEach(System.out::println);
    }
复制代码

结果:

a
b
c
d
a
b
a
c
复制代码

filter

用来过滤数据,返回符合条件的数据。通过filter方法将一个流转换成另一个子集流

 Stream<T> filter(Predicate<? super T> predicate);
复制代码

使用:

    public static void main(String[] args) {
        Stream<String> stream = Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
                .filter(s -> !s.equals("a"));
        stream.forEach(System.out::println);
    }
复制代码

结果:

b
c
d
b
c
复制代码

limit

对流进行截取处理,截取前n个数据

Stream<T> limit(long maxSize);
复制代码

使用:

public static void main(String[] args) {
        Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
                .limit(5)
                //.limit(10)
                //.limit(-1) 报错
                .forEach(System.out::println);
    }
复制代码

结果:

a
b
c
d
a
复制代码

注意:

  • 当n大于等于流中的数据个数时,截取全部数据。
  • 当n小于0时,报错

skip

跳过前面n个元素,进行截取

Stream<T> skip(long n);
复制代码

使用:

public static void main(String[] args) {
        Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
                .skip(5)
                //.skip(-1) 报错
                //.skip(10)
                .forEach(System.out::println);
    }
复制代码

结果:

b
a
c
复制代码

注意:

  • 当n大于等于流中的数据个数时,没有数据被截取
  • 当n小于0时,报错

count

用来计算元素的个数,返回一个long类型的值,代表元素的个数

long count();
复制代码

使用:

    public static void main(String[] args) {
        long count = Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
                .count();
        System.out.println("元素的个数为:"+count);
    }
复制代码

结果:

元素的个数为:8
复制代码

concat

将两个流合并成一个流

public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
        Objects.requireNonNull(a);
        Objects.requireNonNull(b);

        @SuppressWarnings("unchecked")
        Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
                (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
        Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
        return stream.onClose(Streams.composedClose(a, b));
    }
复制代码

使用:

public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("a", "b", "c");
        Stream<String> stream2 = Stream.of("1", "2", "3");
        Stream.concat(stream1,stream2)
                .forEach(System.out::println);
    }
复制代码

结果:

a
b
c
1
2
3
复制代码

distinct

除去重复的元素

Stream<T> distinct();
复制代码

使用:

 public static void main(String[] args) {
        Stream<String> distinct = Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
                .distinct();
        distinct.forEach(System.out::println);

        // 需要在Student类中重写equals和hashcode方法,才能去掉重复的数据
        Stream.of(new Student("张三",18),
                new Student("李四",20),
                new Student("张三",18),
                new Student("王五",22),
                new Student("李四",20))
                .distinct()
                .forEach(System.out::println);
    }
复制代码

结果:

a
b
c
d
Student{name='张三', age=18}
Student{name='李四', age=20}
Student{name='王五', age=22}
复制代码

注意:

  • 基本数据类型的数据,可以直接去重
  • 如果是引用类型的,需要重写equals和hashcode方法

如这一题中的Student类为:

public class Student {
    private String name;
    private int age;

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    
    // 重写了equals和hashcode方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }


}

复制代码

find

找到指定元素

 Optional<T> findFirst();//找到第一个元素
 Optional<T> findAny();//返回的元素是不确定的,对于同一个列表多次调用findAny()有可能会返回不同的值。使用findAny()是为了更高效的性能。如果是数据较少,串行地情况下,一般会返回第一个结果,如果是并行的情况,那就不能确保是第一个
复制代码

使用:

 public static void main(String[] args) {
        Optional<String> first = Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
                .findFirst();
        System.out.println(first.get());
        Optional<String> any = Stream.of("a", "b", "c", "d", "a", "b", "a", "c")
                .findAny();
        System.out.println(any.get());
    }
复制代码

结果:

a
a
复制代码

max,min

max:获取最大值

Optional<T> max(Comparator<? super T> comparator);
复制代码

min:获取最小值

Optional<T> min(Comparator<? super T> comparator);
复制代码

使用:

    public static void main(String[] args) {
        Optional<Integer> max = Stream.of("1", "2", "3", "4", "5")
                .map(Integer::parseInt)
                .max(Comparator.comparingInt(o -> o));
                // .max((o1,o2)->o1-o2);等价
        System.out.println("max = "+max.get());
        Optional<Integer> min = Stream.of("1", "2", "3", "4", "5")
                .map(Integer::parseInt)
                .min((o1,o2)->o1-o2);
        System.out.println("min = "+min.get());
    }
复制代码

结果:

max = 5
min = 1
复制代码

map

将流中的元素映射到另一个流中,该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的数据

<R> Stream<R> map(Function<? super T, ? extends R> mapper);
复制代码

使用:

 public static void main(String[] args) {
        Stream.of("1", "2", "3", "4", "5")
                .map(Integer::parseInt)
                .forEach(System.out::println);
        Stream.of(6,7,8,9,10)
                .map(String::valueOf)
                .forEach(System.out::println);
    }
复制代码

结果:

1
2
3
4
5
6
7
8
9
10
复制代码

reduce

将所有数据归纳得到一个数据

 T reduce(T identity, BinaryOperator<T> accumulator);
复制代码

使用:

public static void main(String[] args) {
        Integer sum = Stream.of("1", "2", "3", "4", "5","1","1","2")
                .map(Integer::parseInt)
                // identity:默认值
                // 第一次的时候会将默认值赋值给a
                // 之后每次都会将上次的结果赋值给a,b接收流中的数据
                .reduce(0, (a, b) -> {
                    System.out.print("a = " + a);
                    System.out.println(" b = " + b);
                    return a + b;
                });
        System.out.println("sum = " + sum);
        // 获取最大值:
        Integer max = Stream.of("1", "2", "3", "4", "5")
                .map(Integer::parseInt)
                .reduce(0, (a, b) -> a > b ? a : b);
                //.reduce(1, Math::max);
        System.out.println("max = "+max);
        // 统计 1 出现的次数
        Integer count = Stream.of("1", "2", "3", "4", "5", "1", "1", "2")
                .map(s -> "1".equals(s) ? 1 : 0)
                .reduce(0, Integer::sum);
        System.out.println(count);
    }
复制代码

结果:

a = 0 b = 1
a = 1 b = 2
a = 3 b = 3
a = 6 b = 4
a = 10 b = 5
a = 15 b = 1
a = 16 b = 1
a = 17 b = 2
sum = 19
max = 5
3
复制代码

maptoInt

将流中的Integer类型转换为int类型

IntStream mapToInt(ToIntFunction<? super T> mapper);
复制代码

使用:

    public static void main(String[] args) {
        Integer[] a = {1,2,6,4,2};
        // Integer类型占用的内存比int多,流中会自动装箱和拆箱
        Stream.of(a)
                .distinct()
                .forEach(System.out::println);
        System.out.println("============");
        // 但是为了效率,我们先将Integer转换为int
        Stream.of(a)
                .mapToInt(Integer::intValue)
                .distinct()
                .forEach(System.out::println);
    }
复制代码

结果:

1
2
6
4
============
1
2
6
4
复制代码

match

判断数据是否符合匹配指定的条件 是一个终结方法

boolean allMatch(Predicate<? super T> predicate);//全部匹配才为true
boolean noneMatch(Predicate<? super T> predicate);//只要有一个匹配就为true
boolean anyMatch(Predicate<? super T> predicate);//取反操作,true->false,false->true
复制代码

使用:

public static void main(String[] args) {
        boolean b = Stream.of("1", "2", "3", "4", "5")
                .map(Integer::parseInt)
                .allMatch(s -> s > 0);
        System.out.println(b);
        boolean b1 = Stream.of("1", "2", "3", "4", "5")
                .map(Integer::parseInt)
                .anyMatch(s -> s > 4);
        System.out.println(b1);
        boolean b2 = Stream.of("1", "2", "3", "4", "5")
                .map(Integer::parseInt)
                .noneMatch(s->s>5);
        System.out.println(b2);
    }
复制代码

结果:

true
true
true
复制代码

到了这里,看到这么多方法,你可以有点晕了,迷糊了,没事,下面我们来一个综合案例,来综合使用一下这些方法

综合案例

定义两个集合,然后在集合中存储多个用户名称。然后完成如下的操作:

  1. 第一个队伍只保留姓名长度为3的成员
  2. 第一个队伍筛选之后只要前3个人
  3. 第二个队伍只要姓张的成员
  4. 第二个队伍筛选之后不要前两个人
  5. 将两个队伍合并为一个队伍
  6. 根据姓名创建Student对象
  7. 打印整个队伍的Student信息

实现:

    public static void main(String[] args) {
        List<String> list1 = Arrays.asList("张无忌","张三丰","金毛狮王","虚竹","段誉");
        List<String> list2 = Arrays.asList("杨过","郭靖","黄蓉","黄老邪","小龙女");
        Stream<String> stream1 = list1.stream()
                .filter(s -> s.length() == 3)//第一个队伍只保留姓名长度为3的成员
                .limit(3);//第一个队伍筛选之后只要前3个人
        Stream<String> stream2 = list2.stream()
                .filter(s -> s.startsWith("张"))//第二个队伍只要姓张的成员
                .skip(2);//第二个队伍筛选之后不要前两个人
        //将两个队伍合并为一个队伍
        Stream<String> stream = Stream.concat(stream1, stream2);
        stream.map(Student::new) // 根据姓名创建Student对象 (Student中要有一个只要name属性的构造器)
                .forEach(System.out::println);// 打印整个队伍的Student信息
    }
复制代码

结果:

Student{name='张无忌', age=0}
Student{name='张三丰', age=0}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享