Java 8 函数式编程(2)

本篇对应《Java 8 函数式编程》的第三章。

PS:如果你还没有了解过 Iterator 设计模式,请先去了解一下 Iterator 设计模式


Stream

Stream 是用函数式编程方式在集合类上进行复杂操作的工具。

int sum = widgets.stream()
                 .filter(w -> w.getColor() == RED)
                 .mapToInt(w -> w.getWeight())
                 .sum();

比如这个官方文档上的代码示例:计算所有红色 Widget 的权重的总和。

  1. 使用 Collection.stream() 方法,创建 widgets 集合的流。
  2. 使用 filter 操作,产生一个只包含红色 widgets 的流。
  3. 使用 mapToInt 操作,转换成红色 widgets 的权重的流。
  4. 使用 sum 操作,计算红色 widgets 的权重之和。

Stream operations and pipelines

流操作(Stream operations)分为中间操作(intermediate operations)和终点操作(terminal operations),合在一起就形成了流管道(stream pipelines)。

一个流管道(stream pipelines)包含一个数据源(source),零到多个中间操作(intermediate operations)和一个终点操作(terminal operations)。

数据源(source)可以是一个数组,一个集合,一个生成函数,一个 I/O 通道,~。

中间操作(intermediate operations)就是把一个流转换成另外一个流,比如 filter 和 map。

终点操作(terminal operations)会产生一个结果或者副作用,比如 count 和 forEach。

流是惰性求值的,只有终点操作(terminal operations)开始的时候,所有数据源(source)上的计算才会真正被执行,并且数据源元素只在需要时才会被使用。

常用的流操作

filterintermediate operationstateless
map
mapToInt
mapToLong
mapToDouble
flatMap
flatMapToInt
flatMapToLong
flatMapToDouble
distinctstateful
sorted
peek
limit
skip
takeWhile
dropWhile
forEachterminal operation
forEachOrdered
toArray
reduce
collect
max
count
anyMatch
allMatch
noneMatch
findFirst
findAny

练习

Question 1:

    public static int addUp(Stream<Integer> numbers) {
        return numbers.reduce(0, (acc, x) -> acc + x);
    }

    public static List<String> getNamesAndOrigins(List<Artist> artists) {
        return artists.stream()
                      .flatMap(artist -> Stream.of(artist.getName(), artist.getNationality()))
                      .collect(toList());
    }

    public static List<Album> getAlbumsWithAtMostThreeTracks(List<Album> input) {
        return input.stream()
                    .filter(album -> album.getTrackList().size() <= 3)
                    .collect(toList());
    }

Question 2:

int totalMembers = (int) artists.stream().flatMap(artist -> artist.getMembers()).count();

Question 3:

a. 及早求值(Eager)- 返回 Stream
b. 惰性求值(Lazy)

Question 4:

a. 是 - 参数是函数
b. 否

Question 5:

a. 没有副作用

Question 6:

    public static int countLowercaseLetters(String string) {
        return (int) string.chars()
                           .filter(Character::isLowerCase)
                           .count();
    }

Question 7:

    public static Optional<String> mostLowercaseString(List<String> strings) {
        return strings.stream()
                      .max(Comparator.comparing(StringExercises::countLowercaseLetters));
    }

进阶练习

Question 1:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;

/**
 * Advanced Exercises Question 1
 */
public class MapUsingReduce {

    public static <I, O> List<O> map(Stream<I> stream, Function<I, O> mapper) {
        return stream.reduce(new ArrayList<O>(), (acc, x) -> {
        	// We are copying data from acc to new list instance. It is very inefficient,
        	// but contract of Stream.reduce method requires that accumulator function does
        	// not mutate its arguments.
        	// Stream.collect method could be used to implement more efficient mutable reduction,
        	// but this exercise asks to use reduce method.
        	List<O> newAcc = new ArrayList<>(acc);
        	newAcc.add(mapper.apply(x));
            return newAcc;
        }, (List<O> left, List<O> right) -> {
        	// We are copying left to new list to avoid mutating it. 
        	List<O> newLeft = new ArrayList<>(left);
        	newLeft.addAll(right);
            return newLeft;
        });
    }

}

Question 2:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * Advanced Exercises Question 2
 */
public class FilterUsingReduce {

    public static <I> List<I> filter(Stream<I> stream, Predicate<I> predicate) {
        List<I> initial = new ArrayList<>();
        return stream.reduce(initial,
                             (List<I> acc, I x) -> {
                                if (predicate.test(x)) {
                                	// We are copying data from acc to new list instance. It is very inefficient,
                                	// but contract of Stream.reduce method requires that accumulator function does
                                	// not mutate its arguments.
                                	// Stream.collect method could be used to implement more efficient mutable reduction,
                                	// but this exercise asks to use reduce method explicitly.
                                	List<I> newAcc = new ArrayList<>(acc);
                                    newAcc.add(x);
                                    return newAcc;
                                } else {
                                	return acc;
                                }
                             },
                             FilterUsingReduce::combineLists);
    }

    private static <I> List<I> combineLists(List<I> left, List<I> right) {
    	// We are copying left to new list to avoid mutating it. 
    	List<I> newLeft = new ArrayList<>(left);
    	newLeft.addAll(right);
        return newLeft;
    }

}

参考:

https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/

https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html

159 total views, 3 views today