Java8_Stream

0

Java8 新特性 Stream

提纲

  • Stream流的概念和特点
    • 什么是Stream流,为什么要使用Stream流
    • Stream流和集合的区别和联系
    • Stream流的三个操作步骤:创建、中间操作、终止操作
    • Stream流的延迟执行和短路机制
  • Stream流的创建方式
    • 通过集合、数组、文件等数据源创建Stream流
    • 通过Stream.of()、Stream.iterate()、Stream.generate()等静态方法创建Stream流
  • Stream流的中间操作
    • 筛选与切片:filter(Predicate p)、distinct ()、limit (long maxSize)、skip (long n)等方法,用于从流中筛选出符合条件或者限制数量的元素。
    • 映射:map(Function f)、flatMap(Function f)等方法,用于将流中的元素进行转换或者扁平化处理,生成一个新的流。
    • 排序:sorted()、sorted(Comparator comp)等方法,用于对流中的元素进行自然排序或者定制排序。
    • 其他:peek(Consumer c)等方法,用于对流中的元素进行消费或者其他操作。
  • Stream流的终止操作
    • 匹配和查找:allMatch(Predicate p)、anyMatch(Predicate p)、noneMatch(Predicate p)、findFirst()、findAny()、count()、max(Comparator c)、min(Comparator c)等方法
    • 归约:reduce(T identify, BinaryOperator b)、reduce(BinaryOperator b)等方法,用于将流中的元素反复结合起来,得到一个值。
    • 收集:collect(Collector c)方法,用于将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法。Collector接口由Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例,例如toList()、toSet()、toMap()、groupingBy()等。
    • 遍历:forEach(Consumer c)方法,用于对流中的每个元素执行一个操作。

Stream流的概念和特点

Stream流是JDK1.8更新后带来的新特性,通过将要处理的元素视作为在管道中的流,并进行诸如筛选,排序,分组等等的处理,通过Stream流进行数据处理可以极大的提高程序员的代码编写效率,还能提高代码的简洁度。

Stream流的三个操作步骤是:

  • 创建,通过数据源(如集合、数组、文件等)或静态方法(如Stream.of()、Stream.iterate()、Stream.generate()等)获取一个流;
  • 中间操作,对流中的元素进行一系列的转换或过滤,如filter()、map()、sorted()、limit()、skip()等,返回一个新的流,可以链式调用多个中间操作;
  • 终止操作,对流中的元素进行最终的处理,如forEach()、count()、reduce()、collect()等,产生一个结果或副作用,终止操作后流就不能再使用了。

Stream流的延迟执行和短路机制是指:

  • 延迟执行,也叫惰性求值,是指只有当调用终止操作时,中间操作才会真正执行,否则只是在流中建立了一系列的操作链,不会对数据源产生任何影响。peek( )
  • 短路机制,是指当遇到某些条件时,可以提前结束流的遍历,不需要处理所有的元素。例如,limit()、findFirst()、anyMatch()等方法都可以实现短路机制。

Stream流的创建方式

  • 通过集合创建Stream流,可以使用集合的stream()或parallelStream()方法,例如:
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); //获取顺序流
Stream<String> parallelStream = list.parallelStream(); //获取并行流
  • 通过数组创建Stream流,可以使用Arrays类的stream()方法,例如:
int[] arr = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(arr); //获取int类型的流
String[] strArr = {"hello", "world", "java"};
Stream<String> strStream = Arrays.stream(strArr); //获取String类型的流
  • 通过文件创建Stream流,可以使用BufferedReader类的lines()方法,例如:
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
    Stream<String> lineStream = br.lines(); //获取每一行文本组成的流
    lineStream.forEach(System.out::println); //打印每一行文本
} catch (IOException e) {
    e.printStackTrace();
}
  • 通过Stream.of()创建Stream流,可以传入单个或多个元素,例如:
Stream<String> strStream = Stream.of("a", "b", "c"); //获取String类型的流
Stream<Integer> intStream = Stream.of(1, 2, 3, 4, 5); //获取Integer类型的流
  • 通过Stream.iterate()创建Stream流,可以传入一个初始值和一个函数,生成一个无限序列,例如:
Stream<Integer> evenStream = Stream.iterate(0, n -> n + 2); //获取偶数序列的流
Stream<Integer> oddStream = Stream.iterate(1, n -> n + 2); //获取奇数序列的流
  • 通过Stream.generate()创建Stream流,可以传入一个供应器(Supplier),生成一个无限元素的流,例如:
Stream<Double> randomStream = Stream.generate(Math::random); //获取随机数的流
Stream<String> helloStream = Stream.generate(() -> "Hello"); //获取常量字符串的流

Stream流的中间操作

  • filter(Predicate p)方法用于从流中筛选出符合条件的元素,例如:
//过滤出年龄大于20岁的员工
List<Employee> emps = Arrays.asList(
  new Employee(101, "张三", 28, 9999),
  new Employee(102, "李四", 49, 666),
  new Employee(103, "王五", 38, 333),
  new Employee(104, "赵六", 12, 7777),
  new Employee(105, "田七", 6, 222)
);
emps.stream()
    .filter(e -> e.getAge() > 20)
    .forEach(System.out::println)
  • distinct()方法用于去除流中的重复元素,需要重写hashCode ()和equals ()方法,例如:
//去除重复的字符串
List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "aaa");
strList.stream()
       .distinct()
       .forEach(System.out::println);
  • limit(long maxSize)方法用于截断流,使其元素不超过给定数量,例如:
//截取前两个元素
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
nums.stream()
    .limit(2)
    .forEach(System.out::println);
  • skip(long n)方法用于跳过流中的前n个元素,如果流中元素不足n个,则返回一个空流,例如:
//跳过前三个元素
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
nums.stream()
    .skip(3)
    .forEach(System.out::println);
  • map (Function f)方法用于将流中的元素进行转换或提取信息,生成一个新的流,例如:
//将字符串转换为大写
List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
strList.stream()
       .map(String::toUpperCase)
       .forEach(System.out::println);
  • flatMap(Function f)方法也可以用于将多维数组或集合转换为一维数组或集合,例如:
//将一个二维数组转换为一个一维数组
Integer[][] arr = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
Integer[] newArr = Arrays.stream(arr)
                         .flatMap(Arrays::stream)
                         .toArray(Integer[]::new);
System.out.println(Arrays.toString(newArr));
// 1,2,3,4,5,6,7,8,9
  • sorted()方法用于对流中的元素进行自然排序,要求元素类实现Comparable接口,例如:
//对整数列表进行自然排序
List<Integer> nums = Arrays.asList(5, 3, 1, 4, 2);
nums.stream()
    .sorted()
    .forEach(System.out::println);
  • sorted(Comparator comp)方法用于对流中的元素进行定制排序,要求传入一个Comparator接口的实例,可以使用lambda表达式创建,例如:
//对员工列表按照年龄升序排序
List<Employee> emps = Arrays.asList(
  new Employee(101, "张三", 28, 9999),
  new Employee(102, "李四", 49, 666),
  new Employee(103, "王五", 38, 333),
  new Employee(104, "赵六", 12, 7777),
  new Employee(105, "田七", 6, 222)
);
emps.stream()
    .sorted((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()))
    .forEach(System.out::println);
  • Comparator接口提供了一些静态方法和默认方法来辅助排序,例如reverseOrder()、comparing()、reversed()等,例如:
//对员工列表按照年龄降序排序
List<Employee> emps = Arrays.asList(
  new Employee(101, "张三", 28, 9999),
  new Employee(102, "李四", 49, 666),
  new Employee(103, "王五", 38, 333),
  new Employee(104, "赵六", 12, 7777),
  new Employee(105, "田七", 6, 222)
);
emps.stream()
    .sorted(Comparator.comparing(Employee::getAge).reversed())
    .forEach(System.out::println);
  • peek(Consumer c) 方法用于对流中的元素进行调试
public class Mainq3 {  
    public static void main(String[] args) {  
        List<Integer> values = Arrays.asList(1,2,3);  
        values.stream()  
                .map(n -> n * 2) //line n1  
                .peek(System.out::print)//line n2  
                .count();  
        System.out.println("-------------");  
        IntStream stream = IntStream.of(1,2,3);  
        stream.map(n -> n*2)  
                .peek(System.out::print)  
                .count();  
        //在Java8中,上述结果为246
        //但是在Java9中,对于count()这样的短路操作,peek方法中的操作不会被这些元素调用。
    }  
}

Stream流的终止操作

  • 匹配和查找:allMatch(Predicate p)、anyMatch(Predicate p)、noneMatch(Predicate p)、findFirst()、findAny()、count()、max(Comparator c)、min(Comparator c)等方法,用于检查流中的元素是否满足某些条件或者返回特定的元素,例如:
//判断员工列表中是否所有员工的年龄都大于18
List<Employee> emps = Arrays.asList(
  new Employee(101, "张三", 28, 9999),
  new Employee(102, "李四", 49, 666),
  new Employee(103, "王五", 38, 333),
  new Employee(104, "赵六", 12, 7777),
  new Employee(105, "田七", 6, 222)
);
//判断员工列表中是否年龄都大于18岁
boolean allMatch = emps.stream().allMatch(e -> e.getAge() > 18);
System.out.println(allMatch); //false

//判断员工列表中是否至少有一个员工的姓名是王五
boolean anyMatch = emps.stream().anyMatch(e -> e.getName().equals("王五"));
System.out.println(anyMatch); //true

//判断员工列表中是否没有员工的薪资超过10000
boolean noneMatch = emps.stream().noneMatch(e -> e.getSalary() > 10000);
System.out.println(noneMatch); //true

//返回员工列表中第一个年龄大于40的员工
Optional<Employee> first = emps.stream().filter(e -> e.getAge() > 40).findFirst();
System.out.println(first.get()); //Employee{id=102, name='李四', age=49, salary=666.0}

//返回员工列表中任意一个年龄大于40的员工
Optional<Employee> any = emps.stream().filter(e -> e.getAge() > 40).findAny();
System.out.println(any.get()); //Employee{id=102, name='李四', age=49, salary=666.0}

//返回员工列表中的元素个数
long count = emps.stream().count();
System.out.println(count); //5

//返回员工列表中薪资最高的员工
Optional<Employee> max = emps.stream().max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(max.get()); //Employee{id=101, name='张三', age=28, salary=9999.0}

//返回员工列表中年龄最小的员工
Optional<Employee> min = emps.stream().min((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
System.out.println(min.get()); //Employee{id=105, name='田七', age=6, salary=222.0}
  • reduce()方法有三个override的方法,其中两个方法是常用的:

    1. Optional<T> reduce(BinaryOperator<T> accumulator):该方法接收一个BinaryOperator类型的参数,用于将Stream中的元素按照指定的方式聚合成一个结果。返回值是一个Optional类型,因为Stream中可能没有元素。
    2. T reduce(T identity, BinaryOperator<T> accumulator):该方法接收两个参数,第一个参数是初始值,第二个参数是一个BinaryOperator类型的参数,用于将Stream中的元素按照指定的方式聚合成一个结果。返回值是一个T类型。

这两个方法都接收一个BinaryOperator类型的参数,该参数用于将Stream中的元素按照指定的方式聚合成一个结果。例如,如果我们要将Stream中的所有元素相加,可以使用以下代码:

List<Integer> list = Arrays.asList(1, 2, 3, 4);
int sum = list.stream().reduce(0, (a, b) -> a + b);

在上面的代码中,我们使用了第二个reduce()方法,初始值为0,聚合方式为相加。因此,最终结果为10。

如果我们要将Stream中的所有元素取最大值或最小值,可以使用以下代码:

List<Integer> list = Arrays.asList(1, 2, 3, 4);
Optional<Integer> max = list.stream().reduce(Integer::max);
Optional<Integer> min = list.stream().reduce(Integer::min);
  • collect(Collector c)方法是一个终端操作,它可以把流中的元素收集到一个结果容器中,比如List、Set、Map等。Collector接口定义了如何对流中的元素进行收集的规则,而Collectors类提供了一些常用的收集器实例,方便我们使用。例如,如果你想要把一个字符串流收集到一个列表中,你可以这样写:
//定义一个字符串流
Stream<String> stream = Stream.of("apple", "banana", "cherry", "date");

//使用collect方法和Collectors.toList()收集器
List<String> list = stream.collect(Collectors.toList());

//打印结果
System.out.println(list); //输出[apple, banana, cherry, date]

Collectors类提供了很多静态方法来创建常用的Collector实例,比如:

  • toList():将流中的元素收集到一个List中。
  • toSet():将流中的元素收集到一个Set中,去除重复元素。
  • toCollection(Supplier<C> collectionFactory):将流中的元素收集到一个指定的集合类型中,比如LinkedList、HashSet等。
  • toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper):将流中的元素收集到一个Map中,其中key和value由指定的函数生成。
  • collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher):将流中的元素先用一个Collector收集,然后再对结果应用一个转换函数。
  • joining():将流中的元素连接成一个字符串。
  • counting():计算流中的元素个数。
  • summarizingDouble/Long/Int(ToDouble/Long/IntFunction<? super T> mapper):计算流中元素的统计信息,比如总和、平均值、最大值、最小值、个数等。
  • averagingDouble/Long/Int(ToDouble/Long/IntFunction<? super T> mapper):计算流中元素的平均值。
  • summingDouble/Long/Int(ToDouble/Long/IntFunction<? super T> mapper):计算流中元素的总和。
  • maxBy/minBy(Comparator<? super T> comparator):根据指定的比较器找出流中的最大或最小元素。
  • groupingBy(Function<? super T,? extends K> classifier):根据指定的分类函数对流中的元素进行分组,返回一个Map,其中key是分类标准,value是对应的元素列表。
  • partitioningBy(Predicate<? super T> predicate):根据指定的布尔函数对流中的元素进行分区,返回一个Map,其中key是布尔值,value是对应的元素列表。