Java基础关键_036_Stream
目 录
一、概述
二、创建 Stream 的三种方式
1.Collection 接口提供的方法
2.Arrays 类提供的方法
3.Stream 接口提供的方法
三、顺序流与并行流
四、中间操作
1.筛选(filter)
2.映射(map)
3.去重(distinct)
4.排序(sorted)
5.合并(concat)
6.跳过(skip)
7.截断(limit)
五、终止操作
1.遍历(forEach)
2.匹配(match)
3.归约(reduce)
4.收集(collect)
(1)归集(toList / toSet / toMap)
(2)统计(counting / averaging)
(3)分组(groupingBy)
(4)接合(joining)
一、概述
- 从 jdk 8 开始,Java 引入了全新的 Stream API,它将函数式编程的风格运用到了 Java 中,允许开发者在不改变数据源的情况下对集合进行操作;
- Collection 是静态的内存数据结构,强调的是数据。而 Stream API 是与集合相关的计算操作,强调的是计算。即 Collection 面向内存,Stream API 面向 CPU;
- 步骤:
- 创建操作:通过数据源获取一个 Stream 对象;
- 中间操作:对数据源的数据进行处理,返回一个 Stream 对象,因此可以链式操作;
- 终止操作:执行终止操作时,才会真正执行中间操作,并返回一个计算完成的结果。
- 特点:
- 不会存储对象,只能对元素进行计算;
- 不会改变数据对象,可能会返回一个持有结果的新 Stream;
- Stream 上的操作属于延迟操作,只有等用户真正需要结果的时候才会执行;
- 一旦执行终止操作,就不能再调用其他的中间操作或终止操作。
二、创建 Stream 的三种方式
1.Collection 接口提供的方法
public class CollectionCreate {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("《诗经》");
arrayList.add("《尚书》");
arrayList.add("《礼记》");
arrayList.add("《周易》");
arrayList.add("《春秋》");
// 顺序流,单线程
Stream<String> stream = arrayList.stream();
System.out.println(stream); // java.util.stream.ReferencePipeline$Head@4eec7777
// 并行流,计算时自动启动多线程
Stream<String> parallelStream = arrayList.parallelStream();
System.out.println(parallelStream); // java.util.stream.ReferencePipeline$Head@3b07d329
}
}
2.Arrays 类提供的方法
public class ArraysCreate {
public static void main(String[] args) {
String[] musics = {"《姑娘别哭泣》", "《谁》", "《遇见》", "《轻红》", "《可可托海的牧羊人》"};
Stream<String> stream = Arrays.stream(musics);
System.out.println(stream); // java.util.stream.ReferencePipeline$Head@4eec7777
}
}
3.Stream 接口提供的方法
public class StreamCreate {
public static void main(String[] args) {
Stream<String> cinema = Stream.of("《这个杀手不太冷》", "《教父》", "《终结者》", "《建军大业》");
System.out.println(cinema); // java.util.stream.ReferencePipeline$Head@4eec7777
}
}
三、顺序流与并行流
顺序流对 Stream 对象处理是单线程的,效率较低。
若 Stream 流中处理数据没有顺序要求,且希望可以并行处理数据,则可以使用并行流来处理,从而提高效率。
将顺序流转换为并行流,只需要调用 Stream 提供的 parallel 方法进行转换,无需编写任何多线程代码。
public class TransportParallel {
public static void main(String[] args) {
Stream<String> country = Stream.of("中国", "俄罗斯", "美国", "英国", "法国");
System.out.println(country.isParallel()); // false
Stream<String> parallel = country.parallel();
System.out.println(parallel.isParallel()); // true
System.out.println(country == parallel); // true
}
}
四、中间操作
1.筛选(filter)
按照一定规则校验流中元素,将符合条件的元素提取到新的流中操作。该操作使用 Stream 接口提供的 【Stream<T> filter(Predicate<? super T> predicate)】方法实现。
public class Student {
private String name;
private int 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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class FilterTest {
public static void main(String[] args) {
Student s1 = new Student("张三", 18);
Student s2 = new Student("李四", 23);
Student s3 = new Student("王五", 9);
Student s4 = new Student("赵六", 45);
Student s5 = new Student("孙七", 13);
ArrayList<Student> arrayList = new ArrayList<>();
arrayList.add(s1);
arrayList.add(s2);
arrayList.add(s3);
arrayList.add(s4);
arrayList.add(s5);
// 过滤出成年学生
// filter 是 Stream 的一个中间操作,返回一个 Stream 对象
// forEach 是 Stream 的一个终止操作
// 匿名内部类
/*arrayList.stream().filter(new Predicate<Student>() {
@Override
public boolean test(Student student) {
return student.getAge() >= 18;
}
}).forEach(new Consumer<Student>() {
@Override
public void accept(Student student) {
System.out.println(student);
}
});
*/
// Lambda 表达式
arrayList.stream().filter(student -> student.getAge() >= 18).forEach(System.out::println);
}
}
2.映射(map)
将一个流的元素按照一定映射规则映射到另一个流中。该操作使用了 Stream 接口提供的【Stream map(Function<? super T, ? > mapper)】方法实现。
可以实现将多个集合中的元素映射到另一个流中。该操作使用了 Stream 接口提供的【Stream flatMap(Function<? super T, ? extends Stream<R>> mapper)】方法实现。
public class MapTest {
public static void main(String[] args) {
Stream<String> stream = Stream.of("A", "BC", "DEF");
// 全部转换为小写字母
// 匿名内部类
/*stream.map(new Function<String, String>() {
@Override
public String apply(String s) {
return s.toLowerCase();
}
}).forEach(new Consumer<String>() {
@Override
public void accept(String string) {
System.out.println(string);
}
});
*/
// Lambda 表达式
stream.map(str -> str.toLowerCase()).forEach(System.out::println);
}
}
public class FlatMapTest {
public static void main(String[] args) {
Student s1 = new Student("明世隐", 20);
Student s2 = new Student("李元芳", 18);
Student s3 = new Student("蔡文姬", 6);
Student s4 = new Student("伽罗", 22);
Student s5 = new Student("澜", 27);
ArrayList<Student> arrayList1 = new ArrayList<>();
arrayList1.add(s1);
arrayList1.add(s2);
ArrayList<Student> arrayList2 = new ArrayList<>();
arrayList2.add(s3);
arrayList2.add(s4);
arrayList2.add(s5);
Stream<ArrayList<Student>> stream = Stream.of(arrayList1, arrayList2);
// flatMap()方法,将多个集合中的元素放到一个 Stream,形成一个新的流
// 匿名内部类
/*stream.flatMap(new Function<ArrayList<Student>, Stream<Student>>() {
@Override
public Stream<Student> apply(ArrayList<Student> students) {
return students.stream();
}
}).forEach(new Consumer<Student>() {
@Override
public void accept(Student student) {
System.out.println(student);
}
});
*/
// lambda 表达式
stream.flatMap(ArrayList<Student>::stream).forEach(System.out::println);
}
}
3.去重(distinct)
去除重复的元素,底层使用了 hashCode() 和 equals(Object obj) 判断元素是否相等。该操作使用了 Stream 接口提供的【Stream distinct()】方法实现。
public class Student {
private String name;
private int 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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@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 class DistinctTest {
public static void main(String[] args) {
Student s1 = new Student("小明", 23);
Student s2 = new Student("小红", 23);
Student s3 = new Student("小王", 23);
Student s4 = new Student("小李", 23);
Student s5 = new Student("小明", 18);
Student s6 = new Student("小红", 23);
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(s1);
arrayList.add(s2);
arrayList.add(s3);
arrayList.add(s4);
arrayList.add(s5);
arrayList.add(s6);
// distinct 去重
arrayList.stream().distinct().forEach(System.out::println);
}
}
4.排序(sorted)
使用 Stream 接口中的【Stream sorted()】方法,用于对元素的自然排序,使用该方法则元素对应的类必须实现 Comparable 接口。
使用 Stream 接口中的【Stream sorted(Comparator<? super T> comparator)】方法,用于对元素的指定排序,如此可以对一个类实现多种排序规则。
public class Student implements Comparable<Student> {
private String name;
private int 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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.getAge() - o.getAge();
}
}
public class NaturalSorted {
public static void main(String[] args) {
Student s1 = new Student("光光", 18);
Student s2 = new Student("冬冬", 2);
Student s3 = new Student("球球", 16);
Student s4 = new Student("红红", 23);
Student s5 = new Student("白白", 45);
Student[] students = {s1, s2, s3, s4, s5};
Stream<Student> stream = Arrays.stream(students);
stream.sorted().forEach(System.out::println);
}
}
public class Student {
private String name;
private int 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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class SpecifySorted {
public static void main(String[] args) {
Student s1 = new Student("光光", 18);
Student s2 = new Student("冬冬", 2);
Student s3 = new Student("球球", 16);
Student s4 = new Student("红红", 23);
Student s5 = new Student("白白", 45);
ArrayList<Student> students = new ArrayList<>();
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
students.add(s5);
Stream<Student> stream = students.stream();
stream.sorted((o1, o2) -> o1.getAge() - o2.getAge()).forEach(System.out::println);
}
}
5.合并(concat)
将两个 Stream 合并成一个 Stream。该操作使用 Stream 接口提供的【public static Stream concat(Stream<? extends T> a, Stream<? extends T> b)】方法实现。
public class ConcatTest {
public static void main(String[] args) {
Student s1 = new Student("明世隐", 20);
Student s2 = new Student("李元芳", 18);
Student s3 = new Student("蔡文姬", 6);
Student s4 = new Student("伽罗", 22);
Student s5 = new Student("澜", 27);
Stream<Student> stream1 = Stream.of(s1, s2, s3);
Stream<Student> stream2 = Stream.of(s4, s5);
Stream<Student> concat = Stream.concat(stream1, stream2);
concat.forEach(System.out::println);
}
}
6.跳过(skip)
跳过是跳过 n 个元素开始操作。该操作使用 Stream 接口提供的【Stream skip(long n)】方法实现。
public class SkipTest {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 3, 5, 7, 9, 2, 4, 6, 8, 10);
stream.skip(3).forEach(System.out::println);
}
}
7.截断(limit)
截断是截取 n 个元素的操作。该操作使用 Stream 接口提供的【Stream limit(long maxSize)】方法实现。
跳过操作和截断操作可以联合使用。
public class SkipTest {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 3, 5, 7, 9, 2, 4, 6, 8, 10);
stream.limit(3).forEach(System.out::println);
}
}
五、终止操作
触发终止操作时才会真正执行中间操作,终止操作执行完毕会返回计算结果。终止操作执行完毕 Stream 就会失效,即不能再执行中间操作或终止操作了。
1.遍历(forEach)
使用 Stream 接口提供的【void forEach(Consumer<? super T> action)】方法来遍历计算结果。
2.匹配(match)
- 判断 Stream 中是否存在某些元素;
- 方法:
- allMatch(Predicate<? super T> predicate):检查是否匹配所有元素;
- anyMatch(Predicate<? super T> predicate):检查是否匹配至少一个元素;
- noneMatch(Predicate<? super T> predicate):检查是否一个元素都不匹配;
- Optional findFirst:获得第一个元素。Optional是一个值的容器,可以通过 get() 获取容器的值。
public class MatchTest {
public static void main(String[] args) {
Student s1 = new Student("炘南", 23);
Student s2 = new Student("东杉", 27);
Student s3 = new Student("北淼", 25);
Student s4 = new Student("坤中", 18);
Student s5 = new Student("西钊", 24);
Stream<Student> stream = Stream.of(s1, s2, s3, s4, s5);
// 判断全部元素年龄大于18岁
// boolean allMatch = stream.allMatch(s -> s.getAge() > 18);
// System.out.println(allMatch); // false
// 判断是否有元素年龄大于18岁
// boolean anyMatch = stream.anyMatch(s -> s.getAge() > 18);
// System.out.println(anyMatch); // true
// 判断是否有元素年龄小于18岁
// boolean noneMatch = stream.noneMatch(s -> s.getAge() > 18);
// System.out.println(noneMatch); // false
// 判断第一个元素年龄大于18岁
boolean isMatch = stream.findFirst().get().getAge() > 18;
System.out.println(isMatch); // true
}
}
3.归约(reduce)
- 将所有元素按照指定规则合并成一个结果;
- 方法:
- reduce(BinaryOperator<T> accumulator);
- reduce(T identity, BinaryOperator<T> accumulator)。
- reduce 操作可以实现从一组元素中生成一个值,而 max()、min()、count() 方法都属于 reduce 操作,因为常用所以单独设为方法。
public class ReduceTest {
public static void main(String[] args) {
/*
* 求和
* */
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
IntStream stream = Arrays.stream(arr);
OptionalInt reduce = stream.reduce((a, b) -> a + b);
System.out.println(reduce.getAsInt()); // 55
/*
* 比较字符串长度,并将最长的字符串输出
* */
String[] arr2 = {"hello", "world", "java", "stream", "test"};
Stream<String> stream1 = Arrays.stream(arr2);
String s = stream1.reduce((a, b) -> a.length() > b.length() ? a : b).get();
System.out.println(s); // stream
/*
* 获得学生的总年龄
* */
Student s1 = new Student("老张", 18);
Student s2 = new Student("老李", 22);
Student s3 = new Student("老王", 19);
Student s4 = new Student("老郭", 31);
ArrayList<Student> students = new ArrayList<>();
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
Stream<Student> stream2 = students.stream();
Integer sumAge = stream2.map(Student::getAge).reduce((a, b) -> a + b).get();
System.out.println(sumAge); // 90
/*
* count()
* */
Stream<String> stream3 = Stream.of("hello", "world", "java", "stream", "test");
long count = stream3.count();
System.out.println(count); // 5
/*
* max()
* */
Stream<Integer> stream4 = Stream.of(1, 14, 5, 38, 62, 4);
Optional<Integer> max = stream4.max(Integer::compareTo);
System.out.println(max.get()); // 62
/*
* min()
* */
Stream<Integer> stream5 = Stream.of(1, 14, 5, 38, 62, 4);
Optional<Integer> min = stream5.min(Integer::compareTo);
System.out.println(min.get()); // 1
}
}
4.收集(collect)
把一个流收集起来,最终可以是一个值也可以是一个新的集合。调用 Stream 接口提供的【collect(Collector<? super T, A, R collector)】方法实现,参数中的 Collector 对象大都是直接通过 Collectors 工具类获得,实际上传入的 Collector 决定了 collect() 的行为。
(1)归集(toList / toSet / toMap)
因为 Stream 流不存储数据,那么在 Stream 流中数据处理完成后,若需要把数据存入集合中,就需要使用归集操作。
Collectors 提供了 toList、toSet、toMap 操作,此时并没有明确存储数据对应的集合具体类型, 若需要明确具体类型,需要使用toCollection 来实现。
public class CollectTo {
public static void main(String[] args) {
/*
* toList():将流中的元素收集到一个List集合中
* */
Student s1 = new Student("孙悟空", 18);
Student s2 = new Student("猪八戒", 2);
Student s3 = new Student("唐僧", 45);
Stream<Student> stream1 = Stream.of(s1, s2, s3);
List<Student> collect1 = stream1.collect(Collectors.toList());
System.out.println(collect1);
/*
* toSet():将流中的元素收集到一个Set集合中
* */
Stream<Integer> stream2 = Stream.of(1, 3, 5, 7, 9);
Set<Integer> collect2 = stream2.collect(Collectors.toSet());
System.out.println(collect2);
/*
* toMap():将流中的元素收集到一个Map集合中
* */
Stream<String> stream3 = Stream.of("1:I ", "2:Love ", "3:You ");
Map<String, String> collect3 = stream3.collect(Collectors.toMap(new Function<String, String>() {
@Override
public String apply(String s) {
return s.substring(0, s.indexOf(":"));
}
}, new Function<String, String>() {
@Override
public String apply(String s) {
return s.substring(s.indexOf(":") + 1);
}
}));
collect3.forEach((k, v) -> System.out.println(k + ":" + v));
/*
* toCollection()
* */
Stream<Double> stream4 = Stream.of(3.14, 5.20, 13.14);
HashSet<Double> collect4 = stream4.collect(Collectors.toCollection(HashSet::new));
System.out.println(collect4);
}
}
(2)统计(counting / averaging)
- 计数:counting;
- 平均值:averagingInt、averagingLong、averagingDouble;
- 最值:maxBy、minBy;
- 求和:summingInt、summingLong、summingDouble;
- 统计以上所有:summarizingInt、summarizingLong、summarizingDouble。
public class CollectCouAve {
public static void main(String[] args) {
/*
* counting
* */
Student s1 = new Student("光光", 18);
Student s2 = new Student("冬冬", 2);
Student s3 = new Student("球球", 16);
Student s4 = new Student("红红", 23);
Student s5 = new Student("白白", 45);
Student[] students = {s1, s2, s3, s4, s5};
Stream<Student> stream1 = Arrays.stream(students);
Long collect1 = stream1.collect(Collectors.counting());
System.out.println(collect1); // 5
/*
* averagingInt
* */
Stream<Student> stream2 = Arrays.stream(students);
Double collect2 = stream2.collect(Collectors.averagingInt(Student::getAge));
System.out.println(collect2); // 20.8
/*
* maxBy
* */
Stream<Student> stream3 = Arrays.stream(students);
Optional<Student> collect3 = stream3.collect(Collectors.maxBy((o1, o2) -> o1.getAge() - o2.getAge()));
System.out.println(collect3.get()); // Student{name='白白', age=45}
/*
* summingInt
* */
Stream<Student> stream4 = Arrays.stream(students);
Integer collect4 = stream4.collect(Collectors.summingInt(Student::getAge));
System.out.println(collect4); // 104
/*
* summarizingInt
* */
Stream<Student> stream5 = Arrays.stream(students);
IntSummaryStatistics collect5 = stream5.collect(Collectors.summarizingInt(Student::getAge));
System.out.println(collect5); // IntSummaryStatistics{count=5, sum=104, min=2, average=20.800000, max=45}
}
}
(3)分组(groupingBy)
将 Stream 按条件分为若干个 Map。
public class GroupingByTest {
public static void main(String[] args) {
Student s1 = new Student("宋江", 18);
Student s2 = new Student("卢俊义", 18);
Student s3 = new Student("吴用", 23);
Student s4 = new Student("公孙胜", 23);
Student s5 = new Student("关胜", 45);
Student[] students = {s1, s2, s3, s4, s5};
Stream<Student> stream = Arrays.stream(students);
Map<Integer, List<Student>> collect = stream.collect(Collectors.groupingBy(Student::getAge));
collect.forEach((k, v) -> System.out.println(k + ":" + v));
}
}
(4)接合(joining)
将 Stream 计算的数据按照一定规则进行拼接。
public class JoiningTest {
public static void main(String[] args) {
Student s1 = new Student("州洲", 18);
Student s2 = new Student("冬冬", 2);
Student s3 = new Student("由由", 16);
Student s4 = new Student("辉辉", 23);
Student s5 = new Student("季季", 45);
Student[] students = {s1, s2, s3, s4, s5};
Stream<Student> stream = Arrays.stream(students);
Stream<String> stream1 = stream.map(Student::getName);
String collect = stream1.collect(Collectors.joining(","));
System.out.println(collect); // 州洲,冬冬,由由,辉辉,季季
}
}