CoreJava读书笔记--JavaSE 8的流库(一)

JavaSE 8的流库

什么是流?
流是一种比集合更高级的概念,它是指定计算的数据视图。
在我看来,我们处理集合中的数据时,往往需要进行迭代操作,先遍历,再对每个元素进行某项操作。但是流可以优化这个解决办法,只关心需要的是什么,而不关心它的实现过程。

【1】从迭代到流操作
我们先比较一下,迭代和流操作的具体实现。比如,要计算某本书中长单词(大于12个字母就算)的数量。

String contents = new String(Files.readAllBytes(Paths.get("..\\alice30.txt")),StandardCharsets.UTF_8);
List<String> words = Arrays.asList(contents.split("\\PL+"));

先看迭代操作:

long count =0;
for(String w : words){
    if(w.length()>12){
        count++;
    }
}

我们再看看如果使用流操作:

long counts = words.stream().filter(w->w.length()>12).count();

对比来看,流操作比迭代操作要简单,因为它不用关心计数和查找符合条件的单词是怎样实现的,只关心我们要得到的是什么。

还有一个方法叫paralleStream,这个方法是产生并行流,在处理并发时,就可以让流库以并行的方式来过滤和计数:

long count = words.parallelStream().filter(w->w.length()>12).count();

流的特点:

①流不存储数据

②流的操作不会修改其数据源,原本的集合或流中的数据是不会被改变的。

③流是尽可能的惰性执行的,就是说到了非要有结果的时候,它才会执行。

流的操作分为3个步骤:

①创建流

②指定将初始流转换为其他流的中间操作

③终结流

我们看一个简单的例子,用stream方法或者parallelStream方法创建流,用filter方法转换流,用count方法终结流。

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.List;

public class CountLongWordTest {
	public static void main(String[] args) throws IOException {
		String contents = new String(Files.readAllBytes(Paths.get("C:\\Users\\弓长小月\\Desktop\\Test Forlder\\alice30.txt")),StandardCharsets.UTF_8);
		List<String> words = Arrays.asList(contents.split("\\PL+"));
		//普通迭代操作
		long count = 0;
		for(String w : words) {
			if(w.length()>12) {
				count++;
			}
		}
		System.out.println("Iterator : "+count);
		
		//创建普通流来实现
		count = words.stream().filter(w->w.length()>12).count();
		System.out.println("stream : "+count);
		
		//创建并行流
		count = words.parallelStream().filter(w->w.length()>12).count();
		System.out.println("parallelStream : "+count);
	}
}

【2】流的创建

Collection接口的stream方法可以将任何集合转换成流。如果有一个数组就使用静态的Stream.of方法。

我们来看看创建不同流的方式:

//将集合转换成流
List<String> words = ...;
Stream<String> s1 = words.stream();
//将数组转换成流
String[] array = ...;
Stream<String> s2 = Stream.of(array);
//of方法还可以选择截取数组来创建流  of(array,from,to):包含from,不包含to
Stream<String> s3 = Stream.of(array,0,5);

同样的还可以创建不包含任何元素的流:

//创建一个不包含任何元素的流
Stream<String> s4 = Stream.empty();

创建无限流有两种方法:

①调用Stream.generate()方法

②调用Stream.iterate()方法

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.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CreatingStreams {
	public static void main(String[] args) throws IOException {
		//对于String类型的数组,用Stream.of方法来创建流
		Stream<String> words = Stream.of(new String(Files.readAllBytes(Paths.get("C:\\\\Users\\\\弓长小月\\\\Desktop\\\\Test Forlder\\\\alice30.txt")),StandardCharsets.UTF_8).split("\\PL+"));
		show("words",words);
		
		Stream<String> song = Stream.of("gently","down","the","stream");
		show("song",song);
		
		//创建一个不包含任何元素的流
		Stream<String> empty = Stream.empty();
		show("empty",empty);
		
		//创建一个String类型的无限流
		Stream<String> echos = Stream.generate(()->"Echo");
		show("echos",echos);
		
		//创建一个产生随机数的无限流
		Stream<Double> randoms = Stream.generate(Math::random);
		show("randoms",randoms);
		
		//创建一个无限序列,开始为0,每次加1
		//iterate方法可以给出一个种子值,以及一个函数
		Stream<Integer> integers = Stream.iterate(0, n->n+1);
		show("integers",integers);
		
		//Pattern类有一个splitAsStream方法来按照给出的正则表达式分割一个CharSequence对象
		Stream<String> wordsAnotherWay = Pattern.compile("\\PL+").splitAsStream(new String(Files.readAllBytes(Paths.get("C:\\Users\\弓长小月\\Desktop\\Test Forlder\\alice30.txt")),StandardCharsets.UTF_8));
		show("wordsAnotherWay",wordsAnotherWay);
		
		//按照alice30.txt的行来创建一个流
		Stream<String> lines = Files.lines(Paths.get("C:\\Users\\弓长小月\\Desktop\\Test Forlder\\alice30.txt"));
		show("lines",lines);
		
	}
	public static <T> void show(String title,Stream<T> stream) {
		final int SIZE = 10;
		List<T> firstElements = stream.limit(10).collect(Collectors.toList());
		System.out.print(title+" : ");
		for(int i=0;i<firstElements.size();i++) {
			if(i>0) {
				System.out.print(",");
			}
			if(i<SIZE) {
				System.out.print(firstElements.get(i));
			}else {
				System.out.print("...");
			}
		}
		System.out.println();
	}
}

【3】filter,map和flatMap方法

在上面我们看到了如何创建流,现在再来看看如何转换流。流的转换是会产生一个新的流,它的元素来自于原来的流中。看看API文档中对这3个方法的介绍:

 

 

filter方法转换一个流,它的元素与给定的条件相匹配;

map方法转换一个流,它的作用是按照给定的函数来转换流中的值;

flatMap方法转换一个流,它的作用是将当前流中所有元素产生的结果连接到一起

【4】抽取子流和连接流

抽取子流有两个方法:①limit()方法,②skip()方法

//limit方法获取前n个元素 
//例如获取前100个元素的流
Stream<Double> randoms = Stream.generate(Math::random).limit(100);

//skip方法是丢弃前n个元素,保留n之后的
//获取100以后的元素
Stream<Integer> integers = Stream.iterate(0,x->x+1).skip(100);

连接流方法:contact();

//contact方法连接两个流
//假设letters("Hello")的结果是["H","e","l","l","o"]的流
Stream<String> combined = Stream.contact(letters("Hello"),letters("World"));

注意:连接两个流时,前一个流不能是一个无限流。

【5】其他流的转换

方法名方法的作用示例
distinct()产生一个没有重复元素的流,并且按照原来的流的顺序排列Stream<String> uniqueWords = Stream.of("Mary","Mary","Mary","Jack");
sorted()产生一个流,它的元素是当前流中的所有元素按照顺序排列,该方法要求元素是实现了Comparable接口的类的实例Stream<String> stream = words.stream.sorted(Comparator.comparing(String::length));
peek()产生一个流,它的流与原来的流中元素相同,但是每次获取一个元素,都会调用一个函数,多作调试用Stream<Double> stream = Stream.iterate(1.0,n->n*2).peek(e->System.out.println("Fetching "+e)).limit(20);

【6】简单约简

我们之前已经看到了创建流和转换流的方法,流的操作还剩下最后最后一步,那就是终结操作。前面看到的count方法是其中一种,下面我们看看其他几种简单的终结操作:

①max和min

//使用min方法终结流,得到流的最小值
Optional<String> smallest = words.min(String::compareToIgnoreCase);
System.out.println("smallest = "+smallest);

//使用max方法终结流,得到最大值
Optional<String> largest = words.max(String::compareToIgnoreCase);
System.out.println("largest = "+largest);

②findFirst和findAny

//findFirst方法返回与之匹配的非空集合的第一个值
//注意,如果没有任何值与之匹配就返回一个空的Optional值
Optional<String> first = words.filter(s->s.startWith("Q").findFirst();

//findAny方法返回任意匹配的任意一个值
//注意,如果没有任何值与之匹配就返回一个空的Optional值
Optional<String> any = words.filter(s->s.startWith("Q").findAny();

③anymatch,allmatch和nonematch

//如果想知道有任意元素与给出条件匹配用anymatch,返回的是boolean值
boolean anymatch = words.parallel().anymatch(s->s.contains("e"));

//查看是否所有元素都与给出条件匹配用allmatch,返回值是boolean值
boolean allmatch = words.parallel().allmatch(s->s.contains("e"));

//查看是否没有任何元素与给出条件匹配,返回值是boolean值
boolean nonematch = words.parallel().nonematch(s->s.contains("e"));

【7】Optional类型

Optional<T>对象是一种包装器对象,要么包装了T类型对象,要么没有包装任何对象。Optional<T>类型被当作一种更安全的方式,用来代替类型T的引用,这种引用要么引用某个对象,要么为null。

(1)如何使用Optional值

Optional在值存在的时候,才会使用这个值;在值不存在时,它会有一个可替代物。我们先看看Optional类的三个简单方法:

//orElse()方法,如果存在值,就用原值,如果不存在值就使用一个值来替代它
//我们这里用空字符串来替代没有值的情况
String result = optionalString.orElse("");


//orElseGet()方法,后面可以接一个lambda表达式,表示如果不存在值的话就用表达式的值来替代它
String result = optionalString.orElseGet(()->Locale.getDefault().getDisplayName());


//orElseThrow()方法,如果不存在值,就在方法中抛出对应的异常
String result = optionalString.orElseThrow(IllegalStateException::new);

还有一个ifPresent()方法,ifPresent(v->Process v)该方法会接收一个函数,如果在值存在的情况下,它会将值传递给该函数,但是如果在值不存在的情况下,它不会做任何事。

//比如我们想要在值存在的情况下,就把值添加到一个集中
optionalValue.ifPresent(v->result.add(v));

或者

optionalValue.ifPresent(result::add);

注意:ifPresent方法并不返回任何值。

如果想要得到结果,就需要使用map方法

Optional<Boolean> added = optionalValue.map(result::add);
//这种情况下,added值可能有三种情况,
//当optionalValue存在的时候可能是true或者false
//当optionalValue不存在的时候,就是一个空的Optional

(2)不适合使用Optional值的方式

不适合使用Optional值的情况有两种:

①需要用到get()方法,因为Optional值在不存在的情况下,使用get方法会抛出NoSuchElementException

Optional<T> optionalValue = ...;
optionalValue.get().somemethod();

//这种方式并不比下面安全
T value = ...;
value.somemethod();

②需要用到isPresent()方法作非空判断时

if(optionalValue.isPresent()){
    optionalValue.get().somemethod();
}
//这种方式也并不比下面容易处理
if(value!=null){
    value.somemethod();
}

(3)创建Optional值

三种方式创建Optional值:

①empty()方法:创建一个空的Optional值

Optional<String> empty = Optional.empty();

②of(T value)方法:value不能为null,如果为null,会抛出NullPointerException

if(value!=null){
    Optional<String> os = Optional.of(value);
}

③ofNullable(T value)方法:这个方法相当于empty方法和of方法的桥梁,在value为null时,它会返回一个空Optional;在value不为null时,它会返回of方法的返回值

Optional<String> ofNullable = Optional.ofNullable(value);

(4)用flatMap来构建Optional值的函数

假设你有一个可以产生Optional<T>对象的方法f,并且目标类型T有一个可以产生Optional<U>的方法g,如果他们都是普通方法,那么你可以使用s.f().g()来调用。但是现在s.f()的返回类型是Optional<T>,而不是类型T,无法调用g方法,这个时候我们就可以利用flatMap方法来组合这两个方法。

Optional<U> result = s.f().flatMap(T::g);

接下来,我们来写个例子,使用一下OptionalAPI中的方法:

package com.zxyy.stream.bean;

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.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;

public class OptionalTest {
	public static void main(String[] args) throws IOException {
		String contents = new String(Files.readAllBytes(Paths.get("C:\\Users\\弓长小月\\Desktop\\Test Forlder\\alice30.txt")),StandardCharsets.UTF_8);
		List<String> wordList = Arrays.asList(contents.split("\\PL+"));
		Optional<String> optionalValue = wordList.stream().filter(s->s.contains("fred")).findFirst();
		System.out.println(optionalValue.orElse("No word")+" contains fred");
		
		Optional<String> optionalString = Optional.empty();
		String results = optionalString.orElse("N/A");
		System.out.println("results : "+results);
		results = optionalValue.orElseGet(()->Locale.getDefault().getDisplayName());
		System.out.println("results : "+results);
		
		try {
			results = optionalValue.orElseThrow(IllegalStateException::new);
			System.out.println("results : "+results);
		} catch (Throwable t) {
			t.printStackTrace();
		}
		
		optionalValue = wordList.stream().filter(s->s.contains("red")).findFirst();
		optionalValue.ifPresent(s->System.out.println(s+" contains red"));
		
		Set<String> result = new HashSet<String>();
		optionalValue.ifPresent(result::add);
		Optional<Boolean> added = optionalValue.map(result::add);
		System.out.println(added);
		
		System.out.println(inverse(4.0).flatMap(OptionalTest::squreRoot));
		System.out.println(inverse(0.0).flatMap(OptionalTest::squreRoot));
		System.out.println(inverse(-1.0).flatMap(OptionalTest::squreRoot));
		
		Optional<Double> result2 = Optional.of(0.0625).flatMap(OptionalTest::inverse).flatMap(OptionalTest::squreRoot);
		System.out.println("result2 = "+result2);
	}
	
	public static Optional<Double> inverse(Double x){
		return x==0?Optional.empty():Optional.of(1/x);
	}
	
	public static Optional<Double> squreRoot(Double x){
		return x<0?Optional.empty():Optional.of(Math.sqrt(x));
	}
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值