第 1 章 为什么要关心 Java 8
1.1 Java 怎么还在变
1.1.1 Java 在编程语言生态系统中的位置
1.1.2 流处理
流是一系列数据项,一次只生成一项
1.1.3 用行为参数化把代码传递给方法
1.1.4 并行与共享的可变数据
1.1.5 Java 需要演变
1.2 Java 中的函数
1.2.1 方法和 Lambda 作为一等公民
1.2.2 传递代码:一个例子
1.2.3 从传递方法到 Lambda
1.3 流
Collection主要是为了存储和访问数据,而Stream则主要用于描述对数据的计算。
顺序处理
import static java.util.stream.Collectors.toList; List<Apple> heavyApples = inventory.stream().filter((Apple a) -> a.getWeight() > 150) .collect(toList());
并行处理:
import static java.util.stream.Collectors.toList; List<Apple> heavyApples = inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150) .collect(toList());
1.4 默认方法
接口中的默认方法,实现类可以不用实现
default void sort(Comparator<? super E> c) { Collections.sort(this, c); }
1.5 来自函数式编程的其他好思想
1.6 小结
函数是一等值;记得方法如何作为函数式值来传递,还有Lambda是怎样写的。
Java 8中Streams的概念使得Collections的许多方面得以推广,让代码更为易读,并允许并行处理流元素。
你可以在接口中使用默认方法,在实现类没有实现方法时提供方法内容。
其他来自函数式编程的有趣思想,包括处理null和使用模式匹配
第 2 章 通过行为参数化传递代码
2.1 应对不断变化的需求
2.1.1 初试牛刀:筛选绿苹果
public static List<Apple> filterGreenApples(List<Apple> inventory) { List<Apple> result = new ArrayList<Apple>(); for(Apple apple: inventory){ if( "green".equals(apple.getColor() ) { result.add(apple); } } return result; }
2.1.2 再展身手:把颜色作为参数
public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple: inventory){ if ( apple.getColor().equals(color) ) { result.add(apple); } } return result; }
2.1.3 第三次尝试:对你能想到的每个属性做筛选
public static List<Apple> filterApples(List<Apple> inventory, String color, int weight, boolean flag) { List<Apple> result = new ArrayList<Apple>(); for (Apple apple: inventory){ if ( (flag && apple.getColor().equals(color)) || (!flag && apple.getWeight() > weight) ){ result.add(apple); } } return result; }
2.2 行为参数化
对选择标准建模:
public interface ApplePredicate{ boolean test (Apple apple); }
用ApplePredicate的多个实现代表不同的选择标准
public class AppleHeavyWeightPredicate implements ApplePredicate{ public boolean test(Apple apple){ return apple.getWeight() > 150; } } public class AppleGreenColorPredicate implements ApplePredicate{ public boolean test(Apple apple){ return "green".equals(apple.getColor()); } }
策略图
行为参数化:让方法接受多种行为(或战略)作为参数,并在内部使用,来完成不同的行为
第四次尝试:根据抽象条件筛选
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p){ List<Apple> result = new ArrayList<>(); for(Apple apple: inventory){ if(p.test(apple)){ result.add(apple); } } return result; }
找出所有重量超过150克的红苹果,
public class AppleRedAndHeavyPredicate implements ApplePredicate{ public boolean test(Apple apple){ return "red".equals(apple.getColor()) && apple.getWeight() > 150; } } List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());
参数化filterApples的行为,并传递不同的筛选策略
参数化filterApples的行为并传递不同的筛选策略
2.3 对付啰嗦
行为参数化:用谓词筛选苹果
public class AppleHeavyWeightPredicate implements ApplePredicate { public boolean test(Apple apple) { return apple.getWeight() > 150; } } public class AppleGreenColorPredicate implements ApplePredicate { public boolean test(Apple apple) { return "green".equals(apple.getColor()); } } public class FilteringApples { public static void main(String... args) { List<Apple> inventory = Arrays.asList(new Apple(80, "green"), new Apple(155, "green"), new Apple(120, "red")); List<Apple> heavyApples = filterApples(inventory, new AppleHeavyWeightPredicate()); List<Apple> greenApples = filterApples(inventory, new AppleGreenColorPredicate()); } public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) { List<Apple> result = new ArrayList<>(); for (Apple apple : inventory) { if (p.test(apple)) { result.add(apple); } } return result; } }
2.3.1 匿名类
2.3.2 第五次尝试:使用匿名类
List<Apple> redApples=filterApples(inventory,new ApplePredicate(){ public boolean test(Apple apple){ return"red".equals(apple.getColor()); } });
2.3.3 第六次尝试:使用 Lambda 表达式
List<Apple> result=filterApples(inventory,(Apple apple)->"red".equals(apple.getColor()));
行为参数化与值参数化
2.3.4 第七次尝试:将 List 类型抽象化
public interface Predicate<T> { boolean test(T t); } public static <T> List<T> filter(List<T> list, Predicate<T> p) { List<T> result = new ArrayList<>(); for (T e : list) { if (p.test(e)) { result.add(e); } } return result; }
用在香蕉、桔子、Integer或是String的列表上
List<Apple> redApples=filter(inventory,(Apple apple)->"red".equals(apple.getColor()));
List<Integer> evenNumbers=filter(numbers,(Integer i)->i%2==0);
2.4 真实的例子
2.4.1 用 Comparator 来排序 .............. 31
接口
public interface Comparator<T> { public int compare(T o1, T o2); }
按照重量升序对库存排序
inventory.sort(new Comparator<Apple>(){ public int compare(Apple a1,Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } });
用Lambda表达式
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
2.4.2 用 Runnable 执行代码块
原来
Thread t = new Thread(new Runnable() { public void run(){ System.out.println("Hello world"); } });
现在
Thread t = new Thread(() -> System.out.println("Hello world"));
2.4.3 GUI 事件处理
2.5 小结
行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力
Java API包含很多可以用不同行为进行参数化的方法,包括排序、线程和GUI处理
第 3 章 Lambda 表达式
3.1 Lambda 管中窥豹
匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!
函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
简洁——无需像匿名类那样写很多模板代码
有效的表达式
案例
3.2 在哪里以及如何使用 Lambda
3.2.1 函数式接口
函数式接口就是只定义一个抽象方法的接口
Lambda表达式是函数式接口一个具体实现的实例
3.2.2 函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符
3.3 把 Lambda 付诸实践:环绕执行模式
是打开一个资源,做一些处理,然后关闭资源
3.3.1 第 1 步:记得行为参数化
String result = processFile((BufferedReader br) ->br.readLine() + br.readLine());
3.3.2 第 2 步:使用函数式接口来传递行为
接口
@FunctionalInterface public interface BufferedReaderProcessor { String process(BufferedReader b) throws IOException; }
使用
public static String processFile(BufferedReaderProcessor p) throws IOException { … }
3.3.3 第 3 步:执行一个行为
public static String processFile(BufferedReaderProcessor p) throws IOException { try (BufferedReader br =new BufferedReader(new FileReader("data.txt"))) { return p.process(br); } }
3.3.4 第 4 步:传递 Lambda
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());
使pocessFile方法更灵活的四个步骤
3.4 使用函数式接口
3.4.1 Predicate
@FunctionalInterface public interface Predicate<T>{ boolean test(T t); } public static <T> List<T> filter(List<T> list, Predicate<T> p) { List<T> results = new ArrayList<>(); for(T s: list){ if(p.test(s)){ results.add(s); } } return results; } Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty(); List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
3.4.2 Consumer
定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口
@FunctionalInterface public interface Consumer<T>{ void accept(T t); } public static <T> void forEach(List<T> list, Consumer<T> c){ for(T i: list){ c.accept(i); } } forEach(Arrays.asList(1,2,3,4,5),(Integer i) -> System.out.println(i));
3.4.3 Function
定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象
@FunctionalInterface public interface Function<T, R>{ R apply(T t); } public static <T, R> List<R> map(List<T> list,Function<T, R> f) { List<R> result = new ArrayList<>(); for(T s: list){ result.add(f.apply(s)); } return result; } // [7, 2, 6] List<Integer> l = map(Arrays.asList("lambdas","in","action"),(String s) -> s.length());
装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值
常用函数式接口
示例
3.5 类型检查、类型推断以及限制
3.5.1 类型检查
上下文(比如,接受它传递的方法的参数,或接受它的值的局部变量)中Lambda表达式需要的类型称为目标类型
解读Lambda表达式的类型检查过程
3.5.2 同样的 Lambda,不同的函数式接口
同一个Lambda表达式就可以与不同的函数式接口联系起来,只要它们的抽象方法签名能够兼容
Callable<Integer> c = () -> 42;
PrivilegedAction<Integer> p = () -> 42;
3.5.3 类型推断
3.5.4 使用局部变量
Lambda捕获了portNumber变量
int portNumber = 1337; Runnable r = () -> System.out.println(portNumber);
错误的使用
3.6 方法引用
示例
3.6.1 管中窥豹
示例
三种不同类型的Lambda表达式构建方法引用的办法
3.6.2 构造函数引用
3.7 Lambda 和方法引用实战
3.7.1 第 1 步:传递代码
public class AppleComparator implements Comparator<Apple> { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } } inventory.sort(new AppleComparator());
3.7.2 第 2 步:使用匿名类
inventory.sort(new Comparator<Apple>() { public int compare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } });
3.7.3 第 3 步:使用 Lambda 表达式 .... 58
import static java.util.Comparator.comparing; inventory.sort(comparing((a) -> a.getWeight()));
3.7.4 第 4 步:使用方法引用 ............... 59
inventory.sort(comparing(Apple::getWeight));
3.8 复合 Lambda 表达式的有用方法
3.8.1 比较器复合
1. 逆序
inventory.sort(comparing(Apple::getWeight).reversed());
2. 比较器链
3.8.2 谓词复合
3.8.3 函数复合
用compose的话,它将意味着f(g(x)),而andThen则意味着g(f(x)):
andThen和compose之间的区别
3.9 数学中的类似思想
3.9.1 积分
3.9.2 与 Java 8 的 Lambda 联系起来
public double integrate(DoubleFunction<Double> f, double a, double b) { return (f.apply(a) + f.apply(b)) * (b-a) / 2.0; }
3.10 小结
第 4 章 引入流
4.1 流是什么
流是Java API的新成员,它允许你以声明性方式处理数据集合
将流操作链接起来构成流的流水线
4.2 流简介
元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值
源——流会使用一个提供数据的源,如集合、数组或输入/输出资源
数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作
流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线
内部迭代——与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的
使用流来筛选菜单,找出三个高热量菜肴的名字
4.3 流与集合
集合与流之间的差异就在于什么时候进行计算
流与集合
4.3.1 只能遍历一次
和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了
4.3.2 外部迭代与内部迭代
区别
内部迭代与外部迭代
4.4 流操作
4.4.1 中间操作
可以连接起来的流操作称为中间操作,关闭流的操作称为终端操作
4.4.2 终端操作
终端操作会从流的流水线生成结果。其结果是任何不是流的值,比如List、Integer,甚至void
4.4.3 使用流
4.5 小结
第 5 章 使用流 ........................................ 82
5.1 筛选和切片
5.1.1 用谓词筛选
5.1.2 筛选各异的元素
5.1.3 截短流
5.1.4 跳过元素
5.2 映射
5.2.1 对流中每一个元素应用函数
流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)
5.2.2 流的扁平化
1. 尝试使用map和Arrays.stream()
2. 使用flatMap
flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流
5.3 查找和匹配
5.3.1 检查谓词是否至少匹配一个元素
anyMatch方法返回一个boolean,因此是一个终端操作
if(menu.stream().anyMatch(Dish::isVegetarian)){ System.out.println("The menu is (somewhat) vegetarian friendly!!"); }
5.3.2 检查谓词是否匹配所有元素
allMatch流中的元素是否都能匹配
boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);
noneMatch流中没有任何元素与给定的谓词匹配
boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);
5.3.3 查找元素
findAny方法将返回当前流中的任意元素
5.3.4 查找第一个元素
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5); Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream() .map(x -> x * x) .filter(x -> x % 3 == 0) .findFirst(); // 9
5.4 归约
5.4.1 元素求和
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
静态的sum方法来对两个数求和
int sum = numbers.stream().reduce(0, Integer::sum);
5.4.2 最大值和最小值
中间操作和终端操作
5.5 付诸实践
5.5.1 领域:交易员和交易
(1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
(2) 交易员都在哪些不同的城市工作过?
(3) 查找所有来自于剑桥的交易员,并按姓名排序。
(4) 返回所有交易员的姓名字符串,按字母顺序排序。
(5) 有没有交易员是在米兰工作的?
(6) 打印生活在剑桥的交易员的所有交易额。
(7) 所有交易中,最高的交易额是多少?
(8) 找到交易额最小的交易
5.5.2 解答
5.6 数值流
reduce方法计算流中元素的总和,暗含的装箱成本
5.6.1 原始类型流特化
1. 映射到数值流
2. 转换回对象流
3. 默认值OptionalInt
5.6.2 数值范围
range是不包含结束值的,而rangeClosed则包含结束值
5.6.3 数值流应用:勾股数
1. 勾股数
2. 表示三元数
3. 筛选成立的组合
filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
4. 生成三元组
stream.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0) .map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
5. 生成b值
IntStream.rangeClosed(1, 100) .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0) .mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
6. 生成值
Stream<int[]> pythagoreanTriples = IntStream.rangeClosed(1, 100).boxed() .flatMap(a -> IntStream.rangeClosed(a, 100) .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0) .mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a * a + b * b)}) );
7. 运行代码
pythagoreanTriples.limit(5) .forEach(t -> System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
8. 你还能做得更好吗?
Stream<double[]> pythagoreanTriples2 = IntStream.rangeClosed(1, 100).boxed() .flatMap(a -> IntStream.rangeClosed(a, 100) .mapToObj( b -> new double[]{a, b, Math.sqrt(a*a + b*b)}) .filter(t -> t[2] % 1 == 0));
5.7 构建流
5.7.1 由值创建流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
5.7.2 由数组创建流
int[] numbers = {2, 3, 5, 7, 11, 13}; int sum = Arrays.stream(numbers).sum();
5.7.3 由文件生成流
5.7.4 由函数生成流:创建无限流
1. 迭代
Stream.iterate(0, n -> n + 2) .limit(10) .forEach(System.out::println);
2. 生成
在并行代码中使用有状态的供应源是不安全的,下面的代码仅仅是为了内容完整,应尽量避免使用
IntSupplier fib = new IntSupplier(){ private int previous = 0; private int current = 1; public int getAsInt(){ int oldPrevious = this.previous; int nextValue = this.previous + this.current; this.previous = this.current; this.current = nextValue; return oldPrevious; } }; IntStream.generate(fib).limit(10).forEach(System.out::println);
5.8 小结
第 6 章 用流收集数据
6.1 收集器简介
函数式编程
Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream().collect(groupingBy(Transaction::getCurrency));
6.1.1 收集器用作高级归约
按货币对交易分组的归约过程
6.1.2 预定义收集器
6.2 归约和汇总
6.2.1 查找流中的最大值和最小值
Optional<Dish> mostCalorieDish =
menu.stream()
.collect(maxBy(dishCaloriesComparator));
6.2.2 汇总
summingInt收集器的累积过程
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
summarizingInt工厂方法返回的收集器,通过一次summarizing操作你可以就数出菜单中元素的个数,并得到菜肴热量总和、平均值、最大值和最小值
IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
6.2.3 连接字符串
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));
6.2.4 广义的归约汇总
1. 收集框架的灵活性:以不同的方法执行同样的操作
计算菜单总热量的归约过程
int totalCalories = menu.stream().map(Dish::getCalories).reduce(Integer::sum).get(); }
2. 根据情况选择最佳解决方案
6.3 分组
在分组过程中对流中的项目进行分类
6.3.1 多级分组
Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = menu.stream().collect( groupingBy(Dish::getType, groupingBy(dish -> { if (dish.getCalories() <= 400) return CaloricLevel.DIET; else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; else return CaloricLevel.FAT; }) ) );
n层嵌套映射和n维分类表之间的等价关系
6.3.2 按子组收集数据
1. 把收集器的结果转换为另一种类型
2. 与groupingBy联合使用的其他收集器的例子
嵌套收集器来获得多重效果
Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType = menu.stream().collect( groupingBy(Dish::getType, mapping( dish -> { if (dish.getCalories() <= 400) return CaloricLevel.DIET; else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; else return CaloricLevel.FAT; }, toCollection(HashSet::new) )));
6.4 分区
6.4.1 分区的优势
6.4.2 将数字按质数和非质数分区
Collectors类的静态工厂方法
6.5 收集器接口
Collector接口
6.5.1 理解 Collector 接口声明的方法
1. 建立新的结果容器:supplier方法
public Supplier<List<T>> supplier() { return ArrayList::new; }
2. 将元素添加到结果容器:accumulator方法
public BiConsumer<List<T>, T> accumulator() { return List::add; }
3. 对结果容器应用最终转换:finisher方法
public Function<List<T>, List<T>> finisher() { return Function.identity(); }
顺序归约过程的逻辑步骤
4. 合并两个结果容器:combiner方法
public BinaryOperator<List<T>> combiner() { return (list1, list2) -> { list1.addAll(list2); return list1; } }
使用combiner方法来并行化归约过程
5. characteristics方法
UNORDERED——归约结果不受流中项目的遍历和累积顺序的影响。
CONCURRENT——accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约。
IDENTITY_FINISH——这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器A不加检查地转换为结果R是安全的。
6.5.2 全部融合到一起
6.6 开发你自己的收集器以获得更好的性能
6.6.1 仅用质数做除数
1. 第一步:定义Collector类的签名
2. 第二步:实现归约过程
3. 第三步:让收集器并行工作(如果可能)
public BinaryOperator<Map<Boolean, List<Integer>>> combiner() { return (Map<Boolean, List<Integer>> map1, Map<Boolean, List<Integer>> map2) -> { map1.get(true).addAll(map2.get(true)); map1.get(false).addAll(map2.get(false)); return map1; }; }
4. 第四步:finisher方法和收集器的characteristics方法
6.6.2 比较收集器的性能
6.7 小结
第 7 章 并行数据处理与性能
7.1 并行流
7.1.1 将顺序流转换为并行流
并行归纳操作
7.1.2 测量流性能
使用更有针对性的方法
public static long parallelRangedSum(long n) { return LongStream.rangeClosed(1, n) .parallel() .reduce(0L, Long::sum); }
7.1.3 正确使用并行流
forEach中调用的方法有副作用,它会改变多个线程共享的对象的可变状态
7.1.4 高效使用并行流
用适当的基准来检查其性能。
自动装箱和拆箱操作会大大降低性能。Java 8中有原始类型流(IntStream、LongStream、DoubleStream)来避免这种操作
有些操作本身在并行流上的性能就比顺序流差。特别是limit和findFirst等依赖于元素顺序的操作,它们在并行流上执行的代价非常大
要考虑流背后的数据结构是否易于分解。例如,ArrayList的拆分效率比LinkedList高得多,因为前者用不着遍历就可以平均拆分,而后者则必须遍历
流的数据源和可分解性
7.2 分支/合并框架
7.2.1 使用 RecursiveTask
分支/合并过程
用分支/合并框架执行并行求和
分支/合并算法
7.2.2 使用分支/合并框架的最佳做法
对一个任务调用join方法会阻塞调用方,直到该任务做出结果
不应该在RecursiveTask内部使用ForkJoinPool的invoke方法
对子任务调用fork方法可以把它排进ForkJoinPool
和并行流一样,你不应理所当然地认为在多核处理器上使用分支/合并框架就比顺序计算快
7.2.3 工作窃取
分支/合并框架工程用一种称为工作窃取(work stealing)的技术来解决这个问题
分支/合并框架使用的工作窃取算法
7.3 Spliterator
7.3.1 拆分过程
递归拆分过程
Spliterator的特性
7.3.2 实现你自己的 Spliterator
1. 以函数式风格重写单词计数器
Stream<Character> stream = IntStream.range(0, SENTENCE.length())
.mapToObj(SENTENCE::charAt);
用来在遍历Character流时计数的类
2. 让WordCounter并行工作
WordCounterSpliterator
3. 运用WordCounterSpliterator
Spliterator<Character> spliterator = new WordCounterSpliterator(SENTENCE); Stream<Character> stream = StreamSupport.stream(spliterator, true);
7.4 小结
第 8 章 重构、测试和调试
8.1 为改善可读性和灵活性重构代码
8.1.1 改善代码的可读性
8.1.2 从匿名类到 Lambda 表达式的转换
8.1.3 从 Lambda 表达式到方法引用的转换
8.1.4 从命令式的数据处理切换到Stream
8.1.5 增加代码的灵活性
1 采用函数接口
2. 有条件的延迟执行
你需要频繁地从客户端代码去查询一个对象的状态(比如前文例子中的日志器的状态),只是为了传递参数、调用该对象的一个方法(比如输出一条日志),那么可以考虑实现一个新的方法,以Lambda或者方法表达式作为参数
3. 环绕执行
拥有同样的准备和清理阶段
8.2 使用 Lambda 重构面向对象的设计模式
8.2.1 策略模式
图示
使用Lambda表达式
8.2.2 模板方法
8.2.3 观察者模式
观察者模式是一种比较常见的方案,某些事件发生时(比如状态转变),如果一个对象(通常我们称之为主题)需要自动地通知其他多个对象(称为观察者),就会采用该方案
f.registerObserver((String tweet) -> { if(tweet != null && tweet.contains("money")){ System.out.println("Breaking news in NY! " + tweet); } }); f.registerObserver((String tweet) -> { if(tweet != null && tweet.contains("queen")){ System.out.println("Yet another news in London... " + tweet); } });
8.2.4 责任链模式
责任链模式是一种创建处理对象序列(比如操作序列)的通用方案
8.2.5 工厂模式
使用工厂模式,你无需向客户暴露实例化的逻辑就能完成对象的创建
8.3 测试 Lambda 表达式
8.3.1 测试可见 Lambda 函数的行为
8.3.2 测试使用 Lambda 的方法的行为
8.3.3 将复杂的 Lambda 表达式分到不同的方法
8.3.4 高阶函数的测试
8.4 调试
8.4.1 查看栈跟踪
8.4.2 使用日志调试
8.5 小结
第 9 章 默认方法
9.1 不断演进的 AP
9.1.1 初始版本的 API
向接口添加方法
9.1.2 第二版 API
9.2 概述默认方法
9.3 默认方法的使用模式
9.3.1 可选方法
9.3.2 行为的多继承
1. 类型的多继承
2. 利用正交方法的精简接口
3. 组合接口
9.4 解决冲突的规则
9.4.1 解决问题的三条规则
类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法
9.4.2 选择提供了最具体实现的默认方法的接口
9.4.3 冲突及如何显式地消除歧义
9.4.4 菱形继承问题
9.5 小结
第 10 章 用 Optional 取代 null
10.1 如何为缺失的值建模
10.1.1 采用防御式检查减少 NullPointerException
null-安全的第一种尝试:深层质疑
public String getCarInsuranceName(Person person) { if (person != null) { Car car = person.getCar(); if (car != null) { Insurance insurance = car.getInsurance(); if (insurance != null) { return insurance.getName(); } } } return "Unknown"; }
null-安全的第二种尝试:过多的退出语句
public String getCarInsuranceName(Person person) { if (person == null) { return "Unknown"; } Car car = person.getCar(); if (car == null) { return "Unknown"; } Insurance insurance = car.getInsurance(); if (insurance == null) { return "Unknown"; } return insurance.getName(); }
10.1.2 null 带来的种种问题
10.1.3 其他语言中 null 的替代品
10.2 Optional 类入门
使用Optional定义的Car类
使用Optional重新定义Person/Car/Insurance的数据模型
10.3 应用 Optional 的几种模式
10.3.1 创建 Optional 对象
1. 声明一个空的Optional
Optional<Car> optCar = Optional.empty();
2. 依据一个非空值创建Optional
Optional<Car> optCar = Optional.of(car);
3. 可接受null的Optional
Optional<Car> optCar = Optional.ofNullable(car);
10.3.2 使用 map 从 Optional 对象中提取和转换值
Stream和Optional的map方法对比
10.3.3 使用 flatMap 链接Optional 对象
1. 使用Optional获取car的保险公司名称
public String getCarInsuranceName(Optional<Person> person) { return person.flatMap(Person::getCar) .flatMap(Car::getInsurance) .map(Insurance::getName) .orElse("Unknown"); }
2. 使用Optional解引用串接的Person/Car/Insurance对象
10.3.4 默认行为及解引用Optional 对象
get()是这些方法中最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException异常。
orElse(T other)是我们在代码清单10-5中使用的方法,正如之前提到的,它允许你在Optional对象不包含值时提供一个默认值。
orElseGet(Supplier<? extends T> other)是orElse方法的延迟调用版,Supplier方法只有在Optional对象不含值时才执行调用。
orElseThrow(Supplier<? extends X> exceptionSupplier)和get方法非常类似,它们遭遇Optional对象为空时都会抛出一个异常,但是使用orElseThrow你可以定制希望抛出的异常类型。
ifPresent(Consumer<? super T>)让你能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作
10.3.5 两个 Optional 对象的组合
10.3.6 使用 filter 剔除特定的值
Optional<Insurance> optInsurance = ...; optInsurance.filter(insurance -> "CambridgeInsurance".equals(insurance.getName())) .ifPresent(x -> System.out.println("ok"));
Optional类的方法
10.4 使用 Optional 的实战示例
10.4.1 用 Optional 封装可能为null 的值
Optional<Object> value = Optional.ofNullable(map.get("key"));
10.4.2 异常与 Optional 的对比
基础类型的Optional对象,以及为什么应该避免使用它们
10.4.3 把所有内容整合起来
以命令式编程的方式从属性中读取duration值
使用Optional从属性中读取duration
public int readDuration(Properties props, String name) { return Optional.ofNullable(props.getProperty(name)) .flatMap(OptionalUtility::stringToInt) .filter(i -> i > 0) .orElse(0); }
10.5 小结
第 11 章 CompletableFuture:组合式异步编程
11.1 Future 接口
典型的“混聚”式应用
并发和并行
使用Future以异步的方式执行一个耗时的操作
11.1.1 Future 接口的局限性
很难表述Future结果之间的依赖性
11.1.2 使用 CompletableFuture 构建异步应用
11.2 实现异步 API
11.2.1 将同步方法转换为异步方法
同步
public double getPrice(String product) { return calculatePrice(product); } private double calculatePrice(String product) { delay(); return random.nextDouble() * product.charAt(0) + product.charAt(1); }
getPriceAsync方法的实现
11.2.2 错误处理
抛出CompletableFuture内的异常
使用工厂方法supplyAsync创建CompletableFuture
public Future<Double> getPriceAsync(String product) { return CompletableFuture.supplyAsync(() -> calculatePrice(product)); }
11.3 让你的代码免受阻塞之苦
11.3.1 使用并行流对请求进行并行操作
public List<String> findPrices(String product) { return shops.parallelStream() .map(shop -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product))) .collect(toList()); }
11.3.2 使用 CompletableFuture 发起异步请求
为什么Stream的延迟特性会引起顺序执行,以及如何避免
11.3.3 寻找更好的方案
11.3.4 使用定制的执行器
如果你进行的是计算密集型的操作,并且没有I/O,那么推荐使用Stream接口
如果你并行的工作单元还涉及等待I/O的操作(包括网络连接等待),那么使用CompletableFuture灵活性更好
11.4 对多个异步任务进行流水线操作
11.4.1 实现折扣服务
11.4.2 使用 Discount 服务
11.4.3 构造同步和异步操作
构造同步操作和异步任务
11.4.4 将两个 CompletableFuture 对象整合起来,无论它们是否存在依赖
合并两个相互独立的异步任务
11.4.5 对 Future 和 CompletableFuture 的回顾 .
11.5 响应 CompletableFuture 的completion 事件
11.5.1 对最佳价格查询器应用的优化
重构findPrices方法返回一个由Future构成的流
public Stream<CompletableFuture<String>> findPricesStream(String product) { return shops.stream() .map(shop -> CompletableFuture.supplyAsync( () -> shop.getPrice(product), executor)) .map(future -> future.thenApply(Quote::parse)) .map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync( () -> Discount.applyDiscount(quote), executor))); }
响应CompletableFuture的completion事件
CompletableFuture[] futures = findPricesStream("myPhone") .map(f -> f.thenAccept(System.out::println)) .toArray(size -> new CompletableFuture[size]); CompletableFuture.allOf(futures).join();
11.5.2 付诸实践
11.6 小结
第 12 章 新的日期和时间 API
12.1 LocalDate、LocalTime、Instant、Duration 以及 Period
12.1.1 使用 LocalDate 和LocalTime
创建一个LocalDate对象并读取其值
创建LocalTime并读取其值
12.1.2 合并日期和时间
直接创建LocalDateTime对象,或者通过合并日期和时间的方式创建
// 2014-03-18T13:45:20 LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20); LocalDateTime dt2 = LocalDateTime.of(date, time); LocalDateTime dt3 = date.atTime(13, 45, 20); LocalDateTime dt4 = date.atTime(time); LocalDateTime dt5 = time.atDate(date);
12.1.3 机器的日期和时间格式
12.1.4 定义 Duration 或Period
创建Duration和Period对象
Duration threeMinutes = Duration.ofMinutes(3); Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES); Period tenDays = Period.ofDays(10); Period threeWeeks = Period.ofWeeks(3); Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
12.2 操纵、解析和格式化日期
12.2.1 使用 TemporalAdjuster
以比较直观的方式操纵LocalDate的属性
以相对方式修改LocalDate对象的属性
表示时间点的日期时间类的通用方法
使用预定义的TemporalAdjuster
TemporalAdjuster类中的工厂方法
NextWorkingDay类
12.2.2 打印输出及解析日期时间对象
按照某个模式创建DateTimeFormatter
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); LocalDate date1 = LocalDate.of(2014, 3, 18); String formattedDate = date1.format(formatter); LocalDate date2 = LocalDate.parse(formattedDate, formatter);
创建一个本地化的DateTimeFormatter
DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN); LocalDate date1 = LocalDate.of(2014, 3, 18); String formattedDate = date.format(italianFormatter); // 18. marzo 2014 LocalDate date2 = LocalDate.parse(formattedDate, italianFormatter);
12.3 处理不同的时区和历法
12.3.1 利用和 UTC/格林尼治时间的固定偏差计算时区
12.3.2 使用别的日历系统
第 13 章 函数式的思考
13.1 实现和维护系统
13.1.1 共享的可变数据
13.1.2 声明式编程
13.1.3 为什么要采用函数式编程
13.2 什么是函数式编程
13.2.1 函数式 Java 编程
13.2.2 引用透明性
13.2.3 面向对象的编程和函数式编程的对比
13.2.4 函数式编程实战
13.3 递归和迭代
基于“尾递”的阶乘
static long factorialTailRecursive(long n) { return factorialHelper(1, n); } static long factorialHelper(long acc, long n) { return n == 1 ? acc : factorialHelper(acc * n, n-1); }
第 14 章 函数式编程的技巧
14.1 无处不在的函数
14.1.1 高阶函数
14.1.2 科里化
科里化是一种将具备2个参数(比如,x和y)的函数f转化为使用一个参数的函数g,并且这个函数的返回值也是一个函数,它会作为新函数的一个参数。后者的返回值和初始函数的返回值相同,即f(x,y) = (g(x))(y)
14.2 持久化数据结构
14.2.1 破坏式更新和函数式更新的比较
14.2.2 另一个使用 Tree 的例子
14.2.3 采用函数式的方法
14.3 Stream 的延迟计算
14.3.1 自定义的 Stream
14.3.2 创建你自己的延迟列表
14.4 模式匹配
14.4.1 访问者设计模式
14.4.2 用模式匹配力挽狂澜
14.5 杂项
14.5.1 缓存或记忆表
14.5.2 “返回同样的对象”意味着什么
14.5.3 结合器
第 15 章 面向对象和函数式编程的混合:Java 8 和 Scala 的比较
15.1 Scala 简介
15.1.1 你好,啤酒
15.1.2 基础数据结构:List、Set、Map、Tuple、Stream 以及 Option
15.2 函数
15.2.1 Scala 中的一等函数
15.2.2 匿名函数和闭包
15.2.3 科里化
15.3 类和 trait
15.3.1 更加简洁的 Scala 类
15.3.2 Scala 的 trait 与 Java 8 的接口对比
第 16 章 结论以及 Java 的未来
16.1 回顾 Java 8 的语言特性
16.1.1 行为参数化(Lambda 以及方法引用
16.1.2 流
16.1.3 CompletableFuture
16.1.4 Optional
16.1.5 默认方法
16.2 Java 的未来
16.2.1 集合
16.2.2 类型系统的改进
16.2.3 模式匹配
16.2.4 更加丰富的泛型形式
16.2.5 对不变性的更深层支持
16.2.6 值类型
附录 A 其他语言特性的更新
A.1 注解
A.1.1 重复注解
A.1.2 类型注解
A.2 通用目标类型推断
附录 B 类库的更新
B.1 集合
B.1.1 其他新增的方法
B.1.2 Collections 类
B.1.3 Comparator
B.2 并发
B.2.1 原子操作
Adder和Accumulator
B.2.2 ConcurrentHashMap
B.3 Arrays
B.3.1 使用 parallelSort
parallelSort方法会以并发的方式对指定的数组进行排序,你可以使用自然顺序,也可以为数组对象定义特别的Comparator
B.3.2 使用 setAll 和 parallelSetAll
B.3.3 使用 parallelPrefix
B.4 Number 和 Math
B.4.1 Number
B.4.2 Math
B.5 Files
B.6 Reflection
B.7 String
附录 C 如何以并发方式在同一个流上执行多种操作
C.1 复制流
StreamForker详解
C.1.1 使用 ForkingStreamConsumer 实现 Results 接口
C.1.2 开发 ForkingStreamConsumer 和 BlockingQueueSpliterator
C.1.3 将 StreamForker 运用于实战
Stream<Dish> menuStream = menu.stream(); StreamForker.Results results = new StreamForker<Dish>(menuStream) .fork("shortMenu", s -> s.map(Dish::getName) .collect(joining(", "))) .fork("totalCalories", s -> s.mapToInt(Dish::getCalories).sum()) .fork("mostCaloricDish", s -> s.collect(reducing( (d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)) .get()) .fork("dishesByType", s -> s.collect(groupingBy(Dish::getType))) .getResults(); String shortMenu = results.get("shortMenu"); int totalCalories = results.get("totalCalories"); Dish mostCaloricDish = results.get("mostCaloricDish"); Map<Dish.Type, List<Dish>> dishesByType = results.get("dishesByType"); System.out.println("Short menu: "+shortMenu); System.out.println("Total calories: "+totalCalories); System.out.println("Most caloric dish: "+mostCaloricDish); System.out.println("Dishes by type: "+dishesByType);
C.2 性能的考量
附录 D Lambda 表达式和 JVM 字节码
D.1 匿名类
编译器会为每个匿名类生成一个新的.class文件
D.2 生成字节码
D.3 用 InvokeDynamic 力挽狂澜
D.4 代码生成策略