1. 流的两个重要特点:流水线、内部迭代
<1> 流水线:
很多流操作本身会返回一个流,多个这样的操作链接起来,形成一个大的流水线。
<2> 内部迭代:
与使用迭代器显式迭代的集合不同,流使用内部迭代,即stream库内部帮你把迭代实现了。
2. 流与集合的两个重要区别:结构、迭代
<1> 结构:
流是概念上固定的数据结构,其元素是需计算,即只有在需要的时候才计算值。
集合是一个内存中的数据结构,它包含数据结构中目前所有的值,即集合中的每个元素都得先算出来才能添加到集合中。
举列:
集合就像DVD里的电影,它完整的保存了整个电影,是在之前已经处理完成,再放到DVD里。
流就像网上在线看电影,不是非得把整个电影都下载完成后再来看,而是根据需要计算下载。
<2> 迭代:
流使用内部迭代,即stream库内部帮你把迭代做了。
集合使用外部迭代,即需要自己使用Collection接口做外部显示迭代。
内部迭代的好处:可以自动选择一种适合硬件的数据表示和并行实现,即以更优化的顺序进行处理。
流-内部迭代.png 集合-外部迭代.png注意:
流只能遍历一次,遍历完之后,这个流已经被消费掉了。你可以从原始数据源那里再获取一个新的流来重新遍历一遍。
如下代码多次遍历流,导致抛出异常:
Stream<String> stream = Arrays.asList("a", "b", "c").stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println);
多次遍历流,抛出异常:
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at com.fan.demo.DemoTest.main(DemoTest.java:115)
3. 流的两类操作:中间操作、终端操作
流的中间与终端操作.png<1> 中间操作:
流的中间操作不执行任何处理,它们会返回一个流,多个中间操作一般都可以合并起来,在终端操作时一次性全部处理。
<2> 终端操作:
终端操作会从流的流水线生成结果,其结果不是流,而是如List、Integer、void的值。
4. 流的使用
构建流:
Stream.of -- 通过显式值创建一个流(接受任意类型数量的参数)
Stream<String> stream = Stream.of("Hello World", "James", "Ok");
stream.map(String::toUpperCase).forEach(System.out::println);
输出:
HELLO WORLD
JAMES
OK
创建一个空流:
Stream<String> emptyStream = Stream.empty();
Arrays.stream -- 通过数组创建一个流(接受一个数组作为参数)
int[] numbers = {3, 2, 5, 11, 13, 7};
int sum = Arrays.stream(numbers).sum(); --- 总和41
Files.lines -- 由文件生成流(返回一个由指定文件中的各行构成的字符串流)
Stream.iterate、Stream.generate -- 由函数生成无限流(应该使用limit(n)来加以限制)
Stream.iterate方法接受一个初始值,实际应用在需要依次生成一系列值的时候,比如一些列日期:1月31日,2月1日
注意:Stream.iterate方法是纯粹不变的:它没有修改现有状态,在每次迭代时会创建新的元组。
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
输出:
0
2
4
6
8
使用Stream.iterate构建一个斐波那契数列:(斐波那契数列中前两个数字是0,1,后续的每个数字 = 前两个数字之和)
先构建一个斐波那契元组,格式为:(数列中数字,后续数字),如下:
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]}).limit(10).forEach(t -> System.out.println("(" + t[0] + "," + t[1] + ")"));
输出:
(0,1)
(1,1)
(1,2)
(2,3)
(3,5)
(5,8)
(8,13)
(13,21)
(21,34)
(34,55)
使用map提取每个元组中的第一个元素,则构建的斐波那契数列为:
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]}).limit(10).map(t -> t[0]).forEach(System.out::println);
输出:
0
1
1
2
3
5
8
13
21
34
Stream.generate方法接受一个Supplier<T>类型的Lambda提供新的值
Stream.generate(Math::random).limit(5).forEach(System.out::println);
输出:
0.7017580650263067
0.5551699003184056
0.17273970322460497
0.5153472880449076
0.8041474317735003
筛选:
filter(谓词) -- 用谓词筛选(谓词:一个返回boolean的函数)
找出所有偶数,并返回列表
List<Integer> numbers = Arrays.asList(8, 9, 100, 200, 8, 18, 23, 100);
List<Integer> evenNumList = numbers.stream().filter(i -> i % 2 == 0).collect(Collectors.toList()); --- [8, 100, 200, 8, 18, 100]
distinct -- 去重(根据流所生成元素的hashCode和equals方法实现筛选各异元素)
List<Integer> numbers = Arrays.asList(8, 9, 100, 200, 8, 18, 23, 100);
numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
输出:
8
100
200
18
limit(n) -- 截短流(截取前n个元素)
找出所有偶数,并截取走前2两个
Stream.of(8, 9, 100, 200, 8, 18, 23, 100).filter(i -> i % 2 == 0).limit(2).forEach(System.out::println);
输出:
8
100
skip(n) -- 跳过元素(扔掉前n个元素,流中元素不足n个,返回空流)
找出所有偶数,并扔掉前两个
Stream.of(8, 9, 100, 200, 8, 18, 23, 100).filter(i -> i % 2 == 0).skip(2).forEach(System.out::println);
输出:
200
8
18
100
映射
map(函数) -- 对流中每一个元素应用函数,将其映射为一个新元素(不去修改,创建新版本)
对每个单词求其长度并返回列表
List<String> words = Arrays.asList("Hello", "Are", "You", "Ok");
List<Integer> wordLengths = words.stream().map(String::length).collect(Collectors.toList()); --- [5, 3, 3, 2]
flatMap -- 对流中每个元素都转换成另一个流,并把所有的流连接起来成为一个流,即流的扁平化
对给定的单词,得到不同字符的列表:
List<String> words = Arrays.asList("Hello", "Are", "You", "Ok");
List<String> uniqueCharacters = words.stream().map(w -> w.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
--- [H, e, l, o, A, r, Y, u, O, k]
查找和匹配
anyMatch(谓词) -- 流中是否至少有一个元素能匹配谓词
是否至少有一个元素大于100
boolean flag = Stream.of(8, 9, 100, 200, 8, 18, 23, 100).anyMatch(i -> i > 100); --- true
allMatch(谓词) -- 流中是否所有的元素都匹配谓词
是否所有都为偶数
boolean flag = Stream.of(8, 9, 100, 200, 8, 18, 23, 100).allMatch(i -> i % 2 == 0); --- false
noneMatch(谓词) -- 是否流中没有元素与谓词匹配
是否没有元素大于200
boolean flag = Stream.of(8, 9, 100, 200, 8, 18, 23, 100).noneMatch(i -> i > 200); --- true
findAny() -- 返回当前流中的任意元素
返回当前流中长度大于2的任意元素
Optional<String> word = Stream.of("Hello World", "James", "Ok").filter(e -> e.length() > 2).findAny(); --- Hello World
如果流中没有满足条件的元素,则findAny()返回空(Optional.empty)
找出流中长度大于2的任意元素,如果存在,按如下格式输出(结合Optional对象的ifPresent(函数) ):
Stream.of("Hello World", "James", "Ok").filter(e -> e.length() > 2).findAny().ifPresent(s -> System.out.println("[" + s + "]'s length = "+ s.length()));
--- [Hello World]'s length = 11
findFirst() -- 查找第一个元素
对流中元素求平方,找出第一个能被3整除的元素
Optional<Integer> firstElement = Stream.of(1, 2, 3, 4, 5, 6).map(x -> x * x).filter(x -> x % 3 == 0).findFirst(); --- 9
归约
reduce -- 归约(将流中元素反复结合起来,得到一个值)
元素求和:
int sum = Stream.of(5, 6, 7, 8).reduce(0, (a, b) -> a + b); --- 26
int sum = Stream.of(5, 6, 7, 8).reduce(0, Integer::sum); --- 26
无初始值,返回Optional对象
Optional<Integer> sumOpt = Stream.of(5, 6, 7, 8).reduce((a, b) -> (a + b)); --- 26
求最大值:
int max = Stream.of(10, 20, 0, 11, 100, 250).reduce(0, Integer::max); --- 250
Optional<Integer> maxOpt = Stream.of(10, 20, 0, 11, 100, 250).reduce(Integer::max); --- 250
求最小值:
int min = Stream.of(10, 20, 0, 11, 100, 250).reduce(0, Integer::min); --- 0
Optional<Integer> minOpt = Stream.of(10, 20, 0, 11, 100, 250).reduce(Integer::min); --- 0
map-reduce模式:(map和reduce的连接)
计算单词的个数:
int count = Stream.of("Hello", "James", "Ok").map(e -> 1).reduce(0, Integer::sum); --- 3 (将流中每个元素映射成数字1)
long count = Stream.of("Hello", "James", "Ok").count(); --- 3
特化流 IntStream、DoubleStream、LongStream
特化流IntStream、DoubleStream、LongStream分别将流中的元素特化为int、long和double,从而避免了暗含的装箱操作。
mapToInt、mapToDouble、mapToLong -- 将流转化成特化流
计算每个字符串的长度,并返回一个IntStream特化流(不是一个Stream<Integer>),然后调用IntStream接口中的sum方法求和:
如果流为空,则sum默认返回0
int sum = Stream.of("Hello World", "James", "Ok").mapToInt(String::length).sum(); --- 18
数值范围:
rangeClosed(参数1, 参数2) -- 生成两个参数之间的所有数字,包含第二个参数
IntStream evenNum = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
System.out.println(evenNum.count()); --- 50
range(参数1, 参数2) -- 生成两个参数之间的所有数字,不包含第二个参数
IntStream evenNum = IntStream.range(1, 100).filter(n -> n % 2 == 0);
System.out.println(evenNum.count()); --- 49
boxed方法 -- 将特化流转回一般的对象流
IntStream intStream = Stream.of("Hello World", "James", "Ok").mapToInt(String::length);
Stream<Integer> stream = intStream.boxed();
Optional原始类型特化版本:OptionalInt、OptionalDouble、OptionalLong
计算每个字符串的长度,并返回一个IntStream特化流,调用其max方法,返回一个OptionalInt特化对象:
OptionalInt maxLength = Stream.of("Hello World", "James", "Ok").mapToInt(String::length).max(); --- 11
收集器:
为了便于举例,定义一个Person类,如下:
public class Person {
private final String name;
private final String sex;
private final int age;
private final Job job;
private final boolean partyMember;
public Person(String name, String sex, int age, Job job, boolean partyMember) {
this.name = name;
this.sex = sex;
this.age = age;
this.job = job;
this.partyMember = partyMember;
}
public enum Job { TEACHER, WORKER, DOCTOR, DRIVER, COOK}
...省略get,set方法
}
构建personList列表:
List<Person> personList = Arrays.asList(
new Person("李铁蛋", "男",31, Person.Job.WORKER, true),
new Person("王翠花", "女", 22, Person.Job.TEACHER, false),
new Person("牛建国", "男", 62, Person.Job.DRIVER, false),
new Person("Lucy Rose", "女", 42, Person.Job.DOCTOR, true),
new Person("尼古拉斯蛋蛋", "男", 51, Person.Job.COOK, false)
);
构建流:
Stream<Person> personStream = personList.stream();
Collectors.toList() -- 把流中所有元素收集到一个List
List<Person> persons = personStream.collect(Collectors.toList());
Collectors.toSet() -- 把流中所有元素收集到一个Set,删除重复项
Set<Person> persons = personStream.collect(Collectors.toSet());
Collectors.toCollection -- 把流中所有元素收集到给定的供应源创建的集合
Collection<Person> persons = personStream.collection(Collections.toCollection(ArrayList::new));
Collectors.counting() -- 计算流中元素个数
long count = personStream.collect(counting());
等价于
long count = personStream.count();
对流中元素相应类型的属性求和
Collectors.summingInt、Collectors.summingLong、Collectors.summingDouble:
对所有人的年龄求和
int ageSum = personStream.collect(Collectors.summingInt(Person::getAge));
计算流中元素的平均值
Collectors.averagingInt、Collectors.averagingLong、Collectors.averagingDouble(返回类型都为double):
求所有人年龄的平均值
double ageAver = personStream.collect(Collectors.averagingInt(Person::getAge));
计算流中元素对应类型属性的统计值(最大值,最小值,总和,平均值,数量)
Collectors.summarizingInt、Collectors.summarizingLong、Collectors.summarizingDouble:
对所有人的年龄求统计值
IntSummaryStatistics ageStatistics = personStream.collect(Collectors.summarizingInt(Person::getAge));
输出:
IntSummaryStatistics{count=5, sum=208, min=22, average=41.600000, max=62}
Collectors.joining -- 连接字符串(内部使用StringBuilder来连接)
String personName = personStream.map(Person::getName).collect(Collectors.joining(", "));
Collectors.maxBy -- 按照指定比较器选出最大元素(被Optional包裹)
选出年龄最大的人
Optional<Person> maxAgePerson = personStream.collect(Collectors.maxBy(Comparator.comparingInt(Person::getAge)));
Collectors.minBy -- 按照指定比较器选出最小元素(被Optional包裹)
选出年龄最小的人
Optional<Person> minAgePerson = personStream.collect(Collectors.minBy(Comparator.comparingInt(Person::getAge)));
Collectors.reducing -- 从一个作为累加器的初始值开始,利用BinaryOperator与流中的元素逐个结合,从而将流归约为单个值。
对所有人的年龄求和
int ageSum = personStream.collect(Collectors.reducing(0, Person::getAge, Integer::sum));
Collectors.groupingBy -- 对流中元素进行分组,返回结果Map
根据元素的一个属性的值对元素进行分组,分组结果是个Map,Map的key为属性的值,Map的value为属于这个属性的元素列表。
按职业对人进行分组
Map<Person.Job, List<Person>> personsByJob = personStream.collect(Collectors.groupingBy(Person::getJob));
输出:
{
COOK=[Person{name='尼古拉斯蛋蛋', sex='男', age=51, job=COOK}],
TEACHER=[Person{name='王翠花', sex='女', age=22, job=TEACHER}],
DRIVER=[Person{name='牛建国', sex='男', age=62, job=DRIVER}],
DOCTOR=[Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR}],
WORKER=[Person{name='李铁蛋', sex='男', age=31, job=WORKER}]
}
按分的子组收集数据:即传递收集器作为groupingBy的第二个参数。
例1:按性别分组,并计算每组的人数:
Map<String, Long> sexCount = personStream.collect(Collectors.groupingBy(Person::getSex, Collectors.counting()));
输出:
{女=2, 男=3}
例2:按性别分组,并查找每组中年龄最大的人:
Map<String, Optional<Person>> maxAgeBySex = personStream.collect(Collectors.groupingBy(Person::getSex, Collectors.maxBy(Comparator.comparingInt(Person::getAge))));
输出:
{
女=Optional[Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR}],
男=Optional[Person{name='牛建国', sex='男', age=62, job=DRIVER}]
}
说明:这个Map中的值是Optional,是由于maxBy收集器返回的类型,实际上,如果不存在某个性别,就不会对应一个Optional.empty()值,根本不会出现在Map的键中,所以这个Optional包装器在这里不是很有用。
支持多级分组,即可以把一个内层groupingBy传递给外层groupingBy,并定义一个为流中元素分类的二级标准。
先按性别分组,然后再按职业分组:
Map<String, Map<Person.Job, List<Person>>> personBySexAndJob = personStream.collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getJob)));
输出:
{
女={
DOCTOR=[Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR}],
TEACHER=[Person{name='王翠花', sex='女', age=22, job=TEACHER}]
},
男={
COOK=[Person{name='尼古拉斯蛋蛋', sex='男', age=51, job=COOK}],
DRIVER=[Person{name='牛建国', sex='男', age=62, job=DRIVER}],
WORKER=[Person{name='李铁蛋', sex='男', age=31, job=WORKER}]
}
}
Collectors.collectingAndThen -- 包裹另一个收集器,对其结果应用转换函数
这个工厂方法接受两个参数,即要转换的收集器和转换函数。该方法相当于对旧收集器的一个包装,collect操作的最后一步将返回值用转换函数做一个映射。
如下例子:collectingAndThen包裹起maxBy收集器,而转换函数Optional::get则把返回的Optional中的值提取出来。
按性别进行分组,并找出每组中年龄最大的人:
Map<String, Person> personMap = personStream.collect(Collectors.groupingBy(Person::getSex,
Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Person::getAge)),Optional::get)));
输出:
{
女=Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR, partyMember=true},
男=Person{name='牛建国', sex='男', age=62, job=DRIVER, partyMember=false}
}
Collectors.partitioningBy(谓词) -- 分区(分组的特殊情况),即谓词(返回一个boolean值的函数)作为分类函数,进行分组。
根据是否为党员对人进行分组:
Map<Boolean, List<Person>> personMap = personStream.collect(Collectors.partitioningBy(Person::isPartyMember));
输出:
{
false=[
Person{name='王翠花', sex='女', age=22, job=TEACHER, partyMember=false},
Person{name='牛建国', sex='男', age=62, job=DRIVER, partyMember=false},
Person{name='尼古拉斯蛋蛋', sex='男', age=51, job=COOK, partyMember=false}
],
true=[
Person{name='李铁蛋', sex='男', age=31, job=WORKER, partyMember=true},
Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR, partyMember=true}
]
}
可以传递收集器作为partitioningBy的第二个参数:
例1:统计党员与非党员的人数:
Map<Boolean, Long> personCount = personStream.collect(Collectors.partitioningBy(Person::isPartyMember, Collectors.counting()));
输出:{false=3, true=2}
例2:先按是否为党员,然后再按性别进行分组:
Map<Boolean, Map<String, List<Person>>> personMap = personStream.collect(Collectors.partitioningBy(Person::isPartyMember, Collectors.groupingBy(Person::getSex)));
输出:
{
false={
女=[Person{name='王翠花', sex='女', age=22, job=TEACHER, partyMember=false}],
男=[
Person{name='牛建国', sex='男', age=62, job=DRIVER, partyMember=false},
Person{name='尼古拉斯蛋蛋', sex='男', age=51, job=COOK, partyMember=false}
]
},
true={
女=[Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR, partyMember=true}],
男=[Person{name='李铁蛋', sex='男', age=31, job=WORKER, partyMember=true}]
}
}
例3:先按是否为党员分组,然后找出年龄最大的人:
Map<Boolean, Person> personMap = personStream.collect(Collectors.partitioningBy(Person::isPartyMember,
Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Person::getAge)), Optional::get)));
输出:
{
false=Person{name='牛建国', sex='男', age=62, job=DRIVER, partyMember=false},
true=Person{name='Lucy Rose', sex='女', age=42, job=DOCTOR, partyMember=true}
}
例4:写一个方法,接受参数int n,并将前n个自然数分为质数和非质数:
判断一个数是否为质数的方法:
public static boolean isPrime(int candidate) {
int candidateRoot = (int) Math.sqrt(candidate);
return IntStream.rangeClosed(2, candidateRoot).noneMatch(i -> candidate % i == 0);
}
创建一个包含n个自然数的流,用isPrime方法作为谓词进行分区:
public static Map<Boolean, List<Integer>> partitionPrimes(int n) {
return IntStream.rangeClosed(1, n).boxed().collect(Collectors.partitioningBy(candidate -> isPrime(candidate)));
}
网友评论