- 流提供了一种让我们可以在比集合更高的概念级别上指定计算的数据视图 。通过使用流,我们可以说明想要完成什么任务,而不是说明如何去实现它
- 使用集合、顺序流以及并行流对特定长度字符计数的例子:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class TestForStream {
public static void main(String[] args) {
// TODO Auto-generated method stub
String[] strings = {
"hello","world","hust","hh","computer","science","math"
};
//将数组转成List,只有集合才能调用stream()
List<String> words = Arrays.asList(strings);
//使用集合遍历统计长度大于3的字符串个数
long count1 = 0;
for (String word : words) {
if(word.length() > 3) count1++;
}
//使用顺序流计算长度大于3的字符串个数
long count2 = Stream.of(strings).filter(w -> w.length() > 3).count();
//使用顺序流对数组中特定范围的元素进行统计其中长度大于3的个数
long count3 = Arrays.stream(strings, 0, 4).filter(w -> w.length() > 3).count();
//使用并行流计算长度大于3的字符串个数
long count4 = words.parallelStream().filter(w -> w.length() > 3).count();
System.out.println("count1: " + count1 + " count2: " + count2 + " count3: " + count3 + " count4: " + count4);
}
}
- 针对上例,需注意:
- 只有集合对象才能调用 stream 方法,数组无法调用
- 但是可以使用Steam的静态方法,Stream.of( T.. ),,将数组作为参数传入从而得到其流
- 数组可以调用 Arrays.stream( array, from, to),从数组中位于 from 和 to 的元素创建一个流(书上写的 Array.stream 应该是打印错了)
- 流库的 count 方法返回的是一个 long 值
- 流和集合之间的差异:
- 流并不存储元素。这些元素可能存储在底层的集合中,或者是按需生成的
- 流的操作不会修改其源数据。比如,filter 方法不会从原来的流中移除元素,而是会生成一个新的流,其中不包含被过滤掉的元素
- 流的操作是尽可能惰性执行的
- 几种产生流的方法:
import java.math.BigInteger;
import java.util.Iterator;
import java.util.stream.Stream;
public class TestForStream {
public static void main(String[] args) {
//创建不包含任何元素的流
Stream<String> stream1 = Stream.empty();
//获得无限流
Stream<String> stream2 = Stream.generate(() -> "echo");
Stream<Double> stream3 = Stream.generate(Math::random);
Iterator<Double> it3 = stream3.iterator();
while (it3.hasNext()) {
System.out.println(it3.next());
}
//获得无限序列的流
Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
}
}
- map 和 flatMap 的区别:map 返回的结果可能是一个包含流的流,即Stream<Stream<T>>,flatMap 返回的结果只是一个流,即Stream<T>。(就如其名一样,flat,展平)
- 抽取子流和连接流:
import java.util.Iterator;
import java.util.stream.Stream;
public class TestForStream {
public static void main(String[] args) {
//limit(100)为将原流转成元素共100个的流
//skip(5)为将原流(100个元素的流)转成跳过前5个元素的流
Stream<Integer> stream = Stream.iterate(1,n -> n+1).limit(100).skip(5);
Stream<Integer> stream2 = Stream.iterate(1000, n -> n + 1).limit(100);
//将stream和stream2连接
Stream<Integer> stream3 = Stream.concat(stream, stream2);
Iterator<Integer> it3 = stream3.iterator();
while (it3.hasNext()) {
System.out.println(it3.next());
}
}
}
- 其他流的转换(若一个流转换成了另一个流,则先前的流就会被关闭,无法再被使用):
import java.util.Comparator;
import java.util.Iterator;
import java.util.stream.Stream;
public class TestForStream {
public static void main(String[] args) {
//创建数组流
Stream<String> stream = Stream.of("hust", "hust", "hh");
//distinct去除原流中的重复元素
//sorted按给定的comparator参数排序。这里给定的 是按照String的length长度的逆序排序,即按字符串的长度由长到短排序
Stream<String> distinctSortedStream = stream.distinct().sorted(Comparator.comparing(String::length).reversed());
//获得迭代器
Iterator<String> it1 = distinctSortedStream.iterator();
//遍历流中的元素
while (it1.hasNext()) {
System.out.println(it1.next());
}
//peek为取元素流,每次取一个元素时,都会调用一个函数
Object[] powers = Stream.iterate(1, n -> n * 2).peek(e -> System.out.println("Fetching " + e)).limit(20).toArray();
}
}
- 简单约简:约简是一种终结操作,它们会将流约简为可以在程序中使用的非流值。如之前的 count 方法
import java.util.Optional;
import java.util.stream.Stream;
public class TestForStream {
public static void main(String[] args) {
//创建流
Stream<String> stream = Stream.of("for", "Test", "Stream", "Java","Find","train");
//调用流的max方法,并给出comparator,得到流中的最大元素
Optional<String> largest = stream.max(String::compareToIgnoreCase);
//如果largest为null,则输出"",否则输出最大元素
System.out.println(largest.orElse(""));
//重新创建流,之前的流在使用过后已经被关闭了
Stream<String> stream1 = Stream.of("for", "Test", "Stream", "Java","find","train");
//找到流中第一个以f开头的元素
Optional<String> startWithF = stream1.filter(s -> s.startsWith("f")).findFirst();
//输出找到的元素或者""
System.out.println(startWithF.orElse(""));
}
}
- Optional类型:Optional<T> 对象是一种包装器对象,要么包装了类型T的对象,要么没有包装任何对象。对于第一种情况,我们称这种值为存在的。Optional <T> 类型被当做一种更安全的方式,用来替代类型T的引用 ,这个引用要么引用某个对象,要么为null
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class TestForStream {
public static void main(String[] args) {
List<String> results = new ArrayList<>();
//创建流
Stream<String> stream = Stream.of("for", "Test", "Stream", "Java","Find","train");
// Stream<String> stream = Stream.empty();
//调用流的max方法,并给出comparator,得到流中的最大元素
Optional<String> largest = stream.max(String::compareToIgnoreCase);
//判断largest中是否存在值
System.out.println(largest.isPresent());
//获得largest中的值,若largest中不存在值,则抛出异常 NoSuchElementException
System.out.println(largest.get());
//如果largest存在,则将其添加到results中,如果
largest.ifPresent(results::add);
System.out.println(results.size() > 0 ? results.get(0) : "null");
//创建Optional值。empty方法创建一个Optional对象,内为null。
//ofNullable方法在参数obj不为空的情况下返回Optional.of(obj),否则返回Optional.empty()
Optional<Double> optional1 = Optional.empty();
Optional<Double> optional2 = Optional.ofNullable(0.85);
//用flatMap构建Optional值的函数
//无论是inverse方法还是squareRoot返回Optional.empty(),则res中只含null
Optional<Double> res = Optional.of(4).flatMap(TestForStream::inverse).flatMap(TestForStream::squareRoot);
System.out.println(res.get());
}
//安全地求一个数的倒数
public static Optional<Double> inverse(double x) {
return x == 0 ? Optional.empty() : Optional.of(1 / x);
}
//安全地求一个数的平方根
public static Optional<Double> squareRoot(double x) {
return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}
}
- 收集结果:
import java.util.ArrayList;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestForStream {
public static void main(String[] args) {
List<String> results = new ArrayList<>();
//创建顺序流
Stream<String> stream = Stream.of("for", "Test", "Stream", "Java","Find","train");
//在顺序流上遍历流元素。若在并行流,则遍历顺序不一,可使用forEachOrdered 遍历
stream.forEach(System.out::println);//终结操作
//获得由流的元素组成的数组。 stream.toArray()会返回一个Object[]数组
//如果想让数组具有正确的类型,可以将类型传递到数组构造器中
String[] res = stream.toArray(String[]::new);
//使用Stream的实例方法collect可以将流中的元素收集到另一个目标中,需要提供一个Collector接口的实例。
//Collectors类提供了大量用于生成公共收集器的工厂方法
List<String> list = stream.collect(Collectors.toList());//将流收集到列表中
Set<String> set = stream.collect(Collectors.toSet());//将流收集到集中
TreeSet<String> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));//将流收集到确定的TreeSet中
String result = stream.collect(Collectors.joining("@"));//通过连接操作,并添加分隔符收集
//Collectors.summarizingInt(String::length)将流中每个字符串映射为其长度,再收集
IntSummaryStatistics summary = stream.collect(Collectors.summarizingInt(String::length));
double averageLength = summary.getAverage();//获取流中元素长度的平均值
double maxLength = summary.getMax();//获取流中元素长度的最大值
}
}
- 收集到映射表中:
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestForStream {
public static void main(String[] args) {
//获得由所有可用国际化语言的缩写组成的流
Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
//使用将流中每个元素(Locale)映射为映射表(key-value),并收集到Map中
//toMap中第一个参数含义为:对流中每个元素执行getDisplayCountry方法,其结果用作key
//第二个参数含义为:对流中的每个元素l,转成单例集合,其中含元素l.getDisplayLanguage()
//第三个参数含义为:对于出现键值重复的情况,a为之前的value,b为当前的value,对其进行并操作,再返回结果作为键的值
//第四个参数为转成一个特定的TreeMap提供一个构造器(可省略)
Map<String, Set<String>> countryLanguageSets = locales.collect(
Collectors.toMap(Locale::getDisplayCountry,
l -> Collections.singleton(l.getDisplayLanguage()),
(a,b) -> {
Set<String> union = new HashSet<>(a);
union.addAll(b);
return union;
},
TreeMap::new)) ;
Set<String> keys = countryLanguageSets.keySet();
for(String str : keys) {
System.out.print(str + ":");
for(String s : countryLanguageSets.get(str)) {
System.out.print(s + " ");
}
System.out.println();
}
}
}
- 群组和分区:群组即按某一个分类标准将流中元素分组并收集,分区则针对于断言函数(返回boolean值的函数),流的元素被分为两个组(即返回true的组和返回false的组)
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestForStream {
public static void main(String[] args) {
//获得由所有可用国际化语言的缩写组成的流
Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
Stream<Locale> locales1 = Stream.of(Locale.getAvailableLocales());
//对locales中的每个元素,使用groupingBy按照getCounty方法返回的结果分组(返回结果一样作为一组)
//getCountry返回的结果作为键,其对应的value为该国家使用的语言集
Map<String, List<Locale>> countryToLocales = locales.collect(Collectors.groupingBy(Locale::getCountry));
Set<String> keys = countryToLocales.keySet();
for (String key : keys) {
System.out.print(key + ": ");
for (Locale lan : countryToLocales.get(key)) {
System.out.print(lan + " ");
}
System.out.println();
}
System.out.println();
//对locales1中每个元素使用partitionBy方法按照其语言是否为“zh”(中文)返回的boolean结果来分区
//即是中文的为同一个区,不是中文的为同一个区
//键为False和True,对应value为集合
Map<Boolean, List<Locale>> englishAndOtherLocales = locales1.collect(
Collectors.partitioningBy(l -> l.getLanguage().equals("zh")));
List<Locale> englishLocales = englishAndOtherLocales.get(true);
englishLocales.forEach(System.out::println);
}
}
- 下游收集器:用于处理由 groupingBy 产生的每个列表
//获得由所有可用国际化语言的缩写组成的流
Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
//对locales中的每个元素,使用groupingBy按照getCounty方法返回的结果分组(返回结果一样作为一组)
//getCountry返回的结果作为键,其对应的value为该国家使用的语言集,并使用Set存储每个组的value
Map<String, Set<Locale>> countryToLocales = locales.collect(
Collectors.groupingBy(Locale::getCountry,Collectors.toSet()));
//对每个组进行counting(),计数并返回每个组的计数结果作为value
Map<String, Long> countryToLocalesCount = locales.collect(
Collectors.groupingBy(Locale::getCountry,Collectors.counting()));
- 约简操作reduce:类似 Python 的内置函数 reduce
- 流库中专门用来直接存储基本类型值的类有:IntStream、LongStream 和 DoubleStream。想要存储short、char、byte和boolean,可以使用IntStream,而对于float,可以使用DoubleStream
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class TestForStream {
public static void main(String[] args) {
//创建基本类型流的五种方法,以IntStream为例
IntStream stream = IntStream.of(1,3,5,7,9);
IntStream stream2 = IntStream.range(0, 10);
IntStream stream3 = IntStream.rangeClosed(0, 10);
IntStream stream4 = IntStream.generate(() -> (int) (Math.random() * 100));
IntStream stream5 = IntStream.iterate(1, n -> n + 1);
//将基本类型流stream5转成对象流,调用boxed方法
Stream<Integer> integers = stream5.boxed();
//将对象流转成基本类型流,调用mapToInt方法,并提供一个mapper函数
Stream<String> words = Stream.of("hello","hust");
IntStream lengths = words.mapToInt(String::length);
}
}
- 基本类型流上的方法与对象流上的方法的差异:
- 对于基本类型流,toArray 方法会返回基本类型数组
- 对于基本类型流,产生结果的方法会返回一个 OptionalInt、OptionalLong 或者 OptionalDouble。这些类与 Optional 类类似,但是具有 getAsInt、getAsLong 和 getAsDouble 方法,而不是 get 方法
- 对于基本类型流,具有返回总和、平均值、最大值和最小值的 sum、average、max 和 min 方法,对象流没有定义这些方法
- summaryStatistics 方法会产生一个类型为 IntSummaryStatistics、LongSummaryStatistics 或者 DoubleSummaryStatistics的对象,它们可以同时报告流的总和、平均值、最大值和最小值
- 并行流:
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestForStream {
public static void main(String[] args) {
String[] strings = {"hello","hust"};
List<String> list = Arrays.asList(strings);
//从数组获得并行流
Stream<String> stream1 = Stream.of(strings).parallel();
//从集合获得并行流
Stream<String> stream2 = list.parallelStream();
//使用并行流对各个长度小于10的单词统计个数
Map<Integer, Long> shortWordCount = stream1
.filter(s -> s.length() < 10)
.collect(Collectors.groupingBy(String::length,Collectors.counting()));
}
}
- 为了让并行流正常工作,需要满足大量的条件:
- 数据应该再内存中。必须等待数据到达是非常低效的
- 流应该可以被高效地分成若干个子部分。由数组或平衡二叉树支撑的流都可以工作地很好,但是 Stream.iterate 返回的结果不行
- 流操作的工作量应该具有较大的规模。如果总工作负载不是很大,那么搭建并行计算时所付出的代价就没有什么意义
- 流操作不应该被阻塞
- 不要将所有的流都转换为并行流,只有在对已经位于内存中的数据执行大量计算操作时,才应该使用并行流