JavaSE8的流库
【8】收集结果
当流处理完以后,我们如果想要获得处理结果,如果是查看流中的元素,有三种方式:①传统的迭代器查看,②使用forEach方法查看,③使用forEachOrdered方法查看
package com.zxyy.stream.test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) throws IOException {
/*
* 测试2:测试3种方式遍历流
*/
String contents = new String(Files.readAllBytes(Paths.get("C:\\Users\\弓长小月\\Desktop\\Test Forlder\\alice30.txt")),StandardCharsets.UTF_8);
Stream<String> words = Stream.of(contents.split("\\PL+")).skip(1).limit(10);
//①使用传统的迭代器遍历流
Iterator<String> wordsIter = words.iterator();
while(wordsIter.hasNext()) {
System.out.print(wordsIter.next()+" ");
}
System.out.println();
//②是用forEach方法遍历流
Stream<Double> sd = Stream.iterate(1.0, n->n*2).limit(10);
sd.forEach((x)->System.out.print(x+" "));
System.out.println();
//③使用forEachOrdered方法遍历流
sd = Stream.iterate(1.0, n->n*2).limit(10);
sd.forEachOrdered((x)->System.out.print(x+" "));
System.out.println();
}
}
注意:在并行流上,forEach方法会按任意顺序对流中的元素进行操作,但是forEachOrdered方法会按照流中的顺序执行操作。
还有可能是我们对流处理完的结果需要收集到某种数据结构中,也是有三种方式:
①调用toArray方法收集到数组中
②调用Collectors类的toList方法收集到列表中
③调用Collectors类的toSet方法收集到集中
package com.zxyy.stream.test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) throws IOException {
/*
* 测试3:测试将流收集到数组,收集到集或者列表中
*/
String contents = new String(Files.readAllBytes(Paths.get("C:\\Users\\弓长小月\\Desktop\\Test Forlder\\alice30.txt")),StandardCharsets.UTF_8);
Stream<String> words = Stream.of(contents.split("\\PL+")).skip(1).limit(10);
//①将流收集到数组中
//注意toArray会返回一个Object类型数组
String[] wordsArr = words.toArray(String[]::new);
for(String w : wordsArr) {
System.out.print(w+" ");
}
System.out.println();
//②将流收集到列表中
Stream<Integer> is = Stream.iterate(2, n->2*n).limit(10);
List<Integer> wordsList = is.collect(Collectors.toList());
for(Integer w : wordsList) {
System.out.print(w+" ");
}
System.out.println();
//③将流收集到Set中
Stream<Double> random = Stream.generate(()->Math.random()*100).limit(5);
Set<Double> sd = random.collect(Collectors.toSet());
for(Double d : sd) {
System.out.print(d+" ");
}
}
}
收集结果时,往往还想要做些其他事情,比如指定数据结构,比如想在字符串中加上分隔符等等,我们再看看下面的示例:
package com.zxyy.stream.test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CollectingResultTest {
public static void main(String[] args) throws IOException {
Iterator<Integer> iter = Stream.iterate(1, n->n*2).limit(10).iterator();
while(iter.hasNext()) {
System.out.print(iter.next()+" ");
}
System.out.println();
Object[] numbers = Stream.iterate(0, n->n+1).limit(10).toArray();
System.out.println("Object array : "+numbers);
try {
Integer number = (Integer) numbers[0];
System.out.println("number : "+number);
System.out.println("The following statement throws an exception:");
Integer[] numbers2 = (Integer[])numbers;
} catch (ClassCastException e) {
System.out.println(e);
}
Integer[] numbers3 = Stream.iterate(0, n->n+1).limit(10).toArray(Integer[]::new);
System.out.println("Integer array"+numbers3);
Set<String> noVowelsSet = delVowels().collect(Collectors.toSet());
show("noVowelsSet",noVowelsSet);
TreeSet<String> noVowelsTreeSet = delVowels().collect(Collectors.toCollection(TreeSet::new));
show("noVowelsTreeSet",noVowelsTreeSet);
String result1 = delVowels().limit(10).collect(Collectors.joining());
System.out.println("joining result : "+result1);
String result2 = delVowels().limit(10).collect(Collectors.joining(","));
System.out.println("joining with commas result : "+result2);
IntSummaryStatistics summary = delVowels().limit(10).collect(Collectors.summarizingInt(String::length));//这不是一个int值,是一个对象
double averageSummary = summary.getAverage();
double maxwordLength = summary.getMax();
System.out.println("Average words length : "+averageSummary);
System.out.println("max word length : "+maxwordLength);
System.out.println("forEach : ");
delVowels().limit(10).forEach(System.out::print);
}
public static Stream<String> delVowels() throws IOException{
String contents = new String(Files.readAllBytes(Paths.get("C:\\Users\\弓长小月\\Desktop\\Test Forlder\\alice30.txt")),StandardCharsets.UTF_8);
List<String> wordsList = Arrays.asList(contents.split("\\PL+"));
Stream<String> words = wordsList.stream();
return words.map(s->s.replaceAll("[aeiouAEIOU]", ""));
}
public static <T> void show(String label,Set<T> set) {
System.out.print(label+" : "+set.getClass().getName());
System.out.println("["+set.stream().limit(10).map(Object::toString).collect(Collectors.joining(","))+"]");
}
}
【9】收集到映射表中
上面我们谈到了将流收集到List和Set集合中,下面我们看看如何收集到Map中。假设有一个Stream<Person>的流,里面是Person对象,我们可以用Collectors.toMap方法来收集Person对象:
Map<Integer,String> streamToMap = people.collect(Collectors.toMap(Person::getId,Person::getName));
上面这段代码是把Person对象的id作为key,把Person对象的name作为value。还有一种可能是把这个Person对象作为value,那么我们可以使用Function.identity()方法:
Map<Integer,Person> streamToMap = people.collect(Collectors.toMap(Person::getId,Function.identity()));
这其中有种情况,就是一个键可能会对应多个值,那样会产生冲突,抛出IllegalStateException,如何解决呢?这时我们可以对toMap方法添加第三个“参数”,让它指定一个新值来确定键所对应的值:
Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
Map<String,String> languageNames = locales.collect(Collectors.toMap(
Locale::getDisplayLanguage,
l->l.getDisplayLanguage(l),
(existingValue,newValue)->existingValue
));
那么如果我们需要多个值对应同一个key呢?我们可以考虑将多个值存入集合中,所以我们也可以为value创建一个集合:
Map<String,Set<String>> countryLanguageSets = locales.collect(Collectors.toMap(
Locale::getDisplayLanguage,
l->Collections.singleton(l.getDisplayLanguage()),
(a,b)->{
Set<String> union = new HashSet<String>(a);
union.addAll(b);
return union;
}
));
当然,如果你想要把得到的映射作为TreeMap,那么就要添加第四个“参数”:
TreeMap<Integer,Person> idToPerson = people.collect(Collectors.toMap(
Person::getId,
Function.identity(),
(existingValue,newValue)->{throw new IllegalStateException();}
TreeMap::new
));
综上所述,就是收集到映射中常用的方法。看一个完整的例子:
package com.zxyy.stream.test;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CollectingIntoMaps {
public static class Person{
private int id;
private String name;
public Person(int id,String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String toString() {
return getClass().getName()+"[id="+id+",name="+name+"]";
}
}
public static Stream<Person> people(){
return Stream.of(new Person(1001,"Peter"),new Person(1003,"Paul")
,new Person(1002,"Mary"));
}
public static void main(String[] args) {
Map<Integer,String> idToName = people().collect(Collectors.toMap(Person::getId, Person::getName));
System.out.println("idToName : "+idToName);
Map<Integer,Person> idToPerson = people().collect(Collectors.toMap(Person::getId, Function.identity()));
System.out.println("idToPerson : "+idToPerson.getClass().getName()+idToPerson);
idToPerson = people().collect(Collectors.toMap(Person::getId, Function.identity(), (existingValue,newValue)->{throw new IllegalStateException();}, TreeMap::new));
System.out.println("idToPerson : "+idToPerson.getClass().getName()+idToPerson);
Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
Map<String,String> languageNames = locales.collect(Collectors.toMap(Locale::getDisplayLanguage, l->l.getDisplayLanguage(l), (existingValue,newValue)->existingValue));
System.out.println("languageNames : "+languageNames);
locales = Stream.of(Locale.getAvailableLocales());
Map<String,Set<String>> countryLanguageSets = locales.collect(Collectors.toMap(
Locale::getDisplayCountry,
l->Collections.singleton(l.getDisplayLanguage(l)),
(a,b)->{
Set<String> union = new HashSet<String>(a);
union.addAll(b);
return union;
}
));
System.out.println("countryLanguageSets : "+countryLanguageSets);
}
}
【10】群组和分区
在上一节中,我们看到如何收集给定国家的所有语言,是为每个映射表的值生成一个单例集,然后将现有集合与新集合合并,这样显得复杂,我们可以直接使用一个分组的方法来支持它:
①群组的分类函数:groupingBy
//Locale::getCountry这个函数就是群组的分类函数
Map<String,List<Locale>> countryToLocale = locales.collect(Collectors.groupingBy(Locale::getCountry));
②分区函数:当分类函数时断言函数
//分区函数,当key为Boolean值时
Map<Boolean,List<Locale>> map = locales.collect(Collectors.partitioningBy(l->l.getLanguage().equals("en")));
分区函数会对流的值分为两个列表,一个是true对应的,一个是false对应的。下面看看示例:
package com.zxyy.stream.test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) throws IOException {
Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
Map<String,List<Locale>> countryToLocales = locales.collect(Collectors.groupingBy(Locale::getCountry));
List<Locale> swissLocales = countryToLocales.get("CH");
System.out.println(swissLocales);
//如果分类函数是断言函数(即返回boolean值的函数),就可以使用partitioningBy,该方法会产生两个列表,一个是true对应的,一个是false对应的
locales = Stream.of(Locale.getAvailableLocales());
Map<Boolean,List<Locale>> englishAndOtherLocales = locales.collect(Collectors.partitioningBy(l->l.getLanguage().equals("en")));
List<Locale> englishLocales = englishAndOtherLocales.get(true);
System.out.println(englishLocales);
List<Locale> anotherLocales = englishAndOtherLocales.get(false);
System.out.println(anotherLocales);
}
}
【11】下游收集器
groupingBy方法会查收一个映射列表,它的每一个值都是列表。如果想要以某种方式处理这些列表,就需要提供一个“下游收集器”。
(一)想要的不是列表而是集
Map<String,Set<Locale>> countryToLocaleSet = locales.collect(groupingBy(Locale::getCountry,toSet()));
(二)counting方法收集元素个数
Map<String,Long> countryToLocaleCounts = locales.collect(groupingBy(Locale::getCountry,counting));
(三)summing(Int|Long|Double)求和
Map<String,Integer> stateToCityPopulation = cities.collect(groupingBy(City::getState,summingInt(City::getPopulation)));
(四)maxBy和minBy产生最大值和最小值
//maxBy和minBy会接收一个比较器,产生最大或最小值
Map<String,Optional<City>> stateToLargestCity = cities.collect(groupingBy(City::getState,maxBy(Comparator.comparing(City::getPopulation))));
(五)mapping方法
就是说mapping方法也是一个下游收集器,只是没有像前几个方法那样直接,它里面可以传递一个函数或者是另一个下游收集器。
Map<String,Optional<String>> stateToLongestCityName = cities.collect(groupingBy(City::getState,mapping(City::getName,maxBy(Comparator.comparing(String::length)))));
【12】约简操作
reduce方法是一种用于从流中计算某个值的通用机制。它接收一个函数,比如我们想要做加法:
List<Integer> values = ...;
Optional<Integer> sum = values.stream().reduce((x,y)->x+y);
这个方法就像求和方法summing一样,它会计算v0+v1+v2+v3+...,如果reduce方法有一项约简操作op,那么reduce方法就会计算v0 op v1 op v2 op v3...,这项操作是有限定的,那就操作op是可以结合的,例如乘法a*(b*c),可结合为(a*b)*c,像减法就不能结合。
【13】基本类型流
到目前为止,我们涉及的流都是对象流,但是如果是基本类型值,要包装成对象再变成流,会降低效率。为了解决这个问题,Java也提供了基本类型的流。不像基本数据类型对应的对象那样,每一个都有自己对应的对象,而基本类型流有自己的分类。
① int、byte、short、char、boolean类型归纳到IntStream中
② long类型归纳到LongStream中
③ double、float类型归纳到DoubleStream中
(一)创建IntStream,可以调用两种方法:IntStream.of或者Arrays.stream
IntStream stream = IntStream.of(1,1,2,3,5);
stream = Arrays.stream(values,from,to);
(二)使用静态generate和iterate方法创建IntStream
IntStream is1 = IntStream.generate(()->(int)(Math.random()*100));
(三)使用静态range和rangeClosed方法创建IntStream
//range含头不含尾
IntStream is2 = IntStream.range(5, 10);
//rangeClosed含头也含尾
IntStream is3 = IntStream.rangeClosed(5, 10);
(四)codePoints和chars方法创建IntStream
String sentence = "\uD835\uDD46 is the set of octonions.";
System.out.println(sentence);
//byte short char boolean int 都可以写成IntStream
IntStream codes = sentence.codePoints();
(五)当你有一个对象流时,可以利用mapToInt、mapToLong、mapToDouble方法将其转换为基本类型流。
//将对象流转换成基本类型流
IntStream is4 = words.mapToInt(String::length);
(六)当你有一个基本类型流时,可以利用boxed方法转换为对象流
Stream<Integer> integers = IntStream.range(0, 100).boxed();
通常,基本类型流里的方法和对象流中的方法类似,下面列出最主要的差异:
①toArray方法会返回基本类型数组
②产生可选结果的方法会返回一个OptionalInt、OptionalLong、OptionalDouble,这些类与Optional类类似,但是具有getAsInt,getAsLong,getAsDouble方法,而不是get方法
③具有返回总和、平均值、最大值、最小值的sum、average、max、min方法,对象流没有这些方法
④summaryStatistics方法会产生一个类型为IntSummaryStatistics、LongSummaryStatistics、DoubleSummaryStatistics的对象,它们可以同时报告流的总和、平均值、最大值、最小值。
package com.zxyy.stream.test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class PrimitiveTypeStreams {
public static void show(String title,IntStream stream) {
final int SIZE = 10;
int[] firstElements = stream.limit(SIZE).toArray();
System.out.print(title+" : ");
for(int i=0;i<firstElements.length;i++) {
if(i>0) {
System.out.print(",");
}
if(i<SIZE) {
System.out.print(firstElements[i]);
}else {
System.out.print("...");
}
}
System.out.println();
}
public static void main(String[] args) throws IOException {
IntStream is1 = IntStream.generate(()->(int)(Math.random()*100));
show("is1",is1);
//range含头不含尾
IntStream is2 = IntStream.range(5, 10);
show("is2",is2);
//rangeClosed含头也含尾
IntStream is3 = IntStream.rangeClosed(5, 10);
show("is3",is3);
Stream<String> words = Stream.of(new String(Files.readAllBytes(Paths.get("C:\\Users\\弓长小月\\Desktop\\Test Forlder\\alice30.txt")),StandardCharsets.UTF_8).split("\\PL+"));
IntStream is4 = words.mapToInt(String::length);
show("is4",is4);
String sentence = "\uD835\uDD46 is the set of octonions.";
System.out.println(sentence);
//byte short char boolean int 都可以写成IntStream
IntStream codes = sentence.codePoints();
System.out.println(codes.mapToObj(c->String.format("%X", c)).collect(Collectors.joining()));
Stream<Integer> integers = IntStream.range(0, 100).boxed();
IntStream is5 = integers.mapToInt(Integer::intValue);
show("is5",is5);
}
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
如有错误,尽请指正!