您的位置:首页 > 编程语言 > Java开发

JAVA SE 8 学习笔记(二)Stream API

2016-05-02 16:47 549 查看
二、Stream API

1.stream不会自己储存元素。元素存储在底层集合中,或根据需要生产出来

2.stream操作符不会改变原对象,而是返回一个持有结果的新的Stream

3.stream操作符可能是延迟执行的。等需要结果时才执行。

2.1 创建Stream

1. Collection接口中新增Stream方法,可以将一个集合转化为Stream

例:

List<String> words = ...;

words.Stream();

2. 也可以用静态的Stream.of方法

例:Stream<String> words = Stream.of(....);

3. Stream接口的generate方法接收一个参数或函数,产生一个Stream

例:

// 返回随机数字的Stream

Stream.generate(Math::random);

// 返回常量值Stream

Stream.generate(()-> "Echo");

4. itereate方法可以产生一个无限序列,第一个参数是种子,第二个参数是产生序列的函数

// 1,2,3,4,5...

Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));

5. 获取空的stream

Stream.empty();

注意:Stream接口有一个AutoCloseable父接口,在打开资源文件的Stream时,为了保证文件关闭最好使用try-with-resources语句

例:

// 获取文件中所有行的Stream

try(Stream<String> lines = File.lines(path)) {

// do something

}

2.2 filter、map、flatMap

1. filter方法参数是Predicate<T>对象,即T到boolean函数,通过该函数对Stream进行过滤

例:

words.filter(w -> w.length() > 12); // 获取长度大于12的单词

2. map方法参数中的 lambda表达式/方法引用 会作用于每一个元素

例:

words.map(String::toLowerCase); // 将所有单词转换为小写

3. flatMap把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起

例:

Stream<List<Integer>> inputStream = Stream.of(

 Arrays.asList(1),

 Arrays.asList(2, 3),

 Arrays.asList(4, 5, 6)

 );

Stream<Integer> outputStream = inputStream.

flatMap((childList) -> childList.stream());

结果[1,2,3,4,5,6]

2.3 提取子流和组合流

1. Stream.limit会限制返回的元素个数,Stream.skip会丢弃指定的元素个数

2. Stream.concat方法可以将两个流连接在一起

3. peek方法会产生与原始流相同元素的流,但是每次获取一个元素时,会调用个函数。(很像动态代理AOP)

例:

// 访问某个元素时会打印一条信息,可以确定iterate方法返回的无限流是否被延迟处理

Object[] powers = Stream.iterate(1, p -> p*2).peek(e -> System.out.println(e);).limit(20).toArray();

2.4 有状态的转换

1. distinct方法会消除重复元素 

例:

Stream.of("a", "a", "b"); // 只获取一个a

2. sorted方法必须遍历整个流,因此无法对无限流进行排序

例:

words.sort(Comparator.comparing(String::length).reversed());

注意:Collections.sort方法对原集合排序,Stream.sortedUI返回一个新的已排序流

2.5 简单的聚合方法

1. max/min 方法分别返回最大值和最小值 返回类型为Optional<T>(它可能封装返回值,也可能表示没有返回值,Optional是一种更好表示缺少返回值的方式)

例:

Optional<String> largest = words.max(String::compareToIgnoreCase);

if(largest.isPresent()) {

     // do something

}

2. findFirst会返回第一个值

例:

Optional<String> first = words.filter(e -> s.startWith("Q")).findFirst();

3. findAny会返回发现的第一个元素并结束计算,在并行执行时十分有效

Optional<String> first = words。parallel().filter(e -> s.startWith("Q")).findAny();

4. anyMatch/allMatch/noneMatch 分别在Predicate<T>参数存在匹配、完全匹配、没有匹配时返回true

例:

boolean isAnyMatch= words.parallel().anyMatch(s -> s.startWith("Q"));

2.6 Optional类型

1. Opional<T>对象是对T类型的对象进行封装,正确使用下不会返回null

isPresent方法返回改对象是否有值

get方法则会返回具体值

2.高效使用Optional的关键在于,使用一个正确值,或者返回另外一个替代值

2.1  ifPresent方法参数为一个函数,当存在可选值,将值传递给函数,否则不进行任何操作

例:

optionalValue.ifPresent(v -> results.add(v));

optionalValue.ifPresent(results::add)
1565a
;

ifPresent方法没有返回值,如果希望对结果进行

处理可以使用map方法

Optional<Boolean> added = optionalValue.map(results:add);

2.2  当没有可匹配项时,可使用orElse方法产生一个替代值

例:

String result = optionalString.orElse(""); // 如果没有值,返回空字符串

String result = optionalString.orElseGet(() -> System.getProperty("user.dir"));
//
如果没有值,调用函数

String result = optionalString.orElseThrow(NoSuchElementException::new);
//
如果没有值,抛出异常

3 创建可选值

Optional.of(result) 根据参数创建

Optional.empty() 创建空的Optional

Optional.ofNullable(obj)  当obj为null时返回Optional.empty(),否则返回Optional.of(obj)

4.
使用flatMap来组合可选值函数

假设方法f返回Optional<T>,T中的g方法返回Optional<U>,则可以利用flatMap创建一个调用的流水线。

Optional<U>
= s.f.flatMap(T::g);

如果s.f()不存在则会返回空的Optional<U>

例:

public static Optional<Double> squareRoot(Double x) {

return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));

}

Optioanl<Double> result =  Optional.of(-4.0).flatMap(Test::squareRoot);

2.7  聚合操作

对元素进行求和等聚合操作

Optional<Integer> sum = values.reduce(Integer::sum);

如果有一个标识e使得e op x = x,那么可以理用这个标识作为聚合的起点,从而省去特殊null情况的操作

Optional<Integer> sum = values.reduce(0, Integer::sum);

2.8  收集结果

当处理完流之后,可以使用iterator方法生成一个传统风格的迭代器

也可以通过toArray[]方法获取一个Object数组,如果需要穿件正确类型的数组,可以将类型构造函数传递给方法

例:String[]
result = words.toArray(String[]::new);

如果想将结果放到HashSet中,由于HashSet不是线程安全的,需要使用collect方法(并行收集每一段都要从空的HashSet开始,而聚合函数只能允许一个标识)

collect方法三个参数:

1. 目标类型构造函数

2. add方法

3. addAll方法

例:

stream.collect(HashSet::new, HashSet::add, HashSet::addAll);

实际中Collector接口已经提供了这个方法,Collectors类提供了多个工厂方法

1. List

List<String> result = stream.collect(Collectors.toList());

2. Set

Set<String> result = stream.collect(Collectors.toSet());

如果希望控制Set的类型

TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet::new));

3. 拼接流中的字符串

String result = stream.collect(Collectors.joing());

String
result = stream.collect(Collectors.joing(", "));
// 逗号分隔拼接

4.统计数字类型流的总和、平均值、最大最小值

IntSummaryStatistics
summary = words.collect(Collectors.summarizingInt(String::length));

summary.getAverage();

summary.getMax();

5.
遍历结果集

stream.forEach(System.out::println);

在一个并行流上可能按任何顺序访问元素

如果希望按照顺序执行,应使用forEachOrdered,但是会失去并行计算带来的好处

forEach和forEachOrdered是终止操作,调用后不可再使用该流。如果需要继续使用,请参照peek方法

2.9  将结果收集到Map中

Collectors.toMap参数

1.
key获取函数

2.
value获取函数

3.
当出现相同key的value时,解决冲突的merge函数

4.
返回类型的构造函数

Stream<Locale>
locals = Stream.of(Locale.getAvailableLocales());

Map<String, Set<String>> countryLanguageSets 

= locals.collect(Collectors.toMap(
l -> l.getDisplayCountry(),
l -> Collections.singleton(l.getDisplayLanguage()),
(a, b) -> {
Set<String> r = new HashSet<>(a);
r.addAll(b);
return r;
}));

希望得到指定类型的Map时

Map<String,
Set<String>> countryLanguageSets 
= locals.collect(Collectors.toMap(
l -> l.getDisplayCountry(),
l -> Collections.singleton(l.getDisplayLanguage()),
(a, b) -> {
Set<String> r = new HashSet<>(a);
r.addAll(b);
return r;
},

TreeMap::new));

对于toMap方法的每种形式,都有一个对应的toConcurrentMap方法,产生一个并发的Map

2.10  分组和分片

Collectors.groupingBy函数

// 根据国家分组



Map<String, List<Locale>> contryToLocals = 
locals.collect(Collectors.groupingBy(Locale::getCountry));
// 当分类函数是Predicate函数时,使用partitioningBy更有效率

Map<Boolean, List<Locale>> contryToLocals = 
locals.collect(Collectors.partitioningBy(l -> l.getLanguage().equals("en")));

groupby之后可以提供一个downstream收集器对分组结果进行收集

*假设已经静态引入了Collectors.*

1. counting()方法返回总数

Map<String, Long>> contryToLocals = 
locals.collect(Collectors.groupingBy(Locale::getCountry, counting());

2. summing(Int|Long|Double)方法通过参数中的函数求和

// 每个州人口总和

Map<String,
Integer> contryToLocals = 
locals.collect(Collectors.groupingBy(City::getState,
summingInt(City::getPopulation));

3. maxBy/mayMin接收一个比较器,返回最大/最小值

// 返回人口最多的城市

Map<String,
City> contryToLocals = 
locals.collect(Collectors.groupingBy(City::getState,
maxBy(Comparator.comparing(City::getPopulation))));

4.
mapping方法将一个函数应用到downstream上,并需要一个收集起来处理结果

//
获取每个国家的语言

Map<String,
Set<String>> countryToLanguage = locals
.collect(groupingBy(l -> l.getDisplayCountry(), mapping(l -> l.getDisplayLanguage(), toSet())));

5.
如果mapping函数返回值是int,long,double可以使用summarizing方法进行统计

Map<String,
IntSummaryStatistics> summary = locals
.collect(groupingBy(City::getState, summarizingInt(City::getPopulation)));

对每组的IntSummaryStatistics可以进行取平均值等操作

6. reduce

使用较少,一般直接用Stream.reduce,此处略

2.11  原始类型流

StreamAPI提供了IntStream、LongStream、DoubleStream等类型来直接存储原始类型值,不必使用包装。

如果想要存储short、char、byte、boolean请使用IntStream

float请使用DoubleStream

例:

1.

 IntStream stream = IntStream.of(1,2,3,4,5);

2. 

int[] values= ...;

// 将数组索引从from到to的元素加入Stream

IntStream stream = Arrays.stream(values,
from, to);

3. 

IntSream zeroToNinetyNine = IntStream.range(0,
100);

4.

IntSream
zeroToHundred = IntStream.rangeClosed(0, 100);

当你拥有一个对象流的时候可以通过mapToInt、mapToLong或者mapToDouble转换为原始类型流

Stream<String> words = ...;

IntStream lengths = words.mapToInt(String::length);

将原始对象刘转化为对象流可以使用boxed方法

Stream<Integer> integers  = IntStream.range(0, 100).boxed();

其他方法:

toArray 返回原始类型数组

sum、average、max、min 

summaryStatistics 获取统计信息

产生Optional结果的方法返回OptionalInt等类型,这些类型没有get方法,而是对应getAsInt等来代替

2.12  并行流

parallel方法可以将任意流转换为并行流

例:

Stream<String> parallelWords = Stream.of(wordArray).parallel();

Collections.parallelStream()方法会直接创建一个并行流

注意:对并行流进行操作时需注意线程安全,例如ForEach方法进行i++操作时i

有序并不妨碍并行,例如计算stream.map(fun)时流可以被分为n个片段并发处理

当不考虑有序时,调用unordered方法标记可以不关心顺序。这样可以加快一些方法的速度,比如limit,会返回流中任意n个元素

Collectors.groupingByConcurrent默认使用一个并发map进行操作,但是无法保证与流中原有顺序一致。

2.13 函数式接口

个人理解,函数式接口是:确定一个函数的参数类型和返回类型的接口

例如Predicate<T>代表参数为T返回类型为Boolean的函数接口

一篇较为详细的总结:http://www.ne.jp/asahi/hishidama/home/tech/java/functionalinterface.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: