一文让你掌握Stream流,还是那么自信

课程内容

1>Stream流

2>Stream流方法使用

教学目标

1>能够理解流与集合相比的优点

2>能够理解流的延迟执行特点

3>能够通过集合、映射或数组获取流

4>能够掌握常用的流操作

Stream流

概念

Stream流并不是Java 中IO流,而是Java8 之后新引入的一种数据流操作API,它的出现得益于Lambda函数编程方式引入,其目的是解决集合类库操作短板,让集合操作更加优雅。

集合短板

JDK8之前,集合操作的短板在于集合中元素处理模式较为死板,甚至过于僵化,当需要对集合元素进行灵活处理时,需要没完没了的遍历,上个例子喵喵

需求:花名册中找出数学100分学生

public class Student {
    private String name;
    private int math;
    private int english;
    
    public Student(String name, int math, int english){
        this.name = name;
        this.math = math;
        this.english = english;
    }
    //省略get/set...
} 
public class App {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student("dafei", 100, 100));
        list.add(new Student("xiaofei", 900, 100));
        list.add(new Student("zhongfei", 100, 80));
        list.add(new Student("laofei", 100, 100));
        list.add(new Student("feifei", 60, 70));
        
        List<Student> mathList = new ArrayList<>();
        for (Student student : list) {
            if(student.getMath() == 100){
                mathList.add(student);
            }
        }        
    }
}

需求:花名册中找出数学100分,并且英语也100分的学生

public class App {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student("dafei", 100, 100));
        list.add(new Student("xiaofei", 900, 100));
        list.add(new Student("zhongfei", 100, 80));
        list.add(new Student("laofei", 100, 100));
        list.add(new Student("feifei", 60, 70));

        List<Student> mathList = new ArrayList<>();
        for (Student student : list) {
            if(student.getMath() == 100){
                mathList.add(student);
            }
        }
		//在第一个集合的基础上在做一次遍历
        List<Student> allList = new ArrayList<>();
        for (Student student : mathList) {
            if(student.getEnglish() == 100){
                allList.add(student);
            }
        }
        
    }
}

上面例子,list集合元素操作,避免不了遍历,甚至要多次遍历,这种结构代码一堆积,怎么看都不雅观。此时可以进一步思考,在集合操作中,我们在意的是集合遍历本身呢,还是集合中元素处理逻辑呢?答案很明显,那能不能忽略集合的遍历,突出元素的逻辑处理呢?JDK8的Stream流处理就是为了解决这种问题的。

Stream流将集合中的外部循环转换成内部循环,让使用者更关注集合元素本身处理逻辑

需求:花名册中找出数学100分,并且英语也100分的学生

public class App {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student("dafei", 100, 100));
        list.add(new Student("xiaofei", 900, 100));
        list.add(new Student("zhongfei", 100, 80));
        list.add(new Student("laofei", 100, 100));
        list.add(new Student("feifei", 60, 70));


        List<Student> allList = list.stream()
                //过滤数学100的学生
                .filter(student -> student.getMath() == 100)
                //过滤英语100的学生
                .filter(student -> student.getEnglish() == 100)
                //汇总成集合
                .collect(Collectors.toList());

    }
}

上面代码与之前代码等价,但代码更加简洁啦,只需关注filter中的元素过滤逻辑即可。

流概念

Stream流有点类似现实生活中的快递运输带,或者工厂流水线上的履带。

 

Stream并不是集合,本身不存储任何元素,它是集合元素的函数模型,用于加工元素的载具。你可以设想一种场景:工厂中有一条流水线线,产品从这一头传送到另外一头,此时在流程线上加上产品喷漆机器,然后再加上产品贴标机器,最后加上产品装箱机器等等,一个流程下来,就完成产品加工过程。

结合上面例子能悟出Stream流的功能么?

Stream核心功能:1>数据输入 2>数据加工 3>数据产出

//list数据输入
List<Student> allList = list.stream()
    //数据加工
    .filter(student -> student.getMath() == 100)
    //数据产出
    .collect(Collectors.toList());

Stream操作细节

Stream流是一个来自数据源(list/数组)的元素队列,下面是它具体操作细节

1>Stream流中元素都是特定的数据烈性,也就是泛型一致

2>Stream流不存储元素,仅仅为数据提供操作平台

3>Stream流的操作数据来自集合或者数组

4>Stream流使用Pipeling管道操作模式,允许对Stream流中数据多次加工,每次加工结束都返回流对象本身,也即支持链式编程,需要注意,每次加工一次返回的流都是新流对象

5>Stream流使用内部遍历方式,区分与集合那种外部遍历。

6>Stream流操作步骤:获取(接入)数据源-->数据转换(数据加工)-->返回结果(数据产出)

获取Stream流

java.util.stream.Stream 是Java 8新加入的流接口。常用的方式:

1> Collection 集合使用 stream 默认方法获取流;

2>Stream 接口的静态方法 of 可以获取数组流。

Collection集合方式


List<String> list = new ArrayList<>();
Stream<String> listStream = list.stream();

Set<String> set  = new HashSet<>();
Stream<String> setStream = set.stream();

Map<String, String> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();

数组的方式

String[] array = new String[]{"a","b","c","d"};
Stream<String> arrayStream1 = Stream.of("a", "b", "c", "d");
Stream<String> arrayStream2 = Stream.of(array);

Stream流常用方法

Stream流常用的方法分为2种:

1>数据加工型方法。 加工方法返回类型还是Stream类型,可以使用链式方式调用。

2>终结型方法,返回对象值不在是Stream类型, 根据方法选择返回不同数据。

forEach

遍历,逐一迭代Stream流中数据,此方法为终结方法

void forEach(Consumer<? super T> action);

方法需要传入Consumer消费型接口,无返回

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

案例:遍历给定的集合

List<String> list = Arrays.asList("a", "b", "c", "d");
list.stream().forEarch(item->System.out.println(item))

filter

过滤,对数据进行过滤加工,返回新的子集流(加工之后流对象就不一样了),此方法为加工型方法

Stream<T> filter(Predicate<? super T> predicate);

 方法需要传入Predicate判断型接口,通过返回值true/false对流数据进行筛选

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

案例:挑选后缀名为png的文件

String[] files = {"a.txt", "b.png", "c.avi"};
Stream<String> stream = Stream.of(files).filter(item->item.endsWith("png"));

map

映射,对数据进行映射加工,功能:将元素A转换成元素B格式,此方法为加工型方法

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

方法需要传入Function转换型接口,转换后返回转换后的类型对象的流

需求:从学生对象集合中返回学生名称

List<Student> list = new ArrayList<>();
list.add(new Student("dafei", 100, 100));
list.add(new Student("xiaofei", 900, 100));
list.add(new Student("zhongfei", 100, 80));
list.add(new Student("laofei", 100, 100));
list.add(new Student("feifei", 60, 70));

Stream<String> stream = list.stream().map(item -> item.getName());

flatMap

扁平化映射,将stream流中的每个元素转成流,然后将所有流进行汇总,得到新流,此方法为加工型方法

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

方法参数传入是转换型接口

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

从方法参数限制中可以看出, 返回的R是Stream流类型, T则是流中的元素。

需求:将给出的数组集合,合并成一个Integer类型集合

List<Integer[]> list = new ArrayList<Integer[]>();
Integer[] a = {1,2,3,4};
Integer[] b = {5,6,7,8};
Integer[] c = {9,10,11,12};

list.add(a);
list.add(b);
list.add(c);

//返回的流: 1,2,3,4,5,6,7,8,9,10,11,12
Stream<Integer> stream = list.stream().flatMap(item->Stream.of(item));

从代码上分析,flatMap传入Function函数接口对象, 后续肯定调用apply方法,参数是item,也就是list集合中Integer[] 数组, 然后执行Stream.of(item)方法,表示将数组转化成Stream流,最后将所有的Stream流再合并成最终的Stream<Integer>流

等价于:

Stream<Integer> s = Stream.of();
for (Integer[] itemOut : list) {
    //lambda转化函数对象,逻辑:将Integer[]  -->Stream<Integer>
    Function<Integer[], Stream<Integer>> function = item->Stream.of(item);
    //list集合中每个元素都转化成:Stream<Integer>
    Stream<Integer> st = function.apply(itemOut);
    //将所有的流拼接成一个流
    s = Stream.concat(s, st);
}

distinct

去重,将Stream流中重复的元素排除掉,判断重复的标准是:hashCode与equals方法,此方法是加工型方法

Stream<T> distinct();

需求:对给定的List集合进行排重

List<Integer> ll = Arrays.asList(1,2,3,4,4,3,2,1);
Stream<Integer> ss = ll.stream().distinct();
ss.forEach(System.out::println);

count

统计,对经过Stream流的数据进行统计,求个数,此方法为终结型方法

long count();

方法无返回值,执行后返回long类型数据

需求:求学生总数

List<Student> list = new ArrayList<>();
long count = stream.count();

limit

限制,或者叫截取, 对流进行截断,取前面几个数据形成新流,此方法为加工型方法

Stream<T> limit(long maxSize);

方法传入需要截取的元素个数,然后返回新的子流

需求:查询数据成绩在前面3的学生

public class Student {
    private String name;
    private int math;
    private int english;
}
List<Student> list = new ArrayList<>();
list.add(new Student("dafei", 100, 100));
list.add(new Student("xiaofei", 90, 100));
list.add(new Student("zhongfei", 100, 80));
list.add(new Student("laofei", 100, 100));
list.add(new Student("feifei", 60, 70));

Collections.sort(list, (o1,o2)->o2.getMath()-o1.getMath());
Stream<Student> limit = list.stream().limit(3);
limit.forEach(System.out::println);

skip

跳过,对Stream流中的数据执行跳过处理,此方法为加工型方法

Stream<T> skip(long n);

需求:遍历list集合,跳过前面3个数据

List<Integer> ll = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Stream<Integer> ss = ll.stream().skip(3);
ss.forEach(System.out::println); //4,5,6,7,8,9,10

sorted

第一种排序:对Stream流中数据进行自然排序,此方法为加工型方法

Stream<T> sorted();

需求:遍历list集合,先排重,然后按自然顺序排序

List<Integer> ll = Arrays.asList(3,8,9,4,7,5,8,3,7,4,8,9,5,0,0,7,1,2,3,8,9,4);
Stream<Integer> ss = ll.stream().distinct().sorted();
ss.forEach(System.out::println);

第二种排序:对Stream流中数据按照指定的规则进行排序,此方法为加工型方法

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

参数是一个Comparator比较器接口, compare方法的返回值决定元素排序顺序

返回值大于0表示大于

返回值小于0表示小于

返回值等于0表示等于

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

需求:遍历list集合,先排重,然后倒序排

List<Integer> ll = Arrays.asList(3,8,9,4,7,5,8,3,7,4,8,9,5,0,0,7,1,2,3,8,9,4);
Stream<Integer> ss = ll.stream().distinct().sorted((o1,o2)->o2-o1);
ss.forEach(System.out::println);

max/min

查最大/最小值,返回Optional对象,此方法为终结型方法

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

方法传入的参数为比较器,根据比较器定制判定规则。

需求:查询数学成绩最高的学生与英语成绩最低的学生

List<Student> list = new ArrayList<>();
list.add(new Student("dafei", 100, 100));
list.add(new Student("xiaofei", 90, 100));
list.add(new Student("zhongfei", 100, 80));
list.add(new Student("laofei", 100, 100));
list.add(new Student("feifei", 60, 70));

Optional<Student> max = list.stream().max((o1, o2) -> o2.getMath() - o1.getMath());
System.out.println(max.get());
Optional<Student> min = list.stream().max((o1, o2) -> o1.getEnglish() - o2.getEnglish());
System.out.println(min.get());

o1-o2 为正序比较,求最小值, o2-o1是逆序比较,求最大值

allMatch/anyMatch/noneMatch

数据匹配

allMatch:判断流中数据是否都满足指定的条件(所有都要)

anyMatch:判断流中数据是否存在满足指定条件(至少一个)

noneMatch:判断流中数据是否都不满足指定条件

上面方法都是终结型方法

boolean allMatch(Predicate<? super T> predicate);
boolean anyMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);

方法参数使用Predicate判断型接口

需求:判断集合中数据是否都大于3

需求:判断集合种数据是否存在一个大于3

需求:判断集合种数据是否都不等于3

List<Integer> list = Arrays.asList(1,2,3,4,5,6);
boolean ret = list.stream().allMatch(item -> item > 3);  //false
boolean ret2 = list.stream().anyMatch(item -> item > 3);  //true
boolean ret3 = list.stream().noneMatch(item -> item == 3);  //false

findFirst/findAny

查找

findFirst:查找流中第一个元素,此方法为终结型方法

findAny:查找流中任意一个元素,此方法为终结型方法

Optional<T> findFirst();
Optional<T> findAny();

需求:查询流中第一个元素

Optional<Integer> first = list.stream().findFirst();
Optional<Integer> any = list.stream().findAny();

concat

连接,流连接,将2个流连接成一个新的流,此方法为加工型方法

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

注意,该方法是Stream接口的静态方法

需求:将集合1跟集合2合并

List<Integer> listA = Arrays.asList(1,2,3,4,5,6);
List<Integer> listB = Arrays.asList(7,8,9,10,11);
Stream<Integer> concat = Stream.concat(listA.stream(), listB.stream());

collect

汇总,也可以理解为流转换,将流转换成指定类型数据,此方法为终结型方法

<R, A> R collect(Collector<? super T, A, R> collector);
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);

 

collect:将流转换为其他形式:list

collect:将流转换为其他形式:set

collect:将流转换为其他形式:map

collect:将流转换为其他形式:sum

collect:将流转换为其他形式:avg

collect:将流转换为其他形式:max

collect:将流转换为其他形式:min

List<Integer> listA = Arrays.asList(1,2,3,4,5,6);

List<Integer> list = listA.stream().collect(Collectors.toList());
Set<Integer> set = listA.stream().collect(Collectors.toSet());
Map<Integer, Integer> map = listA.stream().collect(Collectors.toMap(k->k, v->v));
int sum = listA.stream().collect(Collectors.summingInt(item -> item.intValue()));
double avg = listA.stream().collect(Collectors.averagingDouble(item -> item.doubleValue()));
int  max = listA.stream().collect(Collectors.maxBy((o1, o2)->o2-o1)).get();
int  min = listA.stream().collect(Collectors.minBy((o1, o2)->o1-o2)).get();

上面方法最核心逻辑还是Collectors 工具类, 可以将各种常用的逻辑转换成可以输出的收集对象:Collector,再由收集对象对流进行加工成成像样模式。

好到这,Stream流的基本操作就差不多了。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

浪飞yes

我对钱没兴趣~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值