声明:该文章所有内容均由Cay S.Horstmann所著《Java核心技术卷2 高级特性》的内容归纳整理而来。
流与集合的关系
Java的流库,是在Java SE8中引入,用来以“做什么而非怎么做”的方式处理集合。具体说来,以统计长单词个数为例:
传统的集合方式统计长单词个数。
packages streams;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.io.IOException;
import java.util.Arrays;
public class CountLongWords
{
String content=new String(Files.readAllBytes(Paths.get("C:/Java/myclasses/logs/java.log")),StandardCharsets.UTF_8);
List<String> words=Arrays.asList(content.split("\\PL+"));
int count=0;//用于记录长单词个数
for(String s:words)
{
if(s.length()>12)
count++;
}
可以看到,用传统的集合的方式处理长单词计数问题需要明确告知计算机如何处理以及怎样处理。
然后,我们再来看以流的方式处理长单词数量问题。
packages streams;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.io.IOException;
import java.util.Arrays;
public class CountLongWords
{
String content=new String(Files.readAllBytes(Paths.get("C:/Java/myclasses/logs/java.log")),StandardCharsets.UTF_8);
List<String> words=Arrays.asList(content.split("\\PL+"));
int count=0;//用于记录长单词个数
count=words.stream().filter(w->w.length()>12).count();
可以看到只需要简单的告诉计算机需要做什么,就能得到我们需要的结果。流的处理方式为程序优化提供了可能。
流与集合的区别
1.流不实际存储元素。
2.流处理集合时不会修改源数据
3.流一般具有惰性执行的特征,即当我们需要结果时,流才会执行相应的操作。
创建流
- Stream.of(T…t)
创建具有指定元素的流 - Stream.empty()
创建一个空的流 - Stream.generate(Supplier s)
Stream.generate(()->“abc”):创建单个元素为abc的流
Stream.generate(Math::Random):创建单个随机数的流 - Stream.iterate(T seed, UnaryOperator f)
Stream.iterate(BigInteger.ZERO,n->n.add(BigInteger.ONE)) 创建一个0,1,2…的无限序列的流 - Arrays.stream(T[] Array,int start,int end)
创建一个元素从数组索引start开始end截止的流 - Pattern.compile(String regex).splitAsStream(CharSequence input)
- Files.lines(Path path,Charset cs)
创建一个流,他的元素为指定文件的行,文件的字符集为cs.
流转换(filter/map/flatMap)
- filter(Predicate<?extends T> predicate)
将流转换为另一个流,另一个流的元素需要满足某个条件
注:函数式接口Predicate含有抽象方法boolean test(T t) - map(Function<T,R> function)
将流转换为另一个流,另一个流中的元素由对原有流中的每一个元素执行给定的方法转化而来 - flatMap(Function<T,R> function)
给定的方法会将原有流中的元素转为流。flatMap与map的区别在于map会将由原有流转换为流的流,而flapMap会将流元素铺开。
注:函数式接口Function<T,R>含有抽象方法R apply(T t)
抽取子流和连接流
- Stream limit(long maxsize)
产生一个流,包含了当前流中的前maxsize个元素 - Stream skip(long maxsize)
产生一个流,它的元素是除了当前流中的前maxsize个元素之外的其它元素 - static Stream concat(Stream<? extends T> a,Stream<? extends T> b)
产生一个流,它的元素是a的元素后面跟着b的元素
其它的流转换
- Stream distinct()
产生一个流,它的元素保持原有的顺序并剔除重复元素 - Stream sorted()
产生一个流,它的元素包含原有流中的全部元素并以自然顺序排列
注:原有流中的元素需要实现Comparable接口,否则会抛出ClassCaseException异常 - Stream sorted(Comparator<? super T> comparator)
产生一个流,它的元素包含原有流中的元素,并按照给定顺序排列
注:Comparator为函数式接口,存在唯一的抽象方法int compare(T t1,T t2),故可用Lambda表达式表示该接口对象 - Stream peek(Consumer<? super T> action)
产生一个流与原有流完全一样,只是在遍历原有流中的每个元素时,执行给定的函数
注:Consumer接口为函数式接口, void accept(T t)为该接口的唯一的抽象方法,故可用Lambda表达式表示该接口对象,可以用来验证流的惰性处理的属性,即需要结果时才会执行相关的操作
简单约简
约简即为将流转化为可以在程序中直接使用的非流值,约简操作为终结操作,即约简之后的流
被关闭。
- long count()
返回当前流中的元素个数 - Optional max(Comparator<? super T> comparator)
返回按照给定顺序流中的最大值 - Optional min(Comparator<? super T> comparator)
返回按照给定顺序流中的最小值 - Optional findFirst()
返回流中的第一个元素 - Optional findAny()
返回流中的任意一个元素 - boolean anyMatch(Predicate<? super T> test)
流中存在匹配的元素则返回true,否则返回false - boolean noneMatch(Predicate<? super T> test)
流中不存在匹配元素则返回true,否则返回false - boolean allMatch(Predicate<? super T> test)
流中所有元素匹配则返回true,否则返回false
注:Predicate为函数式接口,boolean test(T t)为该接口唯一的抽象方法
Optional
Optional如何使用
- T orElse(T t)
如果Optional对象不为空,则返回Optional的值,否则返回其它值t - T orElseGet(Supplier<? super T> get)
如果Optional对象不为空,则返回Optional的值,否则返回给定方法的返回值 - T orElseThrow(Supplier<? extends X> get)
如果Optional对象不为空,则返回Optional对象的值,否则返回由给定方法产生的异常。
注:Supplier接口为函数式接口,T get()为该接口中唯一的抽象方法 - Optional map(Function<R,T> f)
将Optional对象的值按照给定方法包装成另一个Optional对象,如果Optional对象不为空或者方法调用的结果不为空,则生成的Optional对象不为空.
注:Function接口为函数式接口,R apply(T t)为该接口中唯一的抽象方法 - Optional flatMap(Function<R,T> f)
Optional对象如何创建
-
static Optional of(T t)
-
static Optional empty()
-
static Optional ofNullable(T value)
如果value为空,则为Optional.empty()否则为Optional.of(value)
收集结果
1. void forEach(Consumer<? super T> action)
对流中每一个元素均执行给定的方法,为终结操作,即方法调用后流被关闭
s.filter(w->w.length()>12).limit(100).distinct().forEach(e->System.out.println(e));//将的长度大于12的前一百的去重的字符输出
2. A[] toArray(IntFunction<A[]> generator)
产生一个包含流中元素类型为A的数组,为终结操作,即方法调用后流被关闭
String[] A=words.toArray(String[]::new)//将流Stream<String> words转为字符串数组
3. <R,A> R collect(Collector<? super T,A,R> collector)
3.1 static Collector<T,?,List> toList() 终结操作
List<String> A=words.collect(Collectors.toList());//将流Stream<String> words转为List<String> A
3.2 static static Collector<T,?,Set> toSet() 终结操作
Set<String> A=words.collect(Collectors.toSet());//将流Stream<String> words转为Set<String> A
3.3 static <T,C extends Collection> Collector<T,?,C> toCollection(Supplier collectionFactory) 终结操作
TreeSet<String> A=words.collect(Collectors.toCollection(TreeSet::new);//将流Stream<String> words转为TreeSet<String> A
3.4 static Collector<CharSequence,?,String> joining() 终结操作
String A=words.collect(Collectors.joining());//将流Stream<String> words中的元素连接为字符串A
3.5 static Collector<CharSequence,?,String> joining(CharSequence delimiter) 终结操作
String A=words.collect(Collectors.joining(","));//将流Stream<String> words中的元素用逗号间隔,连接为字符串A
3.6 static Collector<T,?,Int(Double/Long)SummaryStatistics> summarizingInt(Double/Long)(ToInt(Double/Long)Function<? super T> mapper) 终结操作
IntSummaryStatistics result=words.collect(Collectors.summarizingInt(w->w.length()));
System.out.println(result.getCount());//获取元素个数
System.out.println(result.getSum());//单词长度总和
System.out.println(result.getMax());//单词长度最大值
System.out.println(result.getMin());//单词长度最小值
System.out.println(result.getAverage());//单词长度平均值
3.7 Iterator iterator() 终结操作
收集到映射表
- static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper,Function<? super T,? extends U> valueMapper)
得到一个映射表,它的键和值为将对应的方法应用到每个输入的元素得到,当键值重复时,会怕抛出IllegalStateException异常
Map<String,String> s=stream.collect(Collectors.toMap(Locale::getDisplayLanguage,l->l.getDisplayLanguage(l));
- static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,?extends K> keyMapper,Function<? super T,? extends U> valueMapper,
BinaryOperator mergeFunction)
得到一个映射表,它的键和值为将对应的方法应用到每个输入的元素得到,当键重复时,会调用函数接口中方法对值进行处理
Map<String,String> m=stream.collect(Collectors.toMap(Locale::getDisplayLanguage,l->l.getDisplayLanguage(l),(a,b)->a);//当键重复时,保留原有值,去掉新值
- static <T,K,U,M extends Map<K,U>> Collector<T,?,M> toMap(Function<? super T,? extends K> keyMapper,Function<? super T,? extends U> valueMapper,BinaryOperator mergeFunction,Supplier mapFactory)
得到一个类型为M的映射表,它的键和值为将对应的方法应用到每个输入的元素得到,当键重复时,会调用函数接口中方法对值进行处理
TreeMap<String,String> m=stream.collect(Collectors.toMap(Locale::getDisplayLanguage,l->l.getDisplayLanguage(l),(a,b)->a,TreeMap::new);//得到一个TreeMap,当键重复时,保留原有值,去掉新值
群组和分区
- static <T,K> Collector<T,?,Map<K,List>> groupingBy(Function<? super T,? extends K> classifier)
产生一个映射表,其键是由分类函数应用于流元素得到,值为与键匹配的一个个列表
Map<String,List<Locale>> s=Stream.of(Locale.getAvailableLocales()).collect(Collectors.groupingBy(Locale::getCountry));//产生一个以国家分组的映射表
- static Collector<T,?,Map<Boolean,List>> partitioningBy(Predicate<? super T> predicate)
产生一个映射表,键由true/false构成,值由满足/不满足的断言元素构成的一个个列表
Map<Boolean,List<Locale> s=Stream.of(Locale.getAvailableLocales()).collect(Collectors.partitioningBy(l->l.getCountry().equals("aaa")));//生成一个映射表,国家为aaa的元素在一个列表,不为aaa的在另一个列表
下游收集器
下游收集器即用于处理上文所述groupingBy、partitioningBy方法产生的每个值对应的列表。
常见的下游收集器如下:
- static Collector<T,?,Long> counting()
统计每个值的列表中的元素个数 - static Collector<T,?,Set> toSet()
将列表转换为集,故列表中元素会被去重 - static Collector<T,?,Integer/Double/Long> summing(Int/Double/Long)(To(Int/Double/Long)Function<? super T> mapper)
按照某个维度对列表中元素进行求和 - static Collector<T,?,Optional> maxBy(Comparator<? super T> comparator)
按照给定的Comparator去列表中的最大元素 - static Collector<T,?,Optional> minBy(Comparator<? super T> comparator)
按照给定的Comparator去列表中的最小元素 - static Collector<T,?,(Int/Long/Double)SummaryStatistics> summarizing(Int/Long/Double)(To(Int/Long/Double)Function<? super T> mapper)
按照给定维度,统计列表中的数据 - static <T,U,A,R> Collector<T,?,R> mapping(Function<? super T,? extends U> mapper,Collector<? super U,A,R> downstream)
将列表中的元素按照给定的mapper方法转化成另一个列表,然后用给定的下游收集器downstream处理该列表
基本类型流
对于数据为基本数据类型的流,我们均是使用将其元素包装为对应的包装器类型的对象流。这种处理方式,会降低程序执行效率,因此我们在这里介绍基本类型流。基本类型流包含三种:
- IntStream可以用于存储short、char、byte、boolean以及int类型的数据
- LongStream用于存储long类型数据
- DoubleStream用于存储float和double类型的数据
创建基本类型流
三种基本类型流的创建方法相同,故这里我们以IntStream创建方法为例:
- IntStream.of(int…T)
- Arrays.stream(int[] args,int fromidex,int toindex)
- static IntStream generate(IntSupplier s)
- static IntStream iterate(int seed,IntUnaryOperator f)
- static IntStream range(int startInclusive,int endExclusive)
产生一个步长为1的[startinclusive,endExclusive)的序列 - static IntStream rangeClosed(int startInclusive,int endExclusive)
产生一个步长为1的[startinclusive,endExclusive]的序列
对象流与基本数据类型流之间的转换
- 可以通过mapToInt(ToIntFunction<? super T> mapper)、mapToLong(ToLongFunction<? super T> mapper)以及mapToDouble(ToDoubleFunction<? super T> mapper)方法,将对象流转化为对应的基本类型流
- 基本类型流调用boxed()转化为包装器对象流
对象流与基本数据类型流的使用区别
- 基本类型流调用toArray()方法,会产生对应基本数据类型数组
- 基本类型流有sum()、average()、max()以及min()分别用于求和,平均值、最大值以及最小值方法。
- 基本类型流调用SummaryStatistic()方法获取对应的统计数据
并行流
获取并行流
- 集合调用parallelStream()方法获取并行流
- 顺序流调用parallel()方法,转换为并行流
并行流使用时避免因为并发而竞争共享资源
//我们以统计字符串流中长度小于12的各个长度的字符串数量为例
int wordsLen=new int[12];
stream.parallel().forEach(s->{
if(s.length()<=12)
wordsLen[s.length]++;
});
由于在并行的程序中,各个进程会同时修改数组wordsLen导致每次执行的结果都不会一致。
stream.parallel().filter(s->s.length()<=12).collect(Collectors.groupingBy(String::length,Collectors.counting()));
上述方法能够避免在并行程序中,由于共享资源导致运行结果不一致的问题。
并行流的使用
- 排序与并行执行并不矛盾。并行流stream在调用map(Function<? super T,? extends R> mapper)做转换时,会保持原有的流中的元素顺序
- 当然,如果我们放弃对于排序的需求时,某些操作可以更为高效的并行化 。我们可以调用unordered()方法声明放弃排序需求。
2.1 limit(n)
在放弃排序需求的并行流中调用limit方法,会随机返回n个元素
2.2 distinct()
在有序流中,调用distinct方法会保留重复的第一个元素。但是在并行流中,各个进程无法在执行时确定第一个元素,故如果我们保留排序需求,只有在各个进程运行结束时,才能够确定保留哪一个重复元素。但是当我们放弃排序需求时,则只需要按照各个进程的执行进度,保留所有进程最先执行的那个元素即可。
最后,需要强调使用流时需要注意不要在流中修改元素会被其它流使用的集合。