Java进阶之路 java8 流库

Java进阶之路 java8 流库

java8 API中文文档链接

在处理集合时,当我们需要遍历集合中元素,或者需要在每个元素时进行操作。列如,假设我们想要对某本书中的所有长单词进行计数。可以通过

var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")),StandardCharsets.UTF_8);  //从文件中读取数据
List<String> words = List.of(contents.split("\\PL+"));  //通过正则表达式将其单词分为一个个字符串
int count = 0;  
for (String w : words){
    if(w.length() > 12)count++;  //计数
}

在使用流之后,相同的操作看起来像下面这样:

var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")),StandardCharsets.UTF_8);  //从文件中读取数据
List<String> words = List.of(contents.split("\\PL+"));  //通过正则表达式将其单词分为一个个字符串
long count = words.stream()  //将list转化为流
        .filter(w -> w.length() > 12) //lambda表达式
        .count(); //计数

新的操作可读性很强,可以直接通过方法名确定这段代码意欲何为。此外,将stream()修改为parallelStream()可以并行的方式来执行过滤和计数。

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

这里写目录标题

  • Java进阶之路 java8 流库
    • 一,流是什么
    • 二,流的优点
    • 三,流的创建
    • 四,流的转换
    • 五,抽取子流和组合流
    • 六,流的简单约简
    • 七,Optional类
    • 8,访问结果
    • 九,基本类型流
    • 十,并行流

一,流是什么

流是一类数据项,是一种可以让我们在更高的概念级别上指定计算任务的数据视图。

二,流的优点

流遵循了“做什么而非怎么做”的原则,它主要是对集合进行处理,它与集合相似,却有着显著的差异。
1,流并不储存元素。这些元素可能储存在底层的集合中,或者是按需生成的。
2,流的操作不会修改其数据源。当需要对数据进行处理时,会生成一个新的流包含修改后的元素。
3,流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。

三,流的创建

1,通过Collection接口的stream方法将集合转化为一个流。

// default Stream stream()
// default Stream parallelStream()
// 产生当前集合中所有元素的顺序流或并行流
var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")),StandardCharsets.UTF_8);  //从文件中读取数据
List<String> words = List.of(contents.split("\\PL+"));
Stream<String> stream1 = words.stream();
Stream<String> stream2 = words.stream();
//还可以通过Array.stream(array, from, to) 可以用数组中的一部分元素来创建一个流。

2,通过静态的Stream.of方法。

//static  Stream of(T... values>
//产生一个元素为定值的流。
var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")),StandardCharsets.UTF_8);
Stream<String> stream = Stream.of(contents.split("\\PL+")); //split方法返回的为一个String数组

//of方法具有可变长参数,因此我们可以构建具有任意数量引元的流;
Stream<String> stream = Stream.of("gently", "down", "the", "stream");

3,通过Stream.empty方法。

//static  Stream empty()
//产生一个不包含任何元素的流
Stream<String> stream = Stream.empty();

4,通过Stream.generate方法。

//static  Stream generate(Supplier s)
//产生一个无限流,它的值是通过反复调用函数s而构建的
Stream<String> stream1 = Stream.generate(() -> "Echo");
Stream<Double> stream2 = Stream.generate(Math::random);
//generate方法接受一个不包含任何引元的函数(及java8 函数式接口 Supplier接口的对象)

5,通过Stream.iterate方法。

//static  Stream iterate(T seed, UnaryOperator f)
//static  Stream iterate(T seed, Predicate hasNext, UnaryOperator f)
//产生一个无限流,他的元素包含 seed,在 seed上调用f产生的值,在前一个元素上调用f产生的值,等等。第一个方法会产生一个无限流,而第二个方法产生的流会在碰到第一个不满足hasNext的元素时终止。
Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
//该序列中的一个元素时种子BigInteger.ZERO,第二个元素时f(seed),即1,下一个元素时f(f(seed)),即2,后续以此类推。
var limit = new BigInteger("100000");
Stream<BigInteger> stream = Stream.iterate(BigInteger.ZERO, n -> n.compareTo(limit) < 0, n -> n.add(BigInteger.ONE));
//第二个加了一个限制条件,n的最大值。

6,通过Stream.ofNullable方法。

//static  Stream ofNummable(T t)
//如果t为null,返回一个空流,否则返回包含t的流。
var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")),StandardCharsets.UTF_8);
Stream<String> stream1 = Stream.ofNullable(contents);
Stream<String> stream2 = Stream.ofNullable(null);

7,通过迭代器Spliterator的spliteratorUnknownSize方法

//static  Spliterator spliteratorUnknownSize(Iterator iterator, int characteristics)
//用于给定的特性(一种包含诸如Spliterator.ORDERED之类的常量的位模式)将一个迭代器转换为一个具有未知尺寸可分割的迭代器。
Iterator<Path> iterator = Paths.get("E:/1program/words.txt").iterator();
Stream<Path> stream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator,Spliterator.ORDERED), false);

8,通过regex包下Pattern的splitAsStream方法

//Stream splitAsStream(CharSequence input)
//产生一个流,它的元素是input中的由compile方法通过正则表达式界定的部分
var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")),StandardCharsets.UTF_8);
Stream<String> stream = Pattern.compile("\\PL+").splitAsStream(contents);

9,通过Files.lines方法

//static Stream lines(Path path)
//static Stream lines(Path path, Charset cs)
//产生一个流,它的元素时指定文件中的行,该文件的字符集为UTF-8,或者为指定的字符集。
Path path = Paths.get("E:/1program/words.txt");
Stream<String> stream1 = Files.lines(path);
Stream<String> stream2 = Files.lines(path,StandardCharsets.UTF_8);

10,通过StreamSupport.stream方法

//static  Stream stream(Spliterator spliterator, boolean parallel)
//产生一个流,它包含了由给定的可分割迭代器产生的值。
Iterable<Path> iterable = FileSystems.getDefault().getRootDirectories();
Stream<Path> stream = StreamSupport.stream(iterable.spliterator(), false);

11,通过Scanner.stream方法

//public Stream tokens()
//产生一个字符串流,该字符串是调用这个扫描器的next方法时返回的。
Scanner scanner = new Scanner(System.in);
Stream<String> stream = scanner.tokens();

四,流的转换

流的转换会产生一个新的流,它的元素派生自另一个流中的元素。
通过Stream类自带的filter,map,flatMap方法

//Stream filter(Predicate predicate)
//产生一个流,它包含当前流中所有满足谓词条件的元素。
var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")), UTF_8);
Stream<String> words = Stream.of(contents.split("\\PL+"));
Stream<String> stream = words.filter(w -> w.length() > 12);
// Stream map(Function>mapper)
//产生一个流,它包含将mapper应用于当前流中所有元素所产生的结果。
var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")), UTF_8);
Stream<String> words = Stream.of(contents.split("\\PL+"));
Stream<String> stream = words.map(String::toLowerCase);
// Stream flatMap(Function> mapper)
//产生一个流,它是通过将mapper应用于当前流中所有元素所参数的结果连接到一起获得的。(注意,这里的每个结果都是一个流)
var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")), UTF_8);
Stream<String> words = Stream.of(contents.split("\\PL+"));
Stream<String> stream = words.flatMap(w -> Stream.of(w.split("\\PL+")));
//注意:如果在函数为对字符串进行处理返回流时,如果将字符串换为流,将会得到包含流的流,而用flatMap方法而不是用map方法,可以将流摊平为单个流。
public static void main(String[] args){
    List<String> words = new ArrayList<>();
    for(int i = 0;i < 10;i++){
        words.add("words" + i);
    }
    Stream<Stream<String>> result = words.stream().map(w -> codePoints(w));
    Stream<String> flatResult = words.stream().flatMap(w -> codePoints(w));
}
public static Stream<String> codePoints(String s){
    var result = new ArrayList<String>();
    for(int i = 0;i < s.length();i++){
        result.add(s);
    }
    return result.stream();
}

五,抽取子流和组合流

如果你想对一个流进行裁剪,获取它的子流,或者将几个流组合在一起,可以通过以下方法去实现:
1,limit方法

//Stream limit(long maxSize)
//产生一个流,其中包含了当前流中最初的maxSize个元素。
Stream<Double> randoms = Stream.generate(Math::random).limit(100);
//产生一个包含100个随机数的流。

2,skip方法

//Stream skip(long n)
//产生一个流,它的元素是当前流中除了前n个元素之外的所有元素。
var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")), UTF_8);
Stream<String> words = Stream.of(contens.split("\\PL+")).skip(1);

3,takeWhile方法

//Stream takeWhile(Predicate predicate)
//产生一个流,它的元素是当前流中所有满足谓词条件的元素。
var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")), UTF_8);
Stream<String> words = Stream.of(contens.split("\\PL+")).skip(1);
Stream<String> stream = words.takeWhile(s -> "0123456789".contains(s));

4,dropWhile方法

//Stream dropWhile(Predicate predicate)
//产生一个流,它的元素是当前流中排除不满足谓词条件的元素之外的
var contents = new String(Files.readAllBytes(Paths).get("E:/1program/words.txt"), UTF_8);
Stream<String> words = Stream.of(contens.split("\\PL+")).skip(1);
Stream<String> stream = words.dropWhile(s -> "0123456789".contains(s));

5,concat静态方法

//static  Stream concat(Streama, Stream b)
//产生一个流,它的元素是a的元素后面跟着b的元素。
Stream<String> combined = Stream.concat(Stream.of("Hello"), Stream.of("World!"));

6,distinct方法

//Stream distinct()
//产生一个流,包含当前流中所有不同的元素,及剔除重复元素。
Stream<String> uniqueWords = Stream.of("merrily", "merrily", "merrily", "merrily").distinct(); //显然流中只含一个merrily了

7,sorted方法

//Stream sorted()
//Stream sorted(Comparator comparator)
//产生一个流,它的元素是当前流中所有元素按照顺序排列的。第一个方法要求元素是实现了Comparable的类的实例。
var contents = new String(Files.readAllBytes(Paths).get("E:/1program/words.txt"), UTF_8);
Stream<String> words = Stream.of(contens.split("\\PL+"));
Stream<String> stream = words.stream().sorted(Comparator.comparing(String::length).reversed());

8,peek方法

//Stream peek(Consumer action)
//产生一个流,它与当前流中的元素相同,在获取其中每个元素时,会将其传递给action。
Object[] powers = Stream.iterate(1.0, p -> p * 2)
	.peek(e -> System.out.println("Fetching" + e))
	.limit(20).toArray();

六,流的简单约简

在前面我们已经了解了流的创建和获取,以及对流本身的组合和截取。接下来,我们要了解的是从流当中获取答案,接下来所讨论的方法被称为约简约简是一种终结操作,它们通过将流约简为可以在程序中使用的非流值。
1,count方法

//long count()
//获取当前流中元素的数量。
Path path = Paths.get("E:/1program/words.txt");
var contents = new String(Files.readAllBytes(path), UTF_8);
long count = Stream.of(contents.split("\\PL+")).filter(w -> w.length() > 12).count();

2,max方法和min方法
max和min方法的返回值都为Optional类,其中包装了答案,使用该类可以有效防止当流为空时返回null,产生空指针异常,在下一部分我们可以详细讨论Optional类。

//Optional max(Comparator comparator)
//Optional min(Comparator comparator)
//分别产生该流的最大元素和最小元素,使用由给定比较器定义的排序规则,如果这个流为空,会产生一个空的Optional对象。
var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")), UTF_8);
Stream<String> words = Stream.of(contents.split("\\PL+"));
Optional<String> max = words.max(String::compareToIgnoreCase);
System.out.println("max:" + max.orElse(""));
Optional<String> min = words.min(String::compareToIgnoreCase);
System.out.println("min:" + min.orElse(""));

3,findFirst和findAny方法

//Optional findFirst()
//产生该流的第一个元素,如果流为空,会返回一个空的Optional对象。通常与filter组合使用。
Optional<String> start = words.filter(s -> s.startsWith("Q")).findFirst();
//找到流中第一个以字母Q开头的单词。

//Optional findAny()
//产生该流中任意一个元素,如果流为空,会返回一个空的Optional对象。在并行处理流时很有效。 
Optional<String> start = words.parallel().filter(s -> s.startsWith("Q")).findAny();

4,anyMatch,allMatch,noneMatch方法

//boolean anyMatch(Predicate predicate)
//boolean allMatch(Predicate predicate)
//boolean noneMatch(Predicate predicate)
//分别在这个流中任意元素,所有元素和没有任何元素匹配给定谓词时返回ture。
boolean aWordStartWithQ = words.paralle().anymatch(s -> s.startWith("Q"));

七,Optional类

Optional对象是一种包装类对象,要么包含了类型为T的对象,要么没有包装任何对象。Optional类被当做一种更安全的方式,用来替代类型T的引用,这种引用要么引用某个对象,要么为null。接下来我们将讨论如何正确使用。
1,获取Optional值

//T orElse(T other)
//产生这个Optional的值,或者在该Optional为空时,产生other。
//T orElseGet(Supplier other)
//产生这个Optional的值,或者在该Optional为空时,产生调用other的结果。
// T orElseThrow(Supplier exceptionSuppier)
//产生这个Optional的值,或者在该Optional为空时,爆出调用exceptionSupplier的结果。
var contents = new String(Files.readAllBytes(Paths.get("E:/1program/words.txt")), UTF_8);
List<String> wordList = List.of(contents.split("\\PL+"));
Optional<String> optionalValue = wordList.stream()
        .filter(s -> s.contains("fred"))
        .findFirst();
String result1 = optionalString.orElse("");
String result2 = optionalString.orElseGet(() -> System.getProperty("myapp.default"));
String result3 = optionalString.orElseThrow(IllegalStateException::new);

2,消费Optional值

//void ifPresent(Consumer action)
//如果该Optional不为空,就将它的值传递给action。
optionalValue = wordList.stream()
        .filter(s -> s.contains("red"))
        .findFirst();
optionalValue.ifPresent(s -> System.out.println(s + "contains red"));
//void ifPresentOrElse(Consumer action, Runnable emptyAction)
//如果该Optional不为空,就将它的值传给action,否则调用emptyAction。
optionalValue.ifPresentOrElse(s -> System.out.println(s + "contains red"),() -> System.out.println("Not contains"));

3,管道化Optional值

// Optional map mapper>
//产生一个Optional,如果当前的Optional的值存在,那么所产生的Optional的值是通过将给定的函数应用于当前的Optional的值而得到的;否则,产生一个空的Optional
Optional<String> transform = optionalString.map(String::toUpperCase);
//Optional filter(Predicate predicate)
//产生一个Optional,如果当前的Optional的值满足给定的谓词条件,那么所产生的Optional的值就是当前Optional的值;否则,产生一个空的Optional。
Optional<String> transformed = optionalString
 	.filter(s -> s.length() >= 8)
 	.map(String::toUpperCase);
//Optional or(Supplier> supplier)
//如果当前Optional不为空,则产生当前的optional;否则由Supplier产生一个Optional。
Optional<String> result = optionalString.or(() -> alternatives.stream().findFirst());

4,不适用Optional值的方式
如果不正确的使用Optional类,那么相比以往的得到“某物或null” 的方式,你并没有得到任何好处。

//T get()
//get方法会在Optional值存在的情况下获得其中包装的元素,或者在不存在的情况下抛出一个NoSuchElementException异常。
//因此
Optional<T> optionalValue = ...;
optionalValue.get().someMethod();
//并不比下面方式更安全:
T value = ...;
value.someMetod();
//isPresent方法会报告某个Optional对象是否具有值。但是
if(optionalValue.isPresent()) optionalValue.get().someMethod();
//并不比下面的方式更容易处理
if(value != null) value.someMethod();

下面试一些关于Optional类型正确的用法:

  • Optional类型的变量永远都不应该为null。
  • 不要使用Optional类型的域。因为其代价是额外多出来一个对象。在类的内部,使用null表示缺失的域更易于操作。
  • 不要在集合中放置Optional对象,并且不要将它们用作map的键。应该直接收集其中的值。

5,创建Optional值

//static  Optional of(T value)
//static  Optional ofNullable(T value)
//产生一个具有给定值的Optional。如果value为null,那么第一个方法会抛出一个NullPointerException异常,而第二个方法会产生一个空Optional
//static  Optional empty()
//产生一个空Optional。
public static Optional<Double> inverse(Double x){
	return x == 0 ? Optional.empty() : Optional.of(1 / x);
}

6,用flatMap构建Optional值的方法

// Optional flatMap(Function> mapper)
//如果Optional存在,产生将mapper应用于当前Optional值所产生的结果,或者在当前Optional为空时,返回一个空Optional。
public static Optional<Double> squareRoot(Double x){
	return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}
Optional<Double> result = inverse(x).flatMap(MyMath::squareRoot);

8,访问结果

1,iterator方法

//Iterator iterator()
//产生一个用于获取当前流中各个元素的迭代器。这是一个终结操作。
Iterator<Integer> iterator = Stream.iterate(0, n -> n + 1).limit(10).iterator();
while (iterator.hasNext())
    System.out.println(iterator.next());

2,forEach方法

//void forEach(Consumer action)
//在流的每个元素上都调用action,这是一个终结操作。
stream.forEach(System.out::println);

3,toArray方法

//Project[] toArray()
// A[] toArray(IntFunction generator)
//产生一个对象数组,或者在将引用A[]::new传递给构造器时,返回一个A类型的数组。
Object[] numbers = Stream.iterate(0, n -> n + 1).limit(10).toArray();

4,collect方法

// R collect(Collector collector)
//使用给定的收集器来收集当前流中的元素。Collectors类有用于多种收集器的工厂方法。
Set<String> noVowelSet = stream.collect(Collectors.toSet());

在collect方法中调用的返回Collector收集器的方法

//static  Collector, List> toList()
//static  Collector, List> toUnmodifiableList()
//static  Collector, List> toSet()
//static  Collector, List> toUnmodifiableSet()
//产生一个将元素收集到列表和集合中的收集器。
List<String> result1 = stream.collect(Collectors.toList());
Set<String> result2 = stream.collect(Collectors.toSet());

5,summarizing方法

//static  Collector summarizingInt(ToIntFunction mapper)
//static  Collector summarizingLong(ToLongFunction mapper)
//static  Collector summarizingDouble(ToDoubleFunction mapper)
//产生能够生成(Int|Long|Double)SummaryStatistics对象的收集器,通过它们可以获得将mapper应用于每个元素后所产生的结果的数量,总和,平均值,最大值和最小值。
IntSummaryStatistics summary = noVowels().collect(Collectors.summarizingInt(String::length);

//在得到SummaryStatistics对象的收集器后,可以通过以下方法求取值。
//long getCount()
//产生汇总后的元素个数。
int count = summary.getCount();
//(int|long|double) getSum()
//double getAverage()
//产生汇总后的元素的总和或平均值,或者在没有任何元素时返回0。
int sum = summary.getSum();
double average = summary.getAverage();
//(int|long|double) getMax()
//(int|long|double) getMin()
//产生汇总后元素的最大值和最小值,或者在没有任何元素时,产生(Integer|Long|Double).(MAX|MIN)_VALUE。
double max = summary.getMax();
double min = summary.getMin();

九,基本类型流

到目前为止,我们将整数收集到Stream中,这种将数据包装到包装器中再转化为流的方式是很低效的。对于其他基本类型来说,情况也一样。在流库中具有专门的类型IntStream,LongStream和DoubleStream,可以直接用来储存基本类型值,而不需要通过包装器。如果想使用short,char,byte和boolean,可以使用IntStream,对于float,可以使用DoubleStream。
1,创建相应的流。
在这只介绍IntStream一种,其他与IntStream类似

//与前面所述的方法一样,of方法,stream方法
//static IntStream of(int... values)
//产生一个给定元素构成的IntStream

IntStream stream = IntStream.of(1,1,2,3,5);
stream = Arrays.stream(values, from, to);
//此外,还可以使用range方法
//static IntStream range(int startInclusive, int endExclusive)
//static IntStream rangeClosed(int startInclusive, int endInclusive)
//产生一个给定范围内的整数构成的IntStream。
IntStream zeroToNinetyNine = IntStream.range(0, 100);
IntStream zeroToHundred = IntStream.rangeClosed(0. 100);
//IntStream codePoints()
//返回由当前字符串的所有Unicode码点构成的流。
IntStream unicode = string.codePoints();
//IntStream ints()
//IntStream ints(int randomNumberOrigin, int randomNumberBound)
//IntStream ints(long streamSize)
//IntStream ints(long streamSize, int randomNumberOrigin, int randomNumberBound)
//产生随机数流,如果提供了streamSize,这个流就是具有给定数量元素的有限流。当提供了边界时,其元素将位于randomNumberOrigin(包含)和randomNumberBound(不包含)的区间内。

2,获取流中的值

//int[] toArray()
//产生一个由当前流中的元素构成的数组。
int[] elements = stream.limit(10).toArray();
//int sum()
//OptionalDouble average()
//OptionalInt max()
//OptionalInt min()
//IntSummaryStatistics summaryStatistics()
//产生当前流中元素的总和,平均值,最大值和最小值,或者产生1一个可以从中获取所有这四个值的对象。
//Stream boxed()
//产生用于当前流中的元素的包装器对象流。

十,并行流

流使并行处理块操作变得很容易,但首先必须创建一个并行流。

//S parallel()
//产生一个与当前流中元素相同的并行流。
Map<Integer, Long> shortWordCounts = words.parallelStream()
	.filter(s -> s.length() < 12)
	.collect(groupingBy(String::length, counting()));

在默认情况下,从有序集合,范围,生成器和迭代器中产生的流都是有序的,在有序的情况下,如果运行相同的操作两次,将会得到相同的结果。
排序并不与高效的并行处理想排斥。但当放弃排序需求时,有些操作可以更高效的并行化执行。

//S unordered()
//产生一个与当前流中元素相同的无序流。
Stream<String> sample = words.parallelStream().unordered().limit(n);

不要指望通过将所有的流都转化为并行流可以提高操作的效率,记住下列几条:

  • 并行会导致大量的开销,只有面对非常大的数据集才划算。
  • 只有在底层数据源可以被有效的分割为多个部分时,将流并行化才有意义。
  • 并行流使用的线程池可能会因诸如文件I/O或网络访问这样的操作被阻塞而饿死。
  • 只有面对海量的内存数据和运算密集处理,并行流才会工作最佳。

你可能感兴趣的:(Java进阶之路,java)