赞
踩
与集合相比,流提供了一种可以让我们在更高的概念级别上指定计算任务的数据视图。通过使用流,我们可以说明想要完成什么任务,而不是说明如何去实现它。我们将操作的调度留给具体实现去解决。例如,假设我们想要计算某个属性的平均值,那么我们就可以指定数据源和该属性,然后,流库就可以对计算进行优化,例如,使用多线程来计算总和与个数,并将结果合并。
在处理集合时,我们通常会迭代遍历它的元素,并在每个元素上执行某项操作。例如,假设我们想要对某本书中的所有长单词进行计数。首先,将所有单词放到一个列表中:
// Read file into string List<String> words
var contents = new String(Files.readAllBytes(Paths.get("alice.txt")), StandardCharsets.UTF 8);
List<String> words = List.of(contents.split("\\PL+"));
通过迭代循环:
int count=0;
for (String w : words){
if (w.length() > 12) count+;
}
而通过流:
long count = words.stream()
.filter(w -> w.length()> 12)
.count();
通过流操作不必扫描整个代码去查找过滤和计数操作,方法名就可以直接告诉我们其代码意欲何为。而且,循环需要非常详细地指定操作的顺序,而流却能够以其想要的任何方式来调度这些操作,只要结果是正确的即可。
仅将stream修改为parallelStream就可以让流库以并行方式来执行过滤和计数。
long count = words.parallelStream()
.filter(w -> w.length()> 12)
.count();
流遵循了 “做什么而非怎么做“ 的原则。在流的示例中,我们描述了需要做什么:获取长单词,并对它们计数。我们没有指定该操作应该以什么顺序或者在哪个线程中执行。相比之下,本节开头处的循环要确切地指定计算应该如何工作,因此也就丧失了进行优化的机会。
流表面上看起来和集合很类似,都可以让我们转换和获取数据。但是,它们之间存在着显著的差异:
我们再来看看这个示例。stream和parallelStream方法会产生一个用于words列表的流。filter方法会返回另一个流,其中只包含长度大于12的单词。count方法会将这个流化简为一个结果。
这个工作流是操作流时的典型流程。我们建立了一个包含三个阶段的操作管道:
产生一个元素为给定值的流
Stream<String> song = Stream.of("gently","down","the","stream");
产生一个流,它的元素是由数组中指定范围内的元素构成的
String[] strs = new String[]{"a","b","c"};
Stream<String> stream = Arrays.stream(strs, 1, 3);
创建空的不包含任何元素的流
Stream<String> empty = Stream.empty();
产生个无限流,它的值是通过反复调用函数而构建的。generate方法会接受一个不包含任何引元的函数(或者从技术上讲,是一个Supplier接口的对象)。无论何时,只要需要一个流类型的值,该函数就会被调用以产生一个这样的值。
//我们可以像下面这样获得一个常量值的流:
Stream<String> generate = Stream.generate(() -> "test");
Stream<Double> generate1 = Stream.generate(Math::random);
产生一个无限流、它的元素包含seed、在seed上调用f产生的值、在前一个元素上调用f产生的值,等等。第一个方法会产生一个无限流,而第二个方法的流会在碰到第一个不满足hasNext谓词的元素时终止。
如果要产生像0 1 2 3…这样的序列,可以使用iterate方法。它会接受一个"种子"值,以及一个函数(从技术上讲,是一个UnaryOperation),并且会反复地将该函数应用到之前的结果上。例如,
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("10000000");
Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.compareTo(limit)< 8, n -> n.add(BigInteger.ONE));
只要该谓词拒绝了某个迭代生成的值,这个流即结束。
最后,Strean.ofNullable方法会用一个对象来创建一个非常短的流。如果该对象为null,那么这个流的长度就为0;否则,这个流的长度为1,即只包含该对象。这个方法与flatMap 相结合时最有用。
产生一个流,它的元素是输入中由该模式界定的部分。
Stream<String> words = Pattern.compile("\\PL+").splitAsStream(contents);
产生一个字符串流,该字符串是调用这个扫描器的next方法时返回的。
Stream<String> words = new Scanner(contents).tokens();
产生一个流,它的元素是指定文件中的行,该文件的字符集为UTF-8,或者为指定的字符集。
//静态的Files.lines方法会返回一个包含了文件中所有行的Stream:
try(Stream<String> Lines = Files.lines(path)){
//Process lines
}
//如果我们持有的Iterable对象不是集合,那么可以通过下面的调用将其转换为一个流:
StreamSupport.stream(iterable.spliterator(),false);
//如果我们持有的是Iterator对象,并且希望得到一个由它的结果构成的流,那么可以使用下面的语句:
StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator,Spliterator.ORDERED),false);
static Spliterator spliteratorUnknownSize(Iterator<? extends T> iterator, int characteristics)
用给定的特性(一种包含诸如 Spliterator.ORDERED 之类的常量的位模式)将一个迭代器转换为一个具有未知尺寸的可分割的迭代器。
流的转换会产生一个新的流,它的元素派生自另一个流中的元素。我们已经看到了filter转换会产生一个新流,它的元素与某种条件相匹配。下面,我们将一个字符串流转换为只包含长单词的另一个流:
List<String> words = . . . .
Stream<String> longwords = words.stream().filter(w -> w.length()> 12);
filter的引元是Predicate,即从T到boolean的函数。
通常,我们想要按照某种方式来转换流中的值,此时,可以使用map方法并传递执行该转换的函数。例如,我们可以像下面这样将所有单词都转换为小写:
Stream<String> lowercaseWords = words.stream().map(String::ttoLowerCase);
这里,我们使用的是带有方法引用的map,但是,通常我们可以使用lambda表达式来代替:
Stream<String> firstLetters = words.stream()).map(s -> s.substring(θ, 1));
上面的语句所产生的流中包含了所有单词的首字母。
<R> Stream<R> flatMap(Function<? superT,? extends Stream<? extends R>> mapper)
产生一个流,它是通过将 napper应用于当前流中所有元素所产生的结果连接到一起而获得的。(注意,这里的每个结果都是一个流。)
假设我们有一个函数,它返回的不是一个值,而是一个包含众多值的流。下面的示例展示的方法会将字符串转换为字符串流,即一个个的编码点:
public static Stream<String> codePoints(String s){
var result = new ArrayList<String>();
int i= 0;
while (i < s.length()){
int j = s.offsetByCodePoints(i,1);
result.add(s.substring(i,j));
i=j;
}
return result.stream();
}
这个方法可以正确地处理需要用两个char值来表示的Unicode字符,因为本来就应该这样处理。但是,我们不用再次纠结其细节。
例如,codePoints(“boat”)的返回值是流【“b”,“o”,“a”,“t”】。假设我们将codePoints方法映射到一个字符串流上:
Stream<Stream<String>> result = words.stream().map(w -> codePoints(w));
那么会得到一个包含流的流,就像【……【“y”,“o”,“u”,“r”】,【“b”,“a”,“t”】,…】。为了将其摊平为单个流【……“y”,“o”,“tt”,“b”,“o”,“a”,“t”,….】,可以使用flatMap方法而不是map方法:
Stream<String> flatResult = words.stream().flatMap(w -> codePoints(w));
调用**stream.limit(n)**会返回一个新的流,它在n个元素之后结束(如果原来的流比n短,那么就会在该流结束时结束)。这个方法对于裁剪无限流的尺寸特别有用。例如,
Stream<Double> randoms = Stream.generate(Math:random).Limit(100);
会产生一个包含100个随机数的流。
丢弃前n个元素
Stream<String> words = Stream.of(contents.split("\\PL+")).skip(1);
Stream<T> takeWhile(Predicate<? super T> predicate)
产生一个流,它的元素是当前流中所有满足谓词条件的元素。
产生一个流,它的元素是当前流中排除不满足谓词条件的元素之外的所有元素
我们可以用Stream类的静态concat方法将两个流连接起来:
Stream<String> combined = Stream.concat(codePoints("Hello"),codePoints("World"));
//产生一个流,它的元素是a的元素后面跟着b的元素。
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
去重元素
Stream<String> uniqueWords = Stream.of("merrily","merrily","merrily","gently").distinct();
排序
Stream<String> longestFirst = words.stream().sorted(Comparator.comparing(String::length).reversed());
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
产生一个流,它的元素是当前流中的所有元素按照顺序排列的。第一个方法要求元素是实现了Comparable的类的实例。
Stream<T> peek(Consumer<? super T> action)
产生一个流,它与当前流中的元素相同,在获取其中每个元素时,会将其传递给 action。
Object[] powers = Stream.iterate(1.0, p -> p * 2) +e))
.peek(e -> System.out.println("Fetching" + e)
.limit(20).toArray();
//分别产生这个流的最大元素和最小元素,使用由给定比较器定义的排序规则,如果这个流为空,会产生一个空的0ptional对象。这些操作都是终结操作。
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
Optional<String> largest = words.max(String:compareToIgnoreCase);
System.out.println("largest:" + largest.orElse(""));
//分别产生这个流的第一个和任意一个元素,如果这个流为空,会产生一个空的0ptional 对象。这些操作都是终结操作。
Optional<T> findFirst();
Optional<T> findAny();
Optional<String>startswithQ= words.filter(s -> s.startswith("Q").findFirst();
//分别在这个流中任意元素、所有元素和没有任何元素匹配给定谓词时返回true。这些操作都是终结操作。
boolean anyMatch(Predicate<?super T> predicate);
boolean allMatch(Predicate<?super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
Optional<String> startsWithQ = words.parallel().filter(s -> s.startswith("Q").findAny();
//产生一个用于获取当前流中各个元素的迭代器。这是一种终结操作。
Iterator<T> iterator()
Iterator<Integer>iter=Stream.iterate(0, n -> n + 1).limit(10).iterator();
针对将流中的元素收集到另一个目标中,有一个便捷方法collect可用,它会接受一个Collector接口的实例。收集器是一种收集众多元素并产生单一结果的对象,Collectors类提供了大量用于生成常见收集器的工厂方法。要想将流的元素收集到一个列表中,应该使用Collectors.tolist()方法产生的收集器:
List<String> result = stream.collect(Collectors.toList());
收集到set 集
Set<String> result = stream.collect(Collectors.toSet());
如果想要控制获得的集的种类,那么可以使用下面的调用:
TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet::new));
假设想要通过连接操作来收集流中的所有字符串。我们可以调用
String result = stream.collect(Collectors.joining());
如果想要在元素之间增加分隔符,可以将分隔符传递给 joining 方法:
String result = stream.collect(Collectors.joining(","));
如果想要将流的结果约简为总和、数量、平均值、最大值或最小值,可以使用summarizing (Int | Long | Double)方法中的某一个。这些方法会接受一个将流对象映射为数值的函数,产生类型为(Int | Long | Double)SummaryStatistics的结果,同时计算总和、数量、平均值、最大值和最小值。
IntSummaryStatistics summary = stream.collect(Collectors.summarizingInt(String:length));
double averagewordLength = summary.getAverage();
double maxwordLength = summary.getMax();
static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper,Function<? super T,? extends U> valueMapper);
static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper,BinaryOperator<U> mergeFunction);
static <T,K,U,M extends Map<K,U>> Collector<T,?,M> toMap(Function<? super T,? extends K>keyMapper,Function<? super T,? extends U>valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier);
static <T,K,U> Collector<T,?,Map<K,U>> toUnmodifiableMap(Function<? super T,? extends K>keyMapper,Function<?super T,? extends U> valueMapper);
static <T,K,U> Collector<T,?,Map<K,U>> toUnmodifiableMap(Function<? super T,? extends K> keyMappe Function<? super T,? extends U> valueMapper,BinaryOperator<U> mergeFunction);
static <T,K,U> Collector<T,?,ConcurrentMap<K,U>> toConcurrentMap(Function<? super T,? extends K> keyMapper,Function<? super T,? extends U> valueMapper);
产生一个收集器,它会产生一个映射表、不可修改的映射表或并发映射表。keyMapper 和valueMapper函数会应用于每个收集到的元素上,从而在所产生的映射表中生成一个键/值项。默认情况下,当两个元素产生相同的键时,会抛出一个IllegalStateException异常。你可以提供一个 mergeFunction来合并具有相同键的值。默认情况下,其结果是一个HashMap或ConcurrenthashMap。你可以提供一个mapSupplier,它会产生所期望的映射表实例。
假设我们有一个Stream,并且想要将其元素收集到一个映射表中,这样后续就可以通过它们的ID来查找人员了。Collectors.toMap方法有两个函数引元,它们用来产生映射表的键和值。例如,
Map<Integer,String> idToName = people.collect(Collectors.toMap(Person::getId,Person::getName));
通常情况下,值应该是实际的元素,因此第二个函数可以使用Function.identity()。
Map<Integer,Person> idToPerson = people.collect(Collectors.toMap(Person::getId,Function.identity());
如果有多个元素具有相同的键,就会存在冲突,收集器将会抛出一个IllegalStateException 异常。可以通过提供第3个函数引元来覆盖这种行为,该函数会针对给定的已有值和新值来解决冲突并确定键对应的值。这个函数应该返回已有值、新值或它们的组合。
在下面的代码中,我们构建了一个映射表,存储了所有可用locale中的语言,其中每种语言在默认locale中的名字(例如“German”)为键,而其本地化的名字(例如“Deutsch”)为值:
Stream<Locale> locales = Stream.of(Locale.getAvailableLocales();
Map<String,String> languageNames = locales.collect(
Collectors.toMap(Locale:getDisplayLanguage, loc->loc.getDisplayLanguage(loc), (existingValue, newValue)-> existingValue));
我们不关心同一种语言是否可能会出现两次(例如,德国和瑞士都使用德语),因此我们只记录第一项。
现在,假设我们想要了解给定国家的所有语言,这样我们就需要一个Map-String,SetsString>。例如,“Switzerland” 的值是集[French,German,Italian]。首先,我们为每种语言都存储一个单例集。无论何时,只要找到了给定国家的新语言,我们就会对已有集和新集进行并操作。
Map<String,Set<String>> countryLanguageSets = locales.collect(
Collectors.toMap(Locale:getDisplayCountry,
l -> Collections.singleton(1.getDisplayLanguage(),
(a,b)->{
var union = new HashSet<String>(a);
union.addAll(b);
return union;
}));
如果想要得到TreeMap,那么可以将构造器作为第4个引元来提供。你必须提供一种合并函数。下面是本节一开始所列举的示例之一,现在它会产生一个TreeMap:
Map<Integer,Person> idToPerson = people.collect(
Collectors.tomap(
Person::getId,
Function.identity(),
(existingValue,newValue)->{ throw new IllegalStateException();},
TreeMap:new));
//产生一个收集器,它会产生一个映射表或并发映射表,其键是将classifier应用于所有收集到的元素上所产生的结果,而值是由具有相同键的元素构成的一个个列表。
static <T,K> Collector<T,?,Map<K,List<T>>groupingBy(Function<? super T,? extends K> classifier)
static <T,K> Collector<T,?,ConcurrentMap<K,List<T>>> groupingByConcurrent(Function<? super T,? extends K> classifier)
产生一个收集器,它会产生一个映射表,其键是true/false,而值是由满足/不满足断言的元素构成的列表。
static <T> Collector<T,?,Map<Boolean,List<T>>>partitioningBy(Predicate<?super T> predicate)
收集local 根据国家进行分组
Map<String,List<Locale>> countryToLocales = locales.collect(Collectors.groupingBy(Locale:getCountry));
当分类函数是断言函数(即返回boolean值的函数)时,流的元素可以分为两个列表:该函数返回 true 的元素和其他的元素。在这种情况下,使用 partitioningBy 比使用 groupingBy 更高效。例如,在下面的代码中,我们将所有locale分成了使用英语和使用所有其他语言的两类:
Map<Boolean,List<Locale>> engLishAndOtherLocales = locales.collect(Collectors.partitioningBy(l-> l.getLanguage().equals("en"));
List<Locale> englishLocales = englishAnd0therLocales.get(true);
groupingBy方法会产生一个映射表,它的每个值都是一个列表。如果想要以某种方式来处理这些列表,就需要提供一个"下游收集器"。例如,如果想要获得集而不是列表,那么可以使用上一节中看到的Collectors.toSet收集器:
Map<String,Set<Locale>> countryToLocaleSet = locales.collect(groupingBy(Locale::getCountry,toSet());
Java提供了多种可以将收集到的元素约简为数字的收集器:counting会产生收集到的元素的个数。例如:
Map<String,Long> countryToLocaleCounts = locales.collect(groupingBy(Locale::getCountry,counting());
可以对每个国家有多少个locale进行计数。
summing(Int/Long/Double)会接受一个函数作为引元,将该函数应用到下游元素中,并产
生它们的和。例如:
Map<String,Integer> stateToCityPopulation = cities.collect(groupingBy(City::getState,summingInt(City::getPopulation));
可以计算城市流中每个州的人口总和。
maxBy和minBy会接受一个比较器,并分别产生下游元素中的最大值和最小值。例如:
Map<String, Optional<City>> stateToLargestCity = cities.collect(
groupingBy(City::getState,
maxBy(Comparator.comparing(City::getPopulation))
);
可以产生每个州中最大的城市。
collectingAndThen收集器在收集器后面添加了一个最终处理步骤。例如,如果我们想要知道有多少不同的结果,那么就可以将它们收集到一个集中,然后计算其尺寸:
Map<Character,Integer> stringCountsByStartingLetter = strings.collect(
groupingBy(
s -> s.charAt(0),
collectingAndThen(toSet(), Set::size)
)
);
napping收集器的做法正好相反,它会将一个函数应用于收集到的每个元素,并将结果传递给下游收集器。
Map<Character,Set<Integer>> stringLengthsByStartingLetter = strings.collect(
groupingBy(
s -> s.charAt(0),
mapping(String::length,toSet())
)
);
Map<String,IntSummaryStatistics> stateToCityPopulationSummary = cities.collect(
groupingBy(
City::getState,
summarizingInt(City::getPopulation)
)
);
然后,可以从每个组的汇总统计对象中获取这些函数值的总和、数量、平均值、最小值和最大值。
reduce方法是一种用于从流中计算某个值的通用机制,其最简单的形式将接受一个二元函数,并从前两个元素开始持续应用它。如果该函数是求和函数,那么就很容易解释这种机制:
List<Integer> values = List.of(1,2,3,4,5);
Optional<Integer> sum = values.stream().reduce((x,y) -> x + y);
Optional<Integer> sum1 = values.stream().reduce(Integer::sum);
在上面的情况中,reduce方法会计算v0 + v1 +v2+ … 其中v是流中的元素。如果流为空,那么该方法会返回一个0ptional,因为没有任何有效的结果。
reduce 可以提供一个初始值作为计算的起点
Integer sum2 = values.stream().reduce(5, (x,y) -> x + y);
计算的结果就是 5+1+2+3+4+5 了。
如果是一个对象流,需要对某些属性求和,需要提供一个累积器函数和一个组合器函数。
调用示例:
int result = words.reduce(0, (total,word)->total + word.length(), (totall,total2)->totall + total2);
static IntStream range(int startInclusive,int endExclusive)
static IntStream rangeClosed(int startInclusive, int endInclusive)
产生一个由给定范围内的整数构成的IntStream。
static IntStream of(int... values)
产生一个由给定元素构成的IntStream。
int[] toArray()
产生一个由当前流中的元素构成的数组。
int sum();
OptionalDouble average();
OptionalInt max();
OptionalInt min();
IntSummaryStatistics summaryStatistics();
产生当前流中元素的总和、平均值、最大值和最小值,或者产生一个可以从中获取所有这四个值的对象。
Stream<Integer> boxed()
产生用于当前流中的元素的包装器对象流。
static LongStream range(long startInclusive, long endExclusive);
static LongStream rangeClosed(long startInclusive, long endInclusive);
用给定范围内的整数产生一个LongStream。
static LongStream of(long... values);
用给定元素产生一个LongStream。
long[] toArray();
用当前流中的元素产生一个数组。
long sum();
OptionalDouble average();
Optionallong max();
OptionalLong min();
LongSummaryStatistics summaryStatistics();
产生当前流中元素的总和、平均值、最大值和最小值,或者产生一个可以从中获取所有这四个值的对象。
Stream<Long> boxed()
产生用于当前流中的元素的包装器对象流。
方法和int 和double 一样
IntStream codePoints()
产生由当前字符串的所有Unicode码点构成的流。
IntStream ints();
IntStream ints(int randomNumberOrigin, int randomNumberBound);
IntStream ints(long streamSize);
IntStream ints(long streamSize,int randomNumberOrigin,int randomNumberBound);
LongStream longs();
LongStream longs(Long randomNumberOrigin, long randomNumberBound);
LongStream longs(long streamSize);
LongStream longs(long streamSize, longrandomNumber0rigin, long randomNumberBound);
DoubleStream doubles() ;
DoubleStream doubles(double randomNumberOrigin, double randomNumberBound);
DoubleStream doubles(long streamSize);
DoubleStream doubles(long streamSize, double randomNumberOrigin, double randomNumberBound);
产生随机数流。如果提供了streanSize,这个流就是具有给定数量元素的有限流。当提供了边界时,其元素将位于 randomNumberOrigin(包含)和 randomNumberBound(不包含)的区间内。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。