1.概论
- 流提供了一种让我们可以在比集合更高的概念级别上指定计算的数据视图。通过使用流,我们可以说明想要完成什么任务,而不是说明如何去实现它。
2.流与集合的区别
- 流并不存储其元素,这些元素可能存储在底层的集合中,或者按需生成。
- 流的操作不会修改其数据源。
- 流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。
3.流的创建
使用Stream.empty()创建不包含任何元素的流。
Stream
stream = Stream.empty(); 创建无限流。一个是使用Stream.generate(Supplier
),传入一个Supplier 接口对象。
Stream
stream=Stream.generate(Math::random);
或者通过下面的方法获得一个常量值的流:
Stream
stream = Stream.generate(() -> "Echo"); 另一个获取无限流的方法是Stream.iterate( seed, UnaryOperator);该方法会接受一个“种子”,以及一个函数(UnaryOperator
),并且会反复地将该函数应用到之前的结果上。
Stream
integers =Stream.iterate(BigInteger.ZERO,n->n.add(BigInteger.ONE));
该流中第一个元素是种子BigInteger.ZERO,第二个元素是f(seed),即,1,下一个元素是f(f(seed)),即2.后续以此类推。Collection接口的stream()将任何集合转换为一个流。
List
words = ...; words.stream()... 使用Stream类的静态方法,传入一个数组生成流
Stream
words = Stream.of("apple","banana"}); 使用Arrays.stream(array,from,to) 可以从数组中位于from(包括)和to(不包括)的元素中创建一个流。
String[] array = new String[]{"apple","banana","orange"};
Stream
stream = Arrays.stream(array,1,2); 静态的Files.lines(Path)方法会返回一个包含了文件中所有行的Stream:
Stream
lines = Files.lines(path);
4.流的转换
- filter(Predicate
)
对流中的元素进行过滤,引元是Predicate,即从T到boolean的函数。
List
wordList=...;
Stream
longWords = wordList.stream().filter(w -> w.length() > 12); - map(Function mapper)
对流中的元素应用提供的函数,引元是Function mapper
Stream
firstLetters = words.stream().map(s -> s.substring(0,1)); - limit(long maxSize):返回一个新的流,其中包含当前流中最初的maxSize个元素
Stream
words = Stream.generate(Math::random).limit(100); - skip(long n):产生一个流,它的元素是当前流中除了前n个元素之外的所有元素。
Stream
words = Stream.of("apple","banana","orange").skip(1); - concat(Stream a, Stream b):产生一个流,它的元素是a的元素后面跟着b的元素。
Stream
combined = Stream.concat(Stream.of("apple","banana"),Stream.of("pear"));
///combined中的元素 ["apple","banana","pear"]
需要注意的是,第一个流不应该是无限的,否则第二个流永远不会被处理。 - distinct():会返回一个根据原来流中的元素剔除重复元素后产生的流。元素的顺序不变。
Stream
uniqueWords = Stream.of("apple","apple","banana","banana","orange");
///uniqueWords 中的元素["apple","banana","pear"]
- peek(Consumer action):产生一个流,它与当前流中的元素相同,在获取其中每个元素时,会将其传递给action。
5.Optional类型
- 概论:Optional
对象是一种包装器对象,要么包装了类型T的对象,要么没有包装任何对象。对于第一种情况,我们称这种值为存在的。Optional 类型被当作一种更安全的方式,用来替代类型T的引用,这种引用要么引用某个对象,要么为null。 - Optional的使用
- 如何在不存在任何值的情况下产生相应的替代物
- 当Optional的值不存在时,希望使用某种默认值替代。
String result = optionalString.orElse("");
//result的值为optionalString包装的字符串,如果为null,则为空字符串
- 或者调用代码计算默认值:
String result = optionalString.orElseGet(() -> "");
//如果optionalString包装的字符串为null,则调用传入的函数生成默认值。
- 或者可以在没有任何值时抛出异常
String result = optionalString.orElseThrow(IllegalStateException::new);
//提供一个生成异常对象的函数。
- 只有在其存在的情况下才消费该值。
- ifPresent方法会接受一个函数,如果该可选值存在,那么它会被传递给该函数。否则,不会发生任何事情。
optionalValue.ifPresent(v -> result.add(v) );
- 当调用ifPresent时,从该函数不会返回任何值。如果想要处理函数的结果,应该使用map:
Optional
added = optionalValue.map(results::add);
此时的added具有三种值之一:在optionalValue存在的情况下包装在Optional中的true或false,以及在optionalValue不存在的情况下的空Optional。
- 创建Optional值
- Optional.of(T value)
- Optional.ofNullable(T value)
产生一个具有给定值的Optional。如果value为null,那么第一个方法会抛出NullPointerException对象,而第二个方法会产生一个空Optional。
- Optional.empty() :产生一个空Optional对象。
- 用flatMap来构建Optional值的函数
假设你有一个可以产生Optional对象的方法f,并且目标类型T具有一个可以产生Optional对象的方法g。如果他们都是普通方法,那么你可以调用s.f().g()来将他们组合起来。但是这种组合没法工作,因为s.f()的类型为Optional ,而不是T。因此,需要调用:
Optional result = s.f().flatMap(T::g);
如果s.f()的值存在,那么g就可以应用到它上面。否则,就会返回一个空Optional。
很明显,如果有更多的可以产生Optional值的方法或Lambda表达式,那么就可以重复此过程。你可以直接将对flat Map的调用链接起来,从而构建由这些步骤构成的管道,只有所有不走都成功时,该管道才会成功。
6.收集结果
- iterator(),产生可以用来访问元素的迭代器。
- forEach(Consumer action),将某个函数应用于每个元素。
stream.iterator()
stream.forEach(System.out::println);
- toArray():返回一个Object[]数组。
- toArray(String[]::new):返回一个String数组
Object[] result = stream.toArray();
String[] result = stream.toArray();
- 将流中的元素收集到另一个目标中,有一个便捷的collect方法可用。它会接受一个Collector接口的实例。Collectors类提供了大量用于生成公共收集器的工厂方法。
- collect(Collectors.toList()):生成一个列表 ArrayList
- collect(Collectors.toSet()):生成一个集合 HashSet
- collect(Collectors.toCollection(Supplier
collectionFactory)):生成给定的集合类型
List
result = stream.collect(Collectors.toList());
Set
result = stream.collect(Collectors.toSet());
TreeSet
result = stream.collect(Collectors.toCollection(TreeSet::new));
- collect(Collectors.joining()):连接流中的所有字符串;
- collect(Collectors.joining(CharSequence delimiter):通过分隔符连接所有的字符串;
- collect(Collectors.joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix):使用分隔符连接所有的字符串,将prefix作为生成的字符串的前缀,suffix作为字符串的后缀。
Stream
stream = Stream.of("apple","banana");
String result = stream.collect(Collectors.joining()); // result = "applebanana"
result =stream.collect(Collectors.joining(",")); // result = "apple,banana"
result =stream.collect(Collectors.joining(",","prefix","suffix"));
// result = "prefixapple,bananasuffix"
- 如果想要将流的结果约简为总和、平均值、最大值或最小值,可以使用summarizing(Int|Long|Double)方法中的某一个。这些方法会产生类型为(Int|Long|Double)SummaryStatictics的结果,同时计算总和、数量、平均值、最小值和最大值。
Stream
word = Stream.of("apple","banana");
IntSummaryStatistics summary = word.collect(Collectors.summarizingInt(String::length) );
//summary {count=2, sum=11, min=5, average=5.500000, max=6}
- collect(Collectors.toMap(Function keyMapper,Function valueMapper):将其元素收集到映射表中。该方法有两个函数引元,它们用来产生映射表的键和值。
- collect(Collectors.toMap(Function keyMapper,Function valueMapper,BinaryOperator mergeFunction):将其元素收集到映射表中。该方法有三个函数引元,前两个用来产生映射表的键和值,第三个用于合并键值相同的项。
- collect(Collectors.toMap(Function keyMapper,Function valueMapper,BinaryOperator mergeFunction,Supplier
mapSupplier):将其元素收集到映射表中。该方法有四个函数引元,前两个用来产生映射表的键和值,第三个用于合并键值相同的项,第四个函数用于指定生成的映射表。
public static class Person{
private int id;
private String name;
public Person(int id ,String name){this.id=id;this.name= name;}
public int getId() {return id;}
public String getName() {return name;}
@Override
public String toString(){
return getClass().getName()+"[id="+id+",name="+name+"]";
}
public static Stream people(){
return Stream.of(new Person(1001,"Tom"),new Person(1002,"Jack"),new Person(1003,"Bob"));
}
}
public static void main(String[] args) {
Map idToName = Person.people().collect(Collectors.toMap(Person::getId,Person::getName));
System.out.println(idToName);
//idToName {1001=Tom, 1002=Jack, 1003=Bob}
Map idToPerson = Person.people().collect(Collectors.toMap(Person::getId,Function.identity()));
System.out.println("idToPerson "+idToPerson);
//idToPerson {1001=com.company.Main$Person[id=1001,name=Tom], 1002=com.company.Main$Person[id=1002,name=Jack], 1003=com.company.Main$Person[id=1003,name=Bob]}
Map idToPerson=Person.people().collect(Collectors.toMap(Person::getId,Function.identity(),(oldValue,newValue)->newValue ));
Map idToPerson=Person.people().collect(Collectors.toMap(Person::getId,Function.identity(),(oldValue,newValue)->newValue ,TreeMap::new));
}
- collect(Collectors.groupingBy(Function classifier)):通过classifier函数对流中的元素进行分类。
Stream locals = Stream.of(Locale.getAvailableLocales());
Map> country = locals.collect(Collectors.groupingBy(Locale::getCountry));
System.out.println("languageNames:"+country);
//languageNames:{DE=[de_DE],...}
- collect(Collectors.partitioningBy(Predicate predicate)):通过使用断言函数对流中的元素进行分区。
Map> list = locals.collect(Collectors.partitioningBy(locale -> locale.getLanguage().equals("en")));
List english = list.get(true);
System.out.println(english);
//[en_US, en_SG, en_MT, en, en_PH, en_NZ, en_ZA, en_AU, en_IE, en_CA, en_IN, en_GB]
- 下游收集器:是在收集器方法中再次传入一个收集器对象,用于处理groupingBy方法生成的值列表。常用的下游收集器有:
- counting:返回收集到的元素的个数
- summing(Int|Long|Double):接受一个引元函数,将该函数应用到下游元素中,并产生他们的和。
- maxBy和minBy:会接受一个比较器,并产生下游元素中的最大值和最小值。
- mapping:将函数应用到下游元素,并将函数值传递给另一个收集器。
- summarizing(Int|Double|Long):如果grouping或partitioningBy的返回值为int,long,double,那么可以将元素收集到汇总统计对象中。
public static class City{
private String state;
private String name;
private int population;
public City(String state,String name,int population){
this.state=state;
this.name=name;
this.population=population;
}
public String getName() {return name;}
@Override
public String toString(){
return getClass().getName()+"[state="+state+",name="+name+"population="+population+"]";
}
public String getState() {
return state;
}
public int getPopulation() {
return population;
}
}
public static Stream cities(){
return Stream.of(new City("1001","Tom",1000),new City("1002","Jack",2000),new City("1003","Bob",3000));
}
public static void main(String[] args) {
Stream locales = Stream.of(Locale.getAvailableLocales());
Map> countryToLocaleSet=locales.collect(groupingBy(Locale::getCountry,toSet()));
System.out.println("countryToLocals:"+countryToLocaleSet);
//countryToLocals:{PY=[es_PY], LV=[lv_LV], HR=[hr_HR], DO=[es_DO], UA=[uk_UA], YE=[ar_YE],}
locales=Stream.of(Locale.getAvailableLocales());
Map countryToLocaleCounts = locales.collect(groupingBy(Locale::getCountry,counting()));
System.out.println("countryToLocaleCounts" +countryToLocaleCounts);
//countryToLocaleCounts{=46, DE=1, PR=1, HK=1, TW=1, PT=1, ...}
Stream cities = cities();
Map stateToCityPopulation = cities.collect(groupingBy(City::getState,summingInt(City::getPopulation)));
System.out.println("stateToCityPopulation"+stateToCityPopulation);
//stateToCityPopulation{1003=3000, 1002=2000, 1001=1000}
cities=cities();
Map> stateToLongestCityName = cities.collect(
groupingBy(City::getState,
mapping(City::getName,maxBy(Comparator.comparing(String::length)))
)
);
System.out.println("stateToLongestCityName"+stateToLongestCityName);
//stateToLongestCityName{1003=Optional[Bob], 1002=Optional[Jack], 1001=Optional[Tom]}
locales =Stream.of(Locale.getAvailableLocales());
Map> countryToLanguages = locales.collect(
groupingBy(Locale::getDisplayCountry,
mapping(Locale::getDisplayLanguage,toSet()))
);
System.out.println("countryToLanguages"+countryToLanguages);
//countryToLanguages{泰国=[泰文], 巴西=[葡萄牙文],...
cities=cities();
Map stateTpCityPopulationSummary = cities.collect(groupingBy(
City::getState,summarizingInt(City::getPopulation)));
System.out.println("stateTpCityPopulationSummary"+stateTpCityPopulationSummary);
//stateTpCityPopulationSummary{1003=IntSummaryStatistics{count=1, sum=3000, min=3000, average=3000.000000, max=3000},
cities=cities();
Map stateToCityNames = cities.collect(groupingBy(
City::getState,reducing("",City::getName,(s,t)->s.length()==0?t:s+","+t)
));
System.out.println("stateToCityNames"+stateToCityNames);
//stateToCityNames{1003=Bob, 1002=Jack, 1001=Tom}
cities=cities();
stateToCityNames=cities.collect(groupingBy(City::getState,mapping(City::getName,joining(","))));
System.out.println("stateToCityNames"+stateToCityNames);
// stateToCityNames{1003=Bob, 1002=Jack, 1001=Tom}
}
7.基本类型流
将每个整数都包装到包装器对象中是很低效的,对其他基本类型来说,情况也是一样。这些基本类型是:double,float,long,int,short,char,byte和boolean。流库中具有专门的流类型IntStream,LongStream和DoubleStream,用来直接存储基本类型值,而无需使用包装器。如果想要存储short,char,byte和boolean,可以使用IntStream,而对于float,可以使用DoubleStream.