文章目录
- 简介
- 操作流程
- 流的创建
- 中间操作
- 最终操作
- 简单案例
- 扩展
- parallelStream并行流
- 创建方式
- Optional
- 声明式编程/命令式编程?
简介
Java 8 添加了一个新的抽象概念称为流Stream
,可以让你以一种声明的方式处理数,据提供一种对 Java 集合运算和表达的高阶抽象。它与 java.io包里的 InputStream和 OutputStream是完全不同的概念。它将要处理的元素集合看作一种流, 流在管道中传输, 可以在管道的节点上进行数据处理, 比如筛选, 排序,聚合等。Stream 是对集合对象功能的增强,借助于lambda表达式,展现更优雅的表达风格,极大的提高编程效率和程序可读性。
Stream 特点
- 不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果;
- 不会改变数据源,通常情况下会产生一个新的集合;(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)
- 具有延迟执行特性,只有调用终端操作时,中间操作才会执行。
- 不可复用,对一个已经进行过终端操作的流再次调用,会抛出异常。
操作流程
流的创建
java.util.Collection.stream()
方法用集合创建流(常用)
List<String> list = Arrays.asList("1", "2", "3", "4", "5", "6");
Stream<String> stream = list.stream();
java.util.Arrays.stream(T[] array)
方法用数组创建流
int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);
- 静态方法:
of()、iterate()、generate()
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 7).limit(6);
Stream<Double> stream3 = Stream.generate(Math::random).limit(5);
- 通过文件生成
// 通过Files.line方法得到一个流,流的内容是按行读取的文件信息.
Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset());
中间操作
其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。
filter
:从流中排除元素,filter的方法参数为一个条件(过滤保留函数返回值为 true 的元素)
List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 过滤以"a"开头的字符串
List<String> filteredList = list.stream().filter(s -> s.startsWith("a")).collect(Collectors.toList());
distinct
:去重(利用hashCode和equals方法来判断两个元素是否相等)
List<String> list = Arrays.asList("apple", "banana", "apple", "cat", "dog", "banana");
// 去除重复元素
List<String> distinctList = list.stream().distinct().collect(Collectors.toList());
limit
:通过limit方法指定返回流的个数,limit的参数值必须 >=0,否则将会抛出异常。
List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 只保留流中的前两个元素
List<String> limitedList = list.stream().limit(2).collect(Collectors.toList());
skip
:跳过流中元素,参数值必须>=0
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 跳过前三个数据后遍历数据
Stream<Integer> stream = numbers.stream().skip(3).forEach(System.out::println);
map
:接收一个函数作为参数,应用到每个元素上。(mapToDouble、mapToInt、mapToLong等)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 将每个数转化为其平方值
List<Integer> mappedNumbers = numbers.stream().map(n -> n * n).collect(Collectors.toList());
flatMap
:将一个流中的每个值都转换成另一个流,再将所有流连接成一个流。
List<List<String>> listOfLists = Arrays.asList(Arrays.asList("apple"), Arrays.asList("banana", "cherry", "date"));
// 将嵌套列表转换为平铺列表
List<String> flattenedList = listOfLists.stream().flatMap(Collection::stream).collect(Collectors.toList());
peek
:可以在Stream的每个元素恰好被消费前,而不改变流中的元素内容
List<String> list = Arrays.asList("apple", "banana", "cat");
// 在字符串转换为大写之前和之后分别打印它们的值,但并不改变原始字符串列表
List<String> modifiedList = list.stream().peek(s -> System.out.println("before: " + s)).map(String::toUpperCase).peek(s -> System.out.println("after: " + s)).collect(Collectors.toList());
sorted
:按自然排序,或者实现Comparator 接口,写比较器,自定义排序
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5);
// 对数字列表进行排序
List<Integer> sortedNumbers = numbers.stream().sorted().collect(Collectors.toList());
中间操作是延迟执行的,直到执行终止操作才执行。
最终操作
allMatch
(所有元素)、anyMatch
(其中一个)、noneMatch
(全不匹配):判断流中是否全部、任意一个、全不满足指定条件的元素
List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 判断是否存在任意一个以"a"开头的字符串,这里为true
boolean anyMatch = list.stream().anyMatch(s -> s.startsWith("a"));
findFirst
(查找第一个)、findAny
(随机查找一个):返回流中的第一个或任意一个元素
List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 返回列表中的第一个元素,这里为"apple"
Optional<String> first = list.stream().findFirst();
count
:统计流中元素个数(max:最大值、min:返回流中最小值)
List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 计算列表中元素的数量,这里为4
long count = list.stream().count();
forEach
:对流中的每个元素执行指定操作
List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 对列表中的每个字符串进行打印输出
list.stream().forEach(s -> System.out.println(s));
reduce
:通过累加器将流中的所有元素组合成一个单一的值。能实现对集合求和、求乘积和求最值操作。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 将数字列表中的所有数字相加,得到总和
Optional<Integer> sum = numbers.stream().reduce((x, y) -> x + y);
collect
:将流转换为指定类型的集合或数组等数据结构
List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 将列表转换为一个Set对象
Set<String> set = list.stream().collect(Collectors.toSet());
// 将列表转换为一个map对象
Map<String, Integer> map = stream.collect(Collectors.toMap(item -> item.getKey(), item -> item.getValue()));
// 合并流中所有元素为一个字符串
String result = stream.collect(Collectors.joining(","));
// 统计流中元素的个数、总和、平均值、最大值和最小值:
IntSummaryStatistics stats = stream.collect(Collectors.summarizingInt(Integer::intValue));
// 对流中的元素根据条件进行分组
Map<String, List<Person>> groups = stream.collect(Collectors.groupingBy(Person::getGroup));
// 对流中的元素根据条件进行分区(key只能为true、false):
Map<Boolean, List<Person>> partitions = stream.collect(Collectors.partitioningBy(Person::isAdult));
ps:流只能使用一次,进行了终止操作后,该流就不能在使用了,会报错。
简单案例
假设有一个Student类,其中包含属性id、name、age、gender。现在有一个List对象students,需要对其中的元素进行以下操作:
- 将年龄大于等于18岁的女性过滤出来,并按照姓名从A到Z排序;
- 对结果中的每个元素调用一个Web API获取地址信息,然后将地址信息添加到Student对象的address属性中;
- 对结果按照地址排序,并将结果输出为字符串。
List<Student> students = new ArrayList<>();
students.add(new Student(1, "张三", 22, Gender.MALE));
students.add(new Student(2, "李四", 17, Gender.FEMALE));
students.add(new Student(3, "王五", 20, Gender.FEMALE));
students.add(new Student(4, "赵六", 18, Gender.MALE));
String result = students.stream()
.filter(s -> s.getAge() >= 18 && s.getGender() == Gender.FEMALE)
.sorted(Comparator.comparing(Student::getName))
.map(s -> {
String address = WebAPI.getAddress(s.getName());
s.setAddress(address);
return s;
})
.sorted((s1, s2) -> s1.getAddress().compareTo(s2.getAddress()))
.map(s -> s.toString())
.collect(Collectors.joining(","));
System.out.println(result);
假设有一个Person类,其中包含属性name、age、gender和job。现在有一个List对象persons,需要对其中的元素进行以下操作:
- 将所有Person对象按照性别分组;
- 对于每个性别,将其内部的Person对象按照照职业进行分组;
- 对于每个职业,统计该职业的人数。
List<Person> persons = new ArrayList<>();
persons.add(new Person("张三", 22, Gender.MALE, "程序员"));
persons.add(new Person("李四", 17, Gender.FEMALE, "销售"));
persons.add(new Person("王五", 20, Gender.FEMALE, "程序员"));
persons.add(new Person("赵六", 18, Gender.MALE, "销售"));
Map<Gender, Map<String, Long>> result = persons.stream()
.collect(Collectors.groupingBy(Person::getGender, Collectors.groupingBy(Person::getJob, Collectors.counting())));
System.out.println(result);
扩展
parallelStream并行流
stream
是顺序流,由主线程按顺序对流执行操作,而parallelStream
是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求,同时要注意线程安全
创建方式
- 调用集合类的parallelStream()方法,将普通的串行流转换为并行流,例如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Stream<Integer> stream = numbers.parallelStream();
- 使用Stream接口中的静态方法parallel()创建并行流,例如:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6).parallel();
- 使用Arrays类的parallelStream()方法创建并行流,例如:
int[] numbers = {1, 2, 3, 4, 5, 6};
IntStream stream = Arrays.parallelStream(numbers);
Optional
Optional
类是一个可以为null
的容器对象。如果值存在则isPresent()
方法会返回true
,调用get()
方法会返回该对象。
声明式编程/命令式编程?
声明式编程与命令式编程是两种不同的编程范式。在命令式编程中,程序员需要指定计算机执行的具体步骤和控制流程,即通过一系列的命令来改变计算机的状态,从而实现要求的功能。而在声明式编程中,程序员则更关注于描述问题本身,而非实现解决问题的步骤。
简单来说,声明式编程将问题定义为一个表达式或者问题的规则集合,然后利用现有的函数、模块或库来求解这个问题。