CoreJava读书笔记--JavaSE8的流库(二)

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);
	}
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

                                                                                                                                                                  如有错误,尽请指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值