Java 8实战之读书笔记四:高效Java 8编程
2017-06-11 00:00
441 查看
摘要: 本书第三部分将探究如何结合现代程序设计方法利用Java 8的各种特性更有效地改善代码质量。第8章会介绍如何利用Java 8的新特性及一些技巧,改进现有代码。此外,还会探讨一些软件开发技术,如设计模式、重构、测试以及调试。第9章中,你会了解默认方法,如何以兼容的方式使用默认方法改进API,一些实用的使用模式,以及有效地利用默认方法的规则。第10章围绕新引入的Optional类展开。Optional类能帮助我们设计出更优秀的API,降低了空指针异常发生的几率。第11章着重介绍CompletableFuture类。复杂的异步计算。第12章探讨了新的Date和Time接口。
使用Java 8,你可以减少冗长的代码,让代码更易于理解
通过方法引用和Stream API,你的代码会变得更直观
介绍三种简单的重构,利用Lambda表达式、方法引用以及Stream改善程序代码的可读性:
重构代码,用Lambda表达式取代匿名类
用方法引用重构Lambda表达式
用Stream API重构命令式的数据处理
增加代码的灵活性:
1. 采用函数接口
2. 有条件的延迟执行
3. 环绕执行
下面回顾一下这一章的主要内容。
Lambda表达式能提升代码的可读性和灵活性。
如果你的代码中使用了匿名类,尽量用Lambda表达式替换它们,但是要注意二者间语义的微妙差别,比如关键字this,以及变量隐藏。
跟Lambda表达式比起来,方法引用的可读性更好 。
尽量使用Stream API替换迭代式的集合处理。
Lambda表达式有助于避免使用面向对象设计模式时容易出现的僵化的模板代码,典型的比如策略模式、模板方法、观察者模式、责任链模式,以及工厂模式。
即使采用了Lambda表达式,也同样可以进行单元测试,但是通常你应该关注使用了Lambda表达式的方法的行为。
尽量将复杂的Lambda表达式抽象到普通方法中。
Lambda表达式会让栈跟踪的分析变得更为复杂。
流提供的peek方法在分析Stream流水线时,能将中间变量的值输出到日志中,是非常有用的工具。
Java 8中的接口可以通过默认方法和静态方法提供方法的代码实现。
默认方法的开头以关键字default修饰,方法体与常规的类方法相同。
向发布的接口添加抽象方法不是源码兼容的。
默认方法的出现能帮助库的设计者以后向兼容的方式演进API。
默认方法可以用于创建可选方法和行为的多继承。
我们有办法解决由于一个类从多个接口中继承了拥有相同函数签名的方法而导致的冲突。
类或者父类中声明的方法的优先级高于任何默认方法。如果前一条无法解决冲突,那就选择同函数签名的方法中实现得最具体的那个接口的方法。
两个默认方法都同样具体时,你需要在类中覆盖该方法,显式地选择使用哪个接口中提供的默认方法。
![](https://static.oschina.net/uploads/space/2017/0611/212406_fvWU_3290751.png)
![](https://static.oschina.net/uploads/space/2017/0611/212426_EwzI_3290751.png)
这一章中,你学到了以下的内容。
null引用在历史上被引入到程序设计语言中,目的是为了表示变量值的缺失。
Java 8中引入了一个新的类java.util.Optional<T>,对存在或缺失的变量值进行建模。
你可以使用静态工厂方法Optional.empty、 Optional.of以及Optional.ofNullable创建Optional对象。
Optional类支持多种方法,比如map、 flatMap、 filter,它们在概念上与Stream类中对应的方法十分相似。
使用Optional会迫使你更积极地解引用Optional对象,以应对变量值缺失的问题,最终,你能更有效地防止代码中出现不期而至的空指针异常。
使用Optional能帮助你设计更好的API,用户只需要阅读方法签名,就能了解该方法是否接受一个Optional类型的值。
错误处理:
使用 CompletableFuture 发起异步请求:
使用定制的执行器
CompletableFuture总结的有点潦草,如实战的话,还需在深入研究一下。
这一章中,你学到的内容如下。
执行比较耗时的操作时,尤其是那些依赖一个或多个远程服务的操作,使用异步任务可以改善程序的性能,加快程序的响应速度。
你应该尽可能地为客户提供异步API。使用CompletableFuture类提供的特性,你能够轻松地实现这一目标。
CompletableFuture类还提供了异常管理的机制,让你有机会抛出/管理异步任务执行中发生的异常。
将同步API的调用封装到一个CompletableFuture中,你能够以异步的方式使用其结果。
如果异步任务之间相互独立,或者它们之间某一些的结果是另一些的输入,你可以将这些异步任务构造或者合并成一个。
你可以为CompletableFuture注册一个回调函数,在Future执行完毕或者它们计算的结果可用时,针对性地执行一些程序。
你可以决定在什么时候结束程序的运行,是等待由CompletableFuture对象构成的列表中所有的对象都执行完毕,还是只要其中任何一个首先完成就中止程序的运行。
LocalDate、 LocalTime示例:
Instant示例:
通过向静态工厂方法ofEpochSecond传递一个代表秒数的值创建一个该类的实例。
![](https://static.oschina.net/uploads/space/2017/0611/220003_m1Gc_3290751.png)
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
它会抛出下面这样的异常:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field:DayOfMonth
但是你可以通过Duration和Period类使用Instant, 接下来我们会对这部分内容进行介绍。
定义 Duration 或 Period
![](https://static.oschina.net/uploads/space/2017/0611/220232_3e5Z_3290751.png)
![](https://static.oschina.net/uploads/space/2017/0611/220246_n6xU_3290751.png)
操纵、解析和格式化日期
![](https://static.oschina.net/uploads/space/2017/0611/220434_4cBQ_3290751.png)
![](https://static.oschina.net/uploads/space/2017/0611/220527_N4ay_3290751.png)
处理不同的时区和历法:
![](https://static.oschina.net/uploads/space/2017/0611/220852_vxdw_3290751.png)
这一章中,你应该掌握下面这些内容。
Java 8之前老版的java.util.Date类以及其他用于建模日期时间的类有很多不一致及设计上的缺陷,包括易变性以及糟糕的偏移值、默认值和命名。
新版的日期和时间API中,日期时间对象是不可变的。
新的API提供了两种不同的时间表示方式,有效地区分了运行时人和机器的不同需求。
你可以用绝对或者相对的方式操纵日期和时间,操作的结果总是返回一个新的实例,老的日期时间对象不会发生变化。
TemporalAdjuster让你能够用更精细的方式操纵日期,不再局限于一次只能改变它的一个值,并且你还可按照需求定义自己的日期转换器。
你现在可以按照特定的格式需求,定义自己的格式器,打印输出或者解析日期时间对象。这些格式器可以通过模板创建,也可以自己编程创建,并且它们都是线程安全的。
你可以用相对于某个地区/位置的方式,或者以与UTC/格林尼治时间的绝对偏差的方式表示时区,并将其应用到日期时间对象上,对其进行本地化。
你现在可以使用不同于ISO-8601标准系统的其他日历系统了。
三、高效Java 8编程
第8章 重构、测试和调试
Java 8的新特性也可以帮助提升代码的可读性: 使用Java 8,你可以减少冗长的代码,让代码更易于理解
通过方法引用和Stream API,你的代码会变得更直观
介绍三种简单的重构,利用Lambda表达式、方法引用以及Stream改善程序代码的可读性:
重构代码,用Lambda表达式取代匿名类
用方法引用重构Lambda表达式
用Stream API重构命令式的数据处理
增加代码的灵活性:
1. 采用函数接口
2. 有条件的延迟执行
3. 环绕执行
下面回顾一下这一章的主要内容。
Lambda表达式能提升代码的可读性和灵活性。
如果你的代码中使用了匿名类,尽量用Lambda表达式替换它们,但是要注意二者间语义的微妙差别,比如关键字this,以及变量隐藏。
跟Lambda表达式比起来,方法引用的可读性更好 。
尽量使用Stream API替换迭代式的集合处理。
Lambda表达式有助于避免使用面向对象设计模式时容易出现的僵化的模板代码,典型的比如策略模式、模板方法、观察者模式、责任链模式,以及工厂模式。
即使采用了Lambda表达式,也同样可以进行单元测试,但是通常你应该关注使用了Lambda表达式的方法的行为。
尽量将复杂的Lambda表达式抽象到普通方法中。
Lambda表达式会让栈跟踪的分析变得更为复杂。
流提供的peek方法在分析Stream流水线时,能将中间变量的值输出到日志中,是非常有用的工具。
第9章 默认方法
下面是本章你应该掌握的关键概念。 Java 8中的接口可以通过默认方法和静态方法提供方法的代码实现。
默认方法的开头以关键字default修饰,方法体与常规的类方法相同。
向发布的接口添加抽象方法不是源码兼容的。
默认方法的出现能帮助库的设计者以后向兼容的方式演进API。
默认方法可以用于创建可选方法和行为的多继承。
我们有办法解决由于一个类从多个接口中继承了拥有相同函数签名的方法而导致的冲突。
类或者父类中声明的方法的优先级高于任何默认方法。如果前一条无法解决冲突,那就选择同函数签名的方法中实现得最具体的那个接口的方法。
两个默认方法都同样具体时,你需要在类中覆盖该方法,显式地选择使用哪个接口中提供的默认方法。
第10章 用Optional取代null
![](https://static.oschina.net/uploads/space/2017/0611/212406_fvWU_3290751.png)
![](https://static.oschina.net/uploads/space/2017/0611/212426_EwzI_3290751.png)
这一章中,你学到了以下的内容。
null引用在历史上被引入到程序设计语言中,目的是为了表示变量值的缺失。
Java 8中引入了一个新的类java.util.Optional<T>,对存在或缺失的变量值进行建模。
你可以使用静态工厂方法Optional.empty、 Optional.of以及Optional.ofNullable创建Optional对象。
Optional类支持多种方法,比如map、 flatMap、 filter,它们在概念上与Stream类中对应的方法十分相似。
使用Optional会迫使你更积极地解引用Optional对象,以应对变量值缺失的问题,最终,你能更有效地防止代码中出现不期而至的空指针异常。
使用Optional能帮助你设计更好的API,用户只需要阅读方法签名,就能了解该方法是否接受一个Optional类型的值。
第11章 CompletableFuture:组合式 异步编程
示例:public class Shop { public double getPrice(String product) { // 待实现 } } // 模拟1秒钟延迟的方法 public static void delay() { try { Thread.sleep(1000L); } catch (InterruptedException e) { throw new RuntimeException(e); } } // 在getPrice方法中引入一个模拟的延迟 public double getPrice(String product) { return calculatePrice(product); } private double calculatePrice(String product) { delay(); return random.nextDouble() * product.charAt(0) + product.charAt(1); } // 将同步方法转换为异步方法及实现 public Future<Double> getPriceAsync(String product) { ... } public Future<Double> getPriceAsync(String product) { CompletableFuture<Double> futurePrice = new CompletableFuture<>(); new Thread( () -> { double price = calculatePrice(product); futurePrice.complete(price); }).start(); return futurePrice; } // 使用异步API Shop shop = new Shop("BestShop"); long start = System.nanoTime(); Future<Double> futurePrice = shop.getPriceAsync("my favorite product"); long invocationTime = ((System.nanoTime() - start) / 1_000_000); System.out.println("Invocation returned after " + invocationTime + " msecs"); // 执行更多任务,比如查询其他商店 doSomethingElse(); // 在计算商品价格的同时 try { double price = futurePrice.get(); System.out.printf("Price is %.2f%n", price); } catch (Exception e) { throw new RuntimeException(e); } long retrievalTime = ((System.nanoTime() - start) / 1_000_000); System.out.println("Price returned after " + retrievalTime + " msecs");
错误处理:
public Future<Double> getPriceAsync(String product) { CompletableFuture<Double> futurePrice = new CompletableFuture<>(); new Thread( () -> { try { double price = calculatePrice(product); // 如果价格计算正常结束,完成Future操作并设置商品价格 futurePrice.complete(price); } catch (Exception ex) { // 否则就抛出导致失败的异常,完成这次Future操作 futurePrice.completeExceptionally(ex); } }).start(); return futurePrice; } // 使用工厂方法supplyAsync创建CompletableFuture public Future<Double> getPriceAsync(String product) { return CompletableFuture.supplyAsync(() -> calculatePrice(product)); }
使用 CompletableFuture 发起异步请求:
List<CompletableFuture<String>> priceFutures = shops.stream() .map(shop -> CompletableFuture.supplyAsync( () -> String.format("%s price is %.2f",shop.getName(), shop.getPrice(product)))) .collect(toList());
使用定制的执行器
private final Executor executor = Executors.newFixedThreadPool( Math.min(shops.size(), 100), new ThreadFactory() { public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); return t; } }); CompletableFuture.supplyAsync(() -> shop.getName() + " price is " + shop.getPrice(product), executor);
CompletableFuture总结的有点潦草,如实战的话,还需在深入研究一下。
这一章中,你学到的内容如下。
执行比较耗时的操作时,尤其是那些依赖一个或多个远程服务的操作,使用异步任务可以改善程序的性能,加快程序的响应速度。
你应该尽可能地为客户提供异步API。使用CompletableFuture类提供的特性,你能够轻松地实现这一目标。
CompletableFuture类还提供了异常管理的机制,让你有机会抛出/管理异步任务执行中发生的异常。
将同步API的调用封装到一个CompletableFuture中,你能够以异步的方式使用其结果。
如果异步任务之间相互独立,或者它们之间某一些的结果是另一些的输入,你可以将这些异步任务构造或者合并成一个。
你可以为CompletableFuture注册一个回调函数,在Future执行完毕或者它们计算的结果可用时,针对性地执行一些程序。
你可以决定在什么时候结束程序的运行,是等待由CompletableFuture对象构成的列表中所有的对象都执行完毕,还是只要其中任何一个首先完成就中止程序的运行。
第12章 新的日期和时间API
LocalDate、 LocalTime、 Instant、 Duration 以及 Period的使用LocalDate、 LocalTime示例:
// 创建一个LocalDate对象并读取其值 LocalDate date = LocalDate.of(2014, 3, 18); int year = date.getYear(); Month month = date.getMonth(); int day = date.getDayOfMonth(); DayOfWeek dow = date.getDayOfWeek(); int len = date.lengthOfMonth(); boolean leap = date.isLeapYear(); LocalDate today = LocalDate.now(); // 使用TemporalField读取LocalDate的值 int year = date.get(ChronoField.YEAR); int month = date.get(ChronoField.MONTH_OF_YEAR); int day = date.get(ChronoField.DAY_OF_MONTH); // 创建LocalTime并读取其值 LocalTime time = LocalTime.of(13, 45, 20); int hour = time.getHour(); int minute = time.getMinute(); int second = time.getSecond(); LocalDate date = LocalDate.parse("2014-03-18"); LocalTime time = LocalTime.parse("13:45:20"); // 直接创建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); LocalDate date1 = dt1.toLocalDate(); LocalTime time1 = dt1.toLocalTime();
Instant示例:
通过向静态工厂方法ofEpochSecond传递一个代表秒数的值创建一个该类的实例。
![](https://static.oschina.net/uploads/space/2017/0611/220003_m1Gc_3290751.png)
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
它会抛出下面这样的异常:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field:DayOfMonth
但是你可以通过Duration和Period类使用Instant, 接下来我们会对这部分内容进行介绍。
定义 Duration 或 Period
Duration d1 = Duration.between(time1, time2); Duration d1 = Duration.between(dateTime1, dateTime2); Duration d2 = Duration.between(instant1, instant2); Period tenDays = Period.between(LocalDate.of(2014, 3, 8), LocalDate.of(2014, 3, 18)); // 创建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);
![](https://static.oschina.net/uploads/space/2017/0611/220232_3e5Z_3290751.png)
![](https://static.oschina.net/uploads/space/2017/0611/220246_n6xU_3290751.png)
操纵、解析和格式化日期
![](https://static.oschina.net/uploads/space/2017/0611/220434_4cBQ_3290751.png)
![](https://static.oschina.net/uploads/space/2017/0611/220527_N4ay_3290751.png)
处理不同的时区和历法:
ZoneId romeZone = ZoneId.of("Europe/Rome"); ZoneId zoneId = TimeZone.getDefault().toZoneId(); // 为时间点添加时区信息 LocalDate date = LocalDate.of(2014, Month.MARCH, 18); ZonedDateTime zdt1 = date.atStartOfDay(romeZone); LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); ZonedDateTime zdt2 = dateTime.atZone(romeZone); Instant instant = Instant.now(); ZonedDateTime zdt3 = instant.atZone(romeZone);
![](https://static.oschina.net/uploads/space/2017/0611/220852_vxdw_3290751.png)
// 通过ZoneId,你还可以将LocalDateTime转换为Instant: LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); Instant instantFromDateTime = dateTime.toInstant(romeZone); // 你也可以通过反向的方式得到LocalDateTime对象: Instant instant = Instant.now(); LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone); /** * 利用和 UTC/格林尼治时间的固定偏差计算时区 */ ZoneOffset newYorkOffset = ZoneOffset.of("-05:00"); LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45); OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(date, newYorkOffset); /** * 使用别的日历系统 */ LocalDate date = LocalDate.of(2014, Month.MARCH, 18); JapaneseDate japaneseDate = JapaneseDate.from(date); Chronology japaneseChronology = Chronology.ofLocale(Locale.JAPAN); ChronoLocalDate now = japaneseChronology.dateNow(); // 伊斯兰教日历 HijrahDate ramadanDate = HijrahDate.now().with(ChronoField.DAY_OF_MONTH, 1) .with(ChronoField.MONTH_OF_YEAR, 9); System.out.println("Ramadan starts on " + IsoChronology.INSTANCE.date(ramadanDate) + " and ends on " + IsoChronology.INSTANCE .date(ramadanDate.with(TemporalAdjusters.lastDayOfMonth())));
这一章中,你应该掌握下面这些内容。
Java 8之前老版的java.util.Date类以及其他用于建模日期时间的类有很多不一致及设计上的缺陷,包括易变性以及糟糕的偏移值、默认值和命名。
新版的日期和时间API中,日期时间对象是不可变的。
新的API提供了两种不同的时间表示方式,有效地区分了运行时人和机器的不同需求。
你可以用绝对或者相对的方式操纵日期和时间,操作的结果总是返回一个新的实例,老的日期时间对象不会发生变化。
TemporalAdjuster让你能够用更精细的方式操纵日期,不再局限于一次只能改变它的一个值,并且你还可按照需求定义自己的日期转换器。
你现在可以按照特定的格式需求,定义自己的格式器,打印输出或者解析日期时间对象。这些格式器可以通过模板创建,也可以自己编程创建,并且它们都是线程安全的。
你可以用相对于某个地区/位置的方式,或者以与UTC/格林尼治时间的绝对偏差的方式表示时区,并将其应用到日期时间对象上,对其进行本地化。
你现在可以使用不同于ISO-8601标准系统的其他日历系统了。
相关文章推荐
- 设计高效的线程安全的缓存(java并发编程实战5.6)
- 【JAVA并发编程实战】5、构建高效且可伸缩的结果缓存
- Java多线程编程实战指南(核心篇)读书笔记(五)
- 实战体会Java多线程编程精要
- 实战体会Java多线程编程精要
- 编写高效的JAVA程序-编程规范 (中级篇 )
- 编写高效的JAVA程序-编程规范 (开发篇 )
- 简单直观-实战体会Java多线程编程的精要
- java编程思想-读书笔记(6-10章)
- [转]实战体会Java多线程编程精要
- 简单直观-实战体会Java多线程编程的精要 (5)
- java编程思想4-读书笔记-第11章
- java 编程思想 读书笔记
- C#高效编程 读书笔记(1)
- 实战体会Java多线程编程精要
- 编写高效的JAVA程序-编程规范 (基本篇 )
- 简单直观-实战体会Java多线程编程的精要 (4)
- 实战Java多线程编程之不提倡的方法