(二)Stream API

Stream是Java8中处理集合的关键抽象概念,你可以指定对集合的操作,但是真正执行时间由具体实现决定。

stream遵循的是“做什么”,而不是“怎么做”。使用迭代循环一开始就需要指定如何计算,失去了优化的机会。

2.1 从迭代器到stream操作

一个基本的操作:

	List<String> list=new ArrayList<>();
	list.add("asgjasiljgoa");
	list.add("agsd");
	list.add("agarhgadfhadfhahafhga");
	long n=list.stream().filter(s->s.length()>10).count();
	System.out.println(n);

集合调用stream方法得到Stream对象,filter方法进行元素选择,返回新的流对象,count方法是执行和终止流操作,返回元素个数。

Stream与集合的区别:

a,stream自己不存储元素,元素可能存储在底层集合中,或者根据需要产生。

b,stream操作符不改变源对象,返回一个新的stream。

c,操作符可能是延迟执行的,需要结果的时候才执行。

将上面的stream方法改变一下,即可使用并行流:

long n=list.parallelStream().filter(s->s.length()>10).count();

stream的使用:

a,创建一个Stream

b,指定将Stream转换到新流的操作

c,指定终止操作产生一个结果。该操作使前面的延迟操作立即执行。

2.2 创建Stream

对于集合来说,调用stream方法即可。

对于数组,使用静态方法Stream.of()或Arrays.stream()

	int[] a =new int[]{1,2,3,4,5};	
        long x=Stream.of(a).count();
	long y=Arrays.stream(a, 0, a.length).count();

 of接收可变参数,可以一个个输入多个值,以上x=1,y=5。第二个方法将数组的一部分转换为stream

要创建一个空的Stream,

Stream<String> s=Stream.empty();

要创建无限流:

	Stream.generate(()->"hehe");
	Stream.iterate(1, e->e+1);

以上创建的是一个常量流和一个1,2,3,4...序列。

如想按照正则匹配切割一个字符串:返回切割后的流

Pattern.compile(regex).splitAsStream(content);

要将文件所有行转换为一个流:

	try {
		Files.lines(Paths.get(url));
	} catch (IOException e1) {
		e1.printStackTrace();
	}

2.3 filter,map和flatMap方法

在前面代码中已经提到过filter方法,它接收Predicate类型参数,即一种过滤规则,根据测试条件返回true or false

map:对Stream中的元素执行某种映射操作,接收一个Function类型参数。如:

list.stream().map(String::toUpperCase)

它将里面的字母变成大写后返回新的流。

flatMap:和map类似,不过它会合并结果,即Stream<T>,如果T也是一个Stream,那么它会展开里面的stream然后合并成一个stream。如下:

	static Stream<Character> fun(String s){
		List<Character> list=new ArrayList<>();
		for(Character c:s.toCharArray())
			list.add(c);
		return list.stream();
	}
        Stream<Stream<Character>> s1=list.stream().map(s->fun(s));
	Stream<Character> s2=list.stream().flatMap(s->fun(s));

2.4 提取子流和组合流

以下产生一个流,使用limit,skip,concat,peek等方法对流进行操作:

                	static void fun2(double d) {
		        System.out.println(d);
	}
		Stream.generate(Math::random).limit(10).forEach(e -> fun2(e));
		Stream.generate(Math::random).limit(10).skip(5).forEach(e -> fun2(e));
		Stream.concat(Stream.generate(Math::random).limit(5),Stream.generate(Math::random)
                .limit(5)).forEach(e->fun2(e));
		Stream.iterate(1,e->e*2).peek(e->System.out.println(e)).limit(10).toArray();

limit:返回一个限制元素个数的新流

skip:返回一个忽略流中前面若干元素的新流

concat:静态方法,将2个流拼接。注意第一个流不要是无限流,否则永远不会返回。

peek:返回和原始流相同的新流,但是每次获取一个元素时,都会调用其中的方法,这样可以测试无限流是否被延迟处理。在上面的代码中,如果不使用limit方法,那么会一直输出0。如果你通过流执行某些操作,但你还想继续进行其他操作,使用peek是正确的做法。

2.5 有状态的转换

所谓有状态,就是指流的操作对于后面的元素进行操作时,需要考虑前面的元素,无状态则跟前面的元素无关。比如去重操作,必须知道前面是否出现过相同的元素。

distinct方法去除流中重复的元素到1个为止

Stream.of("hehe","haha","hehe").distinct().peek(System.out::println).count();

以上输出hehe,haha。

sotred方法对流进行排序,Collections.sort方法排序是会改变原来的集合的,而流的操作都是返回新流。

		Stream.of("hehe","haha","heihei").sorted().peek(System.out::println).count();
		Stream.of("hehe1","haha","hehe11").sorted(Comparator.comparing(String::length)
				.reversed()).peek(System.out::println).count();

以上也可以提供自定义比较器进行比较。

2.6 简单的聚合方法

前面提到过count方法,它会返回流中元素总数(long类型)。

还有max,min方法,分别返回流中最大,最小值(Optional类型)。

		List<String> list = new ArrayList<>();
		list.add("asgjasiljgoa");
		list.add("agsd");
		list.add("agarhgadfhadfhahafhga");
		Optional<String> op1=list.stream().max(String::compareToIgnoreCase);
		Optional<String> op2=list.stream().min(String::compareToIgnoreCase);
		System.out.println(op1.get()+" "+op2.get());

在java8中,Optional是一种更好的表示缺少返回值的情况。即使没有返回一个元素给它,也不会抛出异常。

findFirst:返回流中第一个元素。通常与filter方法结合使用

		String s=Stream.of("hehe","haha","heihei").filter(e->e.startsWith("ha"))
                .findFirst().get();
		System.out.println(s);//haha

findAny:返回所有元素。

anyMatch:接收参数同filter,即Predicate类型。返回类型boolean。判断是否有符合条件的元素存在。

		boolean b=list.parallelStream().anyMatch(e->e.startsWith("h"));
		System.out.println(b);//true

allMatch:如果所有元素都满足规则,返回true。

noneMatch:没有一个元素满足规则时返回true。

以上带有any,all,none的方法都可以并行执行。

2.7 Optional类型

Optional<T>或者是一个对T类型对象的封装,或者表示不是任何对象。它比一般指向T类型的引用更加安全,因为它不会返回null

如果直接调用它的get方法,如果不存在值时,会抛出NoSuchElementException异常

当然你可以这么使用:

		Optional<String> op1=list.stream().max(String::compareToIgnoreCase);
		if(op1.isPresent())
			System.out.println(op1.get());

但这不会比非null判断更简单。

2.7.1 使用Optional值

ifPresent方法另一种形式是接收一个函数,如果存在值,将值传递给函数,否则不进行任何操作。

上面代码可以变成这样:

		Optional<String> op1=list.stream().max(String::compareToIgnoreCase);
		op1.ifPresent(System.out::println);
		op1.ifPresent(list::add);

如果想对结果进行处理,可以使用map方法:

		Optional<Boolean> option= op1.map(list::add);

option可能有3种值:封装true或者false的Optional,或者一个空的可选值。

当然,有存在时则进行操作的方法,也有不存在时进行操作的方法orElse,orElseGet,orElseThrow:

		Optional<String> op1=list.stream().max(String::compareToIgnoreCase);
		String s=op1.orElse("");
		op1.orElseGet(()->System.getProperty("driver.className"));
		op1.orElseThrow(NoSuchElementException::new);

以上第一个方法提供默认值,第2,3个方法Supplier类型参数,即返回相应类型的方法。

2.7.2 创建可选值

使用Optional.of或者Optional.empty创建一个Optional对象,还可以使用Optional.ofNullable创建可选值。如果值存在,跟调用of一样,不存在跟empty一样。

2.7.3 使用flatMap组合可选函数

根据前面的内容知道,如果函数返回的类型与调用者持有的类型不同,使用map是不恰当的,特别是返回与调用者相同类型的时候,此时使用flatMap可以将中间结果展开然后合并。

假设一个函数f返回Optional<T>,类型T里面有另一个函数g返回Optional<U>,如下:

如果调用s.f().g() 得到的是Optional<Optional<U>>

package chapter2;

import java.util.Optional;

public class Demo2 {
	static Optional<Double> inverse(double x) {
		return x == 0 ? Optional.empty() : Optional.of(1.0 / x);
	}

	static Optional<Double> sqrt(double x) {
		return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
	}

	public static void main(String[] args) {
		Optional<Double> op1=inverse(-4.0).flatMap(Demo2::sqrt);
		Optional<Double> op = Optional.of(-4.0).flatMap(Demo2::inverse).flatMap(Demo2::sqrt);
		op.ifPresent(System.out::println);
	}
}

以上实现了返回类型为Optional的函数的组合。

2.8 聚合操作

如下,通过reduce方法求一个整数流中所有元素的和。其中第二种方式提供了初始sum值,即使流为空也会返回Integer类型值

	Stream<Integer> stream=Stream.iterate(1, e->2+e).limit(100);
	stream.reduce((x,y)->x+y).ifPresent(System.out::println);
	Integer i=stream.reduce(0,(x,y)->x+y);
	System.out.println(i);

如果想对String类型的流求所有字符串的长度总和,不能使用上面的简单形式,因为你用lambda表达式求出2个字符串的长度和为int类型,并不是String类型。所以要通过另一种方式:

	Stream<String> stream3=Stream.of("ajgag","agafgag","asdfgageh");
	//参数:初始值+函数1+函数2
	//函数要求 an associative, non-interfering, statelessfunction
	//第一个单词意思是满足结合律
	//第2,3个单词是互不干扰的和无状态的意思。说的都是无状态性
	//函数1:某线程对流部分的计算结果
	//函数2:将各线程结果合并
	int l=stream3.reduce(0,(total,word)->total+word.length(),(t1,t2)->t1+t2);
	int l2=stream3.mapToInt(String::length).sum();
	System.out.println(l);

上面第二种方式是映射到数字流,更为简单。

2.9 收集结果

可以使用Iterator方法返回一个迭代器,也可以使用toArray方法返回Object数组,如果想得到带类型的数组,还是可以使用数组构造器引用方式:

	String[] str=stream3.toArray(String[]::new);

要将结果收集起来,使用collect方法。使用方式:

a,一个能创建目标类型实例的方法。如HashSet的构造函数

b,一个将元素添加到目标中的方法。

c,一个将2个目标对象合并的方法。如HashSet的addAll方法

	HashSet<String> set1=stream3.collect(HashSet::new,HashSet::add,HashSet::addAll);

其实Collectors提供了这3个方法。所以可以这么做:

	List<String> list=stream3.collect(Collectors.toList());//toSet
        //特定类型
	Set<String> set2=stream3.collect(Collectors.toCollection(TreeSet::new));
	//将字符串全部拼接
        String result=stream3.collect(Collectors.joining());
	//使用,进行拼接
        String result2=stream3.collect(Collectors.joining(","));
        //元素不是String时
	String result3=stream3.map(Object::toString).collect(Collectors.joining(","));

对于一些统计操作,我们可以生成一个统计工具来实现一些统计方法:

	IntSummaryStatistics intSummary=stream3.collect(Collectors.summarizingInt(String::length));
	intSummary.getSum();
	intSummary.getAverage();
	intSummary.getMax();
	intSummary.getMin();

这样可以获取最大最小值,平均值,总和等信息。

2.10 将结果收集到map中

假设有一个Person类,现在想将Stream<Person>收集到map中,如下:

		List<Person> list = new ArrayList<>();
		list.add(new Person(1,"heihei"));
		list.add(new Person(2,"hehe"));
		list.add(new Person(3,"haha"));
		Stream<Person> s=list.stream();
		Map<Integer, String> map1=s.collect(Collectors.toMap(Person::getId, Person::getName));
		Map<Integer, Person> map2=s.collect(Collectors.toMap(Person::getId, Function.identity()));

以上第二种方式使用Person自身作为value。

下面是地区语言和国家信息构成的map。

		Stream<Locale> locales=Stream.of(Locale.getAvailableLocales());
		//第3个参数解决key冲突,可选,不选有冲突时抛出异常
		//第4个参数提供具体类型,可选,不提供使用hashmap
		Map<String,String> map3=locales.collect(Collectors.toMap(
				l->l.getDisplayLanguage(), 
				l->l.getDisplayLanguage(l),
				(oldvalue,newvalue)->oldvalue,
				TreeMap::new));
		//key冲突时,添加新的语言到原来的集合中
		Map<String,Set<String>> map4=locales.collect(Collectors.toMap(
				l->l.getDisplayCountry(),
				l->Collections.singleton(l.getDisplayLanguage()),
				(a,b)->{
					Set<String> set=new HashSet<>(a);
					set.addAll(b);
					return set;
				}
				));

另外,每个toMap对应并发情况都有一个toConcurrentMap方法。

2.11 分组和分片

使用groupingBy和partitioningBy可以简化上面操作,实现数据分类

partitioningBy将数据二分,要么是符合条件,要么不符合

		Map<String, List<Locale>> map5 = locales.collect(Collectors.groupingBy(Locale::getCountry));
		Map<Boolean, List<Locale>> map6 = locales.collect(Collectors.partitioningBy(l -> l.getLanguage().equals("en")));

 groupingBy方法还可以提供第二个参数,类型为Collector。

以下第二个参数使用的是Collectors的静态方法,使用了静态导入。

Map<String, Set<Locale>> map7 = locales.collect(Collectors.groupingBy(
            Locale::getCountry, toSet()));
		//每个国家的语言(Locale)数
		Map<String, Long> map8 = 
locales.collect(Collectors.groupingBy(Locale::getCountry, counting()));

除了counting,还有summing(Int/Double/Long),maxBy/minBy

		//每个州下所有城市的总人数
                Map<String,Integer> map9=cities.collect(Collectors.groupingBy(
				City::getState, 
				summingInt(City::getPopulation)));
		//每个州人口最多的城市
		Map<String,City> map10=cities.collect(Collectors.groupingBy(
				City::getState,
				maxBy(Comparator.comparing(City::getPopulation))));

 mapping方法将一个函数应用到Collector结果上,并且需要另一个收集器来处理结果。

		//根据城市所属州分组,对城市名字长度取最大返回
                Map<String,Optional<String>> map11=cities.collect(Collectors.groupingBy(
				City::getState,
				mapping(
						City::getName,
						maxBy(Comparator.comparing(String::length))
						)
				));

 有了mapping后我们可以更简单的获取每个国家对应的语言:

		Map<String,Set<String>> map12=locales.collect(groupingBy(
				l->l.getDisplayCountry(),
				mapping(l->l.getDisplayLanguage(),
						toSet())
				));

如果grouping或mapping函数返回int,double或long类型,可以将元素收集到一个summary statistics对象中

		Map<String,IntSummaryStatistics> map13=cities.collect(groupingBy(
				City::getState,
				summarizingInt(City::getPopulation)
				));

以下通过2种方式将流中字符串分类后拼接:

	Map<String,String> map14=cities.collect(groupingBy(
			City::getState,
			reducing("",City::getName,(s,t)->s.length()==0?t:s+","+t)
			));
	Map<String,String> map15=cities.collect(groupingBy(
			City::getState,
			mapping(City::getName,
					joining(","))
			));

以上reducing有3种格式:一个参数(二元运算),2个参数(加一个identity),3个参数(再加一个mapper)。有了Stream的reduce方法,Collectors的reducing方法很少会用到了。

2.12 原始类型流

为了高效,不可能老是将基本类型包装,所以提供了IntStream等,它们直接存储基本类型值,不进行包装。boolean,byte,char,short,int使用IntStream,float类型使用DoubleStream

		IntStream i1=IntStream.of(1, 2, 3, 4, 5);
		IntStream i2=Arrays.stream(new int[]{1,2,3,4,5},0,3);
		IntStream.range(0, 100);//0~99
		IntStream.rangeClosed(0, 100);//0~100
		Random rand=new Random();
		IntStream i3=rand.ints();

 以上是IntStream的创建,其他类似。也可以将其转换成包装类的Stream:

		Stream<Integer> s1=IntStream.range(0, 100).boxed();

前面提到过,Stream<String>类型可以使用如下转换成IntStream:

s.mapToInt(String::length);

原始类型流和对象流上调用方法的区别:

a,toArray方法返回一个原始类型数组

b,产生Optional结果的方法会返回OptionalInt/Long/Double等,他们没有get方法而是getAsInt等方法

c,方法sum,average,max,min可以直接使用

d,summaryStatistics方法会产生一个IntSummaryStatistics

2.13 并行流

parallel方法可以将一个Stream转换成一个并行流。

当不考虑有序时,一些操作会得到优化。

调用Stream.unordered方法可以不关心顺序。对于distinct、limit等可以加快执行顺序。

Collectors.groupingByConcurrent天然是无序的,因为需要考虑效率。

2.14 函数式接口

就一个图。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值