Stream流
1. 流的由来
- 在数据来源多样化的今天,在做数据统计和处理的时候,大多是以底层数据库或其他数据来源的数据为基础进行更上层的统计和处理。
- 在Java 的集合 API 中,辅助集合操作的方法太少
- 对数据的筛选、聚合操作等都需要程序员遍历集合进行,效率低下,笨拙
- Stream流是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常遍历、高效的聚合操作或者大数据的批量操作。
- 结合Lambda表达式,提高编程效率和程序的可读性。
2. Stream流
- Stream流是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。
- 数据源:流的来源,可以是集合、数组、I/O等
- 聚合操作: 类似SQL 语句一样的操作,比如filter、map、reduce、find、match、sorted等
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
3. 流的构成
-
流处理数据的过程类似工厂的流水线。
-
- 其中最主要的是
ReferencePipeline
,继承自AbstractPipeline
,AbstractPipeline
实现了BaseStream
接口并实现了它的方法。 ReferencePipeline
内部定义了三个静态内部类:Head
,StatelessOp
,StatefulOp
.Head
:代表的是流的源头,刚构建的时候流的一些属性被包含在这个对象。比如这个集合的元素,毕竟流的存在还是为了对一组元素的操作。StatelessOp
:代表的是无状态的操作,如方法 map()StatefulOp
:代表的是有状态的操作,如方法 sorted()
- 其中最主要的是
4. Stream流的构造
-
static Stream of(T… values)
- 返回其元素为指定值的顺序有序流
- 示例:
Stream stream = Stream.of("a", "b", "c");
-
Arrays工具类 stream() 方法
-
String [] strArray = new String[] {"a", "b", "c"}; stream = Arrays.stream(strArray);
-
-
Collections
-
List<String> list = Arrays.asList(strArray); stream = list.stream();
-
-
对于基本数据类型,有三种包装类
IntStream
,LongStream
,DoubleStream
5. 常用方法
Stream 常用的流操作包括:
-
中间操作(Intermediate Operations)
-
- 无状态(Stateless)操作:每个数据的处理是独立的,不会影响或依赖之前的数据。如
filter()
、flatMap()
、flatMapToDouble()
、flatMapToInt()
、flatMapToLong()
、map()
、mapToDouble()
、mapToInt()
、mapToLong()
、peek()
、unordered()
等 - 有状态(Stateful)操作:处理时会记录状态,比如处理了几个。后面元素的处理会依赖前面记录的状态,或者拿到所有元素才能继续下去。如
distinct()
、sorted()
、sorted(comparator)
、limit()
、skip()
等
- 无状态(Stateless)操作:每个数据的处理是独立的,不会影响或依赖之前的数据。如
-
终止操作(Terminal Operations)
-
- 非短路操作:处理完所有数据才能得到结果。如
collect()
、count()
、forEach()
、forEachOrdered()
、max()
、min()
、reduce()
、toArray()
等。 - 短路(short-circuiting)操作:拿到符合预期的结果就会停下来,不一定会处理完所有数据。如
anyMatch()
、allMatch()
、noneMatch()
、findFirst()
、findAny()
等。
- 非短路操作:处理完所有数据才能得到结果。如
ForEach
-
void forEach(Consumer<? super T> action)
-
不说了,遍历用的,
-
/** * @Author: Hjx * @Date: 2021/8/16 15:30 */ public class TestStream { public static void main(String[] args) { // 获取流 Stream<String> stream = Stream.of("aaa","bbb","ccc","ddd","eee"); stream.forEach(string -> System.out.println(string)); } }
-
aaa bbb ccc ddd eee
-
filter
-
Stream<T> filter(Predicate<? super T> predicate)
-
一般用来筛选元素的,该方法接受一个函数式接口Predicate,可以使用Lambda表达式进行条件的筛选
-
Prodicate接口
- java.util.stream.Predicate函数式接口,其中唯一的抽象方法
boolean test(T t)
,该方法会返回布尔类型值,代表指定的条件是否满足,如果条件满足返回true,那么Stream流的方法filter将集合或者数组其中的元素保留下来,如果条件不满足返回false,那么filter方法会舍弃该元素。
- java.util.stream.Predicate函数式接口,其中唯一的抽象方法
-
/** * @Author: Hjx * @Date: 2021/8/16 15:30 */ public class TestStream { public static void main(String[] args) { // 获取流 Stream<String> stream = Stream.of("aaa", "bbb", "ccc", "ddd", "eee"); stream.filter(string -> !string.equals("aaa")) .forEach(string -> System.out.println(string)); } }
-
bbb ccc ddd eee
map
-
<R> Stream<R> map(Function<? super T,? extends R> mapper)
-
一般用于将元素转换成另外一种元素类型
-
该方法接受一个函数式接口Function作为方法参数,可以将当前流中的T数据转换成另一种R类型的数据
-
Function接口
- 其唯一抽象方法
R apply(T t)
- 可以将一种T类型的数据转换成R类型的数据
- 其唯一抽象方法
-
/** * @Author: Hjx * @Date: 2021/8/16 15:30 */ public class TestStream { public static void main(String[] args) { // 获取流 Stream<String> stream = Stream.of("1", "2", "3", "4", "5"); stream.map(string -> Integer.parseInt(string)) .forEach(string -> System.out.println(string.getClass())); } }
-
class java.lang.Integer class java.lang.Integer class java.lang.Integer class java.lang.Integer class java.lang.Integer
collect
-
<R,A> R collect(Collector<? super T,A,R> collector)
-
终止操作,Stream不调用终止方法,中间的操作不会执行。 作用就是告诉Stream将流中的数据保存到哪里。
-
//1.收集数据到list集合中 stream.collect(Collectors.toList()) //2.收集数据到set集合中 stream.collect(Collectors.toSet())
剩下的等用到再补充吧,累了,毁灭吧
2021/8/23 补充
-
员工类
-
/** * @author 16541 */ public class Employee { /** * 员工姓名 */ public String name; /** * 员工年龄 */ public Integer age; /** * 员工薪资 */ public Double salary; public Employee() { } public Employee(String name, Integer age, Double salary) { this.name = name; this.age = age; this.salary = salary; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}'; } }
limit
-
Stream<T> limit(long maxSize);
-
限制获取的数据个数。传入参数是一个long类型数据
-
/** * @Author: Hjx * @Date: 2021/8/23 14:22 */ public class TestMain { // 员工集合 List<Employee> employees = Arrays.asList( new Employee("张三", 30, 9999.99), new Employee("李四", 20, 7777.77), new Employee("王五", 10, 6666.66), new Employee("陈六", 27, 4444.44), new Employee("田七", 35, 3333.33) ); @Test public void test01(){ employees.stream() .filter(e -> e.getAge() > 25) .limit(2) .forEach(System.out::println); } } 运行结果: Employee{name='张三', age=30, salary=9999.99} Employee{name='陈六', age=27, salary=4444.44}
-
/** * @Author: Hjx * @Date: 2021/8/23 14:22 */ public class TestMain { // 员工集合 List<Employee> employees = Arrays.asList( new Employee("张三", 30, 9999.99), new Employee("李四", 20, 7777.77), new Employee("王五", 10, 6666.66), new Employee("陈六", 27, 4444.44), new Employee("田七", 35, 3333.33) ); @Test public void test01() { employees.stream() .filter(e -> { System.out.println("截断流"); return e.getAge() > 25; } ) .limit(2) .forEach(System.out::println); } } 运行结果: 截断流 Employee{name='张三', age=30, salary=9999.99} 截断流 截断流 截断流 Employee{name='陈六', age=27, salary=4444.44}
-
limit属于截断流,就是当获取到达满足的个数时,便不会继续往下遍历了。
skip
-
Stream<T> skip(long n);
-
跳过指定的数据个数
-
/** * @Author: Hjx * @Date: 2021/8/23 14:22 */ public class TestMain { // 员工集合 List<Employee> employees = Arrays.asList( new Employee("张三", 30, 9999.99), new Employee("李四", 20, 7777.77), new Employee("王五", 10, 6666.66), new Employee("陈六", 27, 4444.44), new Employee("田七", 35, 3333.33) ); @Test public void test02(){ employees.stream() .filter(e -> e.getSalary() > 4000) .skip(2) .forEach(System.out::println); } } 运行结果: Employee{name='王五', age=10, salary=6666.66} Employee{name='陈六', age=27, salary=4444.44}
-
本来张三和李四也是符合条件薪资大于4000的,但跳过两个,就只剩下王五和陈六了。
distinct
-
Stream<T> distinct();
-
去除重复元素,依据的原理是 hashCode值 和 equals方法,
-
重写 employee 的hashCode 方法 和 equals 方法
-
/** * @author 16541 */ public class Employee { /** * 员工姓名 */ public String name; /** * 员工年龄 */ public Integer age; /** * 员工薪资 */ public Double salary; public Employee() { } public Employee(String name, Integer age, Double salary) { this.name = name; this.age = age; this.salary = salary; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } @Override public String toString() { return "Employee{" + "name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}'; } // 这里判断名字相同即为同一员工 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return name.equals(employee.name); } @Override public int hashCode() { return Objects.hash(name); } }
-
/** * @Author: Hjx * @Date: 2021/8/23 14:22 */ public class TestMain { // 员工集合 List<Employee> employees = Arrays.asList( new Employee("张三", 30, 9999.99), new Employee("李四", 20, 7777.77), new Employee("王五", 10, 6666.66), new Employee("陈六", 27, 4444.44), new Employee("田七", 35, 3333.33), new Employee("田七", 35, 3333.33), new Employee("田七", 35, 3333.33) ); @Test public void test03(){ employees.stream() .distinct() .forEach(System.out::println); } } 运行结果: Employee{name='张三', age=30, salary=9999.99} Employee{name='李四', age=20, salary=7777.77} Employee{name='王五', age=10, salary=6666.66} Employee{name='陈六', age=27, salary=4444.44} Employee{name='田七', age=35, salary=3333.33}
flatMap
-
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
-
接收一个函数作为参数,将参数中的每一个元素转换为一个流,然后把所有的流连接起来形成一个流。
-
public class TestMain { @Test public void test04() { List<String> list = Arrays.asList("aaa", "bbb", "ccc"); list.stream() .flatMap(TestMain::flatMapStream) .forEach(System.out::println); } /** * 将 String类型 转换为流 * @param string * @return */ public static Stream<Character> flatMapStream(String string){ List<Character> list = new ArrayList<>(); char[] chars = string.toCharArray(); for(char ch:chars){ list.add(ch); } return list.stream(); } } 运行结果: a a a b b b c c c
sorted
-
Stream<T> sorted();
-
自然排序,按照字典顺序
-
public class TestMain { @Test public void test05(){ List<String> list = Arrays.asList("abc", "aac", "bac", "abb"); list.stream() .sorted() .forEach(System.out::println); } } 运行结果: aac abb abc bac
sorted(Comparator<? super T> comparator)
-
Stream<T> sorted(Comparator<? super T> comparator);
-
自定义排序
-
public class TestMain { // 员工集合 List<Employee> employees = Arrays.asList( new Employee("张三", 30, 9999.99), new Employee("李四", 20, 7777.77), new Employee("王五", 10, 6666.66), new Employee("陈六", 27, 4444.44), new Employee("田七", 35, 3333.33) ); @Test public void test05(){ // 自定义按照年龄大小排序 Comparator<Employee> comparator = (x,y) -> x.getAge().compareTo(y.getAge()); employees.stream() .sorted(comparator) .forEach(System.out::println); } } 运行结果: Employee{name='王五', age=10, salary=6666.66} Employee{name='李四', age=20, salary=7777.77} Employee{name='陈六', age=27, salary=4444.44} Employee{name='张三', age=30, salary=9999.99} Employee{name='田七', age=35, salary=3333.33}