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();
流是一类数据项,是一种可以让我们在更高的概念级别上指定计算任务的数据视图。
流遵循了“做什么而非怎么做”的原则,它主要是对集合进行处理,它与集合相似,却有着显著的差异。
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 super T> 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 extends T> 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 super T> 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 super T,? extends Stream extends R>>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 super T,? extends Stream extends R>> 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 super T> 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 super T> 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(Stream extends T>a, Stream extends T> 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 super T> 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 super T> 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 super T> comparator)
//Optional min(Comparator super T> 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 super T> predicate)
//boolean allMatch(Predicate super T> predicate)
//boolean noneMatch(Predicate super T> predicate)
//分别在这个流中任意元素,所有元素和没有任何元素匹配给定谓词时返回ture。
boolean aWordStartWithQ = words.paralle().anymatch(s -> s.startWith("Q"));
Optional对象是一种包装类对象,要么包含了类型为T的对象,要么没有包装任何对象。Optional类被当做一种更安全的方式,用来替代类型T的引用,这种引用要么引用某个对象,要么为null。接下来我们将讨论如何正确使用。
1,获取Optional值
//T orElse(T other)
//产生这个Optional的值,或者在该Optional为空时,产生other。
//T orElseGet(Supplier extends T> other)
//产生这个Optional的值,或者在该Optional为空时,产生调用other的结果。
// T orElseThrow(Supplier extends X> 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 super T> action)
//如果该Optional不为空,就将它的值传递给action。
optionalValue = wordList.stream()
.filter(s -> s.contains("red"))
.findFirst();
optionalValue.ifPresent(s -> System.out.println(s + "contains red"));
//void ifPresentOrElse(Consumer super T> 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 super T> predicate)
//产生一个Optional,如果当前的Optional的值满足给定的谓词条件,那么所产生的Optional的值就是当前Optional的值;否则,产生一个空的Optional。
Optional<String> transformed = optionalString
.filter(s -> s.length() >= 8)
.map(String::toUpperCase);
//Optional or(Supplier extends Optional extends T>> 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类型正确的用法:
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 super T,? extends Optional extends U>> 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);
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 super T> 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 super T, A, R> 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 super T> mapper)
//static Collector summarizingLong(ToLongFunction super T> mapper)
//static Collector summarizingDouble(ToDoubleFunction super T> 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);
不要指望通过将所有的流都转化为并行流可以提高操作的效率,记住下列几条: