【Java学习笔记十四】——走入Stream API

声明:本文章内容主要摘选自尚硅谷宋红康Java教程、廖雪峰Java教程,示例代码部分出自本人,更多详细内容推荐直接观看以上教程及书籍,若有错误之处请指出,欢迎交流。

一、概念

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

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

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

2.应用场景

在需要用到某些操作时(求和、去重、按照要求过滤等),直接使用已经封装好的Stream API进行实现,不需要我们自己写代码实现,Stream API可以极大提高生产力,让我们写出高效率、干净、简洁的代码。

二、创建Stream

  • Stream 自己不会存储元素。
  • Stream 不会改变源对象,相反,他们会返回一个持有结果的新Stream。
  • Stream 操作是延迟执行的。这意味者他们会等到需要结果的时保才执行。

1.通过Stream的of()

//直接用Stream.of()静态方法,传入可变参数即创建了一个能输出确定元素的Stream:
Stream<Integer> stream = Stream.of(2,4,6,8);
//Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。
stream.forEach(System.out::println);

2.通过数组或集合

//基于一个数组或者集合创建,这样该Stream输出的元素就是数组或者集合持有的元素:

//调用Arrays类的 Stream<T> stream(T[] array) :返回一个流
int[] arr1 = {1,2,3,4,5};
IntStream stream1 = Arrays.stream(arr1);

//Stream<E> stream() : 返回一个顺序流
List<String>  list = Arrays.asList("AA", "BB", "CC", "DD"); 
Stream<String> stream2 = list.stream();//通过集合创建
//Stream<E> parallelStream() :返回一个并行流
Stream<String> stream3 = list.parallelStream();

3.通过Supplier(无限流)

创建Stream还可以通过Stream.generate()方法,它需要传入一个Supplier对象:

基于Supplier创建的Stream会不断调用Supplier.get()方法来不断产生下一个元素,这种Stream保存的不是元素,而是算法,它可以用来表示无限序列。

import java.util.stream.Stream;
import java.util.function.*;
public class StreamTest1{
    public static void main(String[] args){
        	//生成自然数
            Stream<Integer> natual = Stream. generate(new NatualSupplier());
        	//注意:无限序列必须先变成有限序列再打印:
            natual.limit(20).forEach(System.out::println);
        }
        
	//迭代:遍历前10个偶数(使用Lambda表达式)
	Stream.iterate(0, t ->t + 2).limit(10).forEach(System.out::println);

	//生成:生成随机数
	Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
class NatualSupplier implements Supplier<Integer> {
            int n = 0;
            public Integer get(){
                n++;
                return n;
            }
}

上述代码我们用一个Supplier<Integer>模拟了一个无限序列(当然受int范围限制不是真的无限大)。如果用List表示,即便在int范围内,也会占用巨大的内存,而Stream几乎不占用空间,因为每个元素都是实时计算出来的,用的时候再算。

对于无限序列,如果直接调用forEach()或者count()这些最终求值操作,会进入死循环,因为永远无法计算完这个序列,所以正确的方法是先把无限序列变成有限序列,例如,用limit()方法可以截取前面若干个元素,这样就变成了一个有限序列,对这个有限序列调用forEach()或者count()操作就没有问题。

三、执行中间操作

1.筛选与切片

public class StreamTest2{
    public static void main(String[] args){
        List<Employee> employees = EmployeeData.getEmployees();//Employee是我们自己定义的员工类,EmployeeData为数据源,详细请看帖子最底部的代码

        //filter(Predicate p) : 接收Lambda, 通过设置的条件从流中过滤出元素。
        Stream<Employee> stream1 = employees.stream();
        //查询员工表中年龄大于30的员工信息
        System.out.println("给定条件筛选元素:");
        stream1.filter(e -> e.getAge() > 30).forEach(System.out::println);

        //limit(n) : 截断流,使其元素不超过给定数量
        System.out.println("截断指定数量的元素");
        employees.stream().limit(3).forEach(System.out::println);

        //skip(n) :跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
        System.out.println("跳过元素:");
        employees.stream().skip(3).forEach(System.out::println);
        
        //distinct() :筛选,通过流新生成元素的hashCode()和equals()去除重复元素
        System.out.println("去重:");
        Stream<String> list2 = Arrays.asList("a","a","b","c","c","d","a").stream();
        list2.distinct().forEach(System.out::println);
    }
    

2.映射

Stream.map()Stream最常用的一个转换方法,它把一个Stream转换为另一个Stream

所谓map操作,就是接收一个函数作为参数,将元素转换为其他形式或提取信息,该函数会被应用到每一个元素上,并将其映射成一个流。例如,对x计算它的平方,可以使用函数f(x) = x * x。我们把这个函数映射到一个序列1,2,3,4,5上,就得到了另一个序列1,4,9,16,25:

Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s.map(n -> n * n);

flatMap(Function f): 接收一个函数作为参数,将流中的每个值都换成另外一个流,然后把所有流连接成一个流

public class StreamTest2{
    public static void main(String[] args){
//将一个流嵌入另外一个流,即作为另外一个流的一个元素
        Stream<Stream<Character>> streamStream = list.stream().map(StreamTest2::fromStringToStream);
        streamStream.forEach(s ->{
            s.forEach(System.out::println); //2层遍历方可遍历到作为元素的流的元素(即”AA","BB"等)
        });

        //使用flatMap(Function f)
        Stream<Character> characterStream = list.stream().flatMap(StreamTest2::fromStringToStream);
        characterStream.forEach(System.out::println);//直接输出集合中的集合元素的元素,无须再进行2层遍历
	//将字符串中的多个字符构成的集合转化为相应的Stream的实例
        public static Stream<Character> fromStringToStream(String str){
            ArrayList<Character> list = new ArrayList<>();
            for(Character c:str.toCharArray()){
                list.add(c);
            }
            return list.stream();
        }
    }
}

3.排序

sorted ()用于对流进行排序

//sorted():自然排序
System.out.println("自然排序:");
List<Integer> list = Arrays.asList(12,99,34,28,55,33);
list.stream().sorted().forEach(System.out::println);

//定制排序
System.out.println("按照员工年龄排序:");
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().sorted((e1, e2)->Integer.compare(e1.getAge(), e2.getAge()))
         .forEach(System.out::println);
//上方代码示例中的员工类和员工表
import java.util.ArrayList;
import java.util.List;

class EmployeeData {
    public static List<Employee> getEmployees(){
        List<Employee> list = new ArrayList<>();

        list.add(new Employee(1001,"麻花腾",29));
        list.add(new Employee(1002,"赵飞",32));
        list.add(new Employee(1003,"林一",40));
        list.add(new Employee(1004,"雷布斯",26));
        list.add(new Employee(1005,"徐杰",43));
        list.add(new Employee(1006,"秦飞",24));
        list.add(new Employee(1007,"张三",33));
        list.add(new Employee(1008,"何颖琳",24));
        return list;

    }
}
class Employee {
    private int id;
    private String name;
    private int age;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    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;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

此笔记仅针对有一定编程基础的同学,且本人只记录比较重要的知识点,若想要入门Java可以先行观看相关教程或书籍后再阅读此笔记。

最后附一下相关链接:
Java在线API中文手册
Java platform se8下载
尚硅谷Java教学视频

posted @ 2021-01-12 12:06  洛水凌云  阅读(93)  评论(0编辑  收藏  举报