《Java 8实战》读书笔记

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6



技术变革除了带来编码方便的改变,更重要的是思想的转变


文章目录

  • ​​一、书籍大纲​​
  • ​​二、内容总结​​
  • ​​1、Lambda​​
  • ​​行为参数化​​
  • ​​内部类​​
  • ​​优化设计模式​​
  • ​​2、Stream流​​
  • ​​流的概念​​
  • ​​简单API操作​​
  • ​​其他​​
  • ​​3、接口默认方法​​
  • ​​4、Optional​​
  • ​​5、CompletableFuture异步任务​​
  • ​​6、LocalDate新日期API​​
  • ​​7、函数式编程​​
  • ​​8、其他​​



《Java 8实战》读书笔记_API



一、书籍大纲

以下是书中所有章节的一个概括:



《Java 8实战》读书笔记_System_02



二、内容总结

如果你问我Java 8 这本书讲了什么,我必然不会给你罗列上面的章节清单。下列是我对该书内容的一个大纲总结,可以分为以下8个点进行阐述。

1、Lambda



行为参数化

提到这个Lambda表达式,我认为一定要提这几个字——行为参数化。我认为这几个字才是Lambda表达式的核心,区别于面向过程的代码编写,使用Lambda表达式的代码更接近面向对象。对调用者而言,我不再需要关注具体的执行逻辑,只需要调用对应的方法即可。

以Spring源码中创建Bean的代码为例:

1)getSingleton()方法的第二个参数就是一个Lambda表达式



《Java 8实战》读书笔记_API_03

2)第二个参数是一个ObjectFactory类型的接口,并且只有一个getObject方法(这也就是为什么可以直接使用简化的Lambda表达式的原因)



《Java 8实战》读书笔记_spring_04

3)在具体的getSington方法内部,直接调用getObject方法,以此来执行1)中的方法体



《Java 8实战》读书笔记_java_05


观察对应的代码,我们不难发现两个点:

第一点,具体的执行的逻辑,由方法的调用者决定,而非被被调用者决定

第二点,对被调用者而言,它执行的操作完全符合面向对象的代码设计,我不需要关心你怎么实现的,我只需要明白,你能实现。


内部类

Lambda表达式的演进有一个绕不开的点就是内部类。换句话说Lambda表达式就是一个简易版本的内部类。不妨你可以看看下面的创建一个线程的代码:

public class Test {
public static void main(String[] args) {

// 使用内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(666);
}
}).start();


// 使用Lambda表达式
new Thread(() -> {
System.out.println(666);
}).start();
}
}



两段代码达到的效果一模一样,我们也很容易就发现使用Lambda表达式代码可以简化很多。这一切的一切都源于Runable接口只有一个run方法。



《Java 8实战》读书笔记_java_06


当然想要满足这种简易的写法(使用Lambda表达式),必定离不开对应的接口只有一个方法(run方法)。如果接口含有多个方法,并且我们还想使用这种行为参数化的代码写法,那就完全可以使用内部类来代替。笔者部门的项目就是Java 6,一但我想要使用Lambda表达式的思想进行代码编写的时候,就会使用了内部类来代替。


优化设计模式

还是那几个字行为参数化,书中举例了策略模式、模板方法模式、观察者模式、责任链模式、工厂模式。这里以策略模式为例。

策略模式,太具体的概念就不再赘述,简单说来就是根据不同的类型,走不同的处理逻辑,这一点在Spring MVC的源代码中极其常见,说Spring MVC的源码是基于策略模式的一点也不为过。我们完全可以将具体的执行策略以一个参数的形式传入对应的方法,然后简化我们的代码,当然这只能针对一些简单的策略模式,太复杂了就还是中规中矩的写吧


2、Stream流

流的概念

我特别喜欢书中对于流概念的理解,分享给你们

粗略地说,集合与流之间的差异就在于什么时候进行计算。集合是一个内存中的数据结构, 它包含数据结构中目前所有的值——集合中的每个元素都得先算出来才能添加到集合中。(你可以往集合里加东西或者删东西,但是不管什么时候,集合中的每个元素都是放在内存里的,元素都得先算出来才能成为集合的一部分) 相比之下,流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计 算的。 这对编程有很大的好处。在第6章中,我们将展示构建一个质数流(2, 3, 5, 7, 11, …)有 多简单,尽管质数有无穷多个。这个思想就是用户仅仅从流中提取需要的值,而这些值——在用 户看不见的地方——只会按需生成。这是一种生产者-消费者的关系。从另一个角度来说,流就像是一个延迟创建的集合:只有在消费者要求的时候才会计算值(用管理学的话说这就是需求驱 动,甚至是实时制造)。

哲学概念区分集合和流:
你可以把流看作在时间中分布的一组值。相反,集合则是空间(这里就是计算机内存)中分布的一组值,在一个时间点上全体存在——你可以使用迭代器来访问 for-each循环中的内部成员。


简单API操作

这里直接分享书中的一个练习题,可用来熟悉Stream流的简单API操作

package pers.mobian.demo.jdk8.stream;

import pers.mobian.demo.jdk8.stream.pojo.Trader;
import pers.mobian.demo.jdk8.stream.pojo.Transaction;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.maxBy;

public class ExampleTest {
public static void main(String[] args) {
/**
* (1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
* (2) 交易员都在哪些不同的城市工作过?
* (3) 查找所有来自于剑桥的交易员,并按姓名排序。
* (4) 返回所有交易员的姓名字符串,按字母顺序排序。
* (5) 有没有交易员是在米兰工作的?
* (6) 打印生活在剑桥的交易员的所有交易额。
* (7) 所有交易中,高的交易额是多少?
* (8) 找到交易额小的交易。
*/
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");

List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950));


System.out.println("-----------1.1");
transactions.stream()
.filter(transaction -> transaction.getYear() == 2011)
.sorted(Comparator.comparing(Transaction::getValue))

// .collect(Collectors.toList())
.forEach(System.out::println);

System.out.println("-----------1.2");
transactions.stream()
.map(transaction -> transaction.getTrader().getCity())
.distinct()
.forEach(System.out::println);


System.out.println("-----------1.3");
transactions.stream()
.map(transaction -> transaction.getTrader())
.filter(trader -> trader.getCity().equals("Cambridge"))
.distinct()
.sorted(Comparator.comparing(Trader::getName))
.forEach(System.out::println);


System.out.println("-----------1.4");

transactions.stream()
.map(transaction -> transaction.getTrader().getName())
.sorted()
.distinct()
.forEach(System.out::println);


System.out.println("-----------1.5");
long milan = transactions.stream()
.map(transaction -> transaction.getTrader())
.filter(trader -> trader.getCity().equals("Milan"))
.count();
System.out.println(milan);


boolean milan1 = transactions.stream()
.anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan"));
System.out.println(milan1);

System.out.println("-----------1.6");
transactions.stream()
.filter(transaction -> transaction.getTrader().getCity().equals("Cambridge"))
.map(transaction -> transaction.getValue())
.forEach(System.out::println);


System.out.println("-----------1.7");
transactions.stream()
.map(transaction -> transaction.getValue())
.sorted(Comparator.reverseOrder())
.limit(1)
.forEach(System.out::println);


Optional<Integer> highestValue = transactions.stream()
.map(Transaction::getValue)
// 计算生成的流中的最大值
.reduce(Integer::max);

System.out.println(highestValue.get());


Comparator<Transaction> comparing = Comparator.comparing(Transaction::getValue);
System.out.println(transactions.stream()
.collect(maxBy(comparing)).get());

System.out.println("-----------1.8");
transactions.stream()
.sorted(Comparator.comparing(transaction -> transaction.getValue()))
.limit(1)
.forEach(System.out::println);


Optional<Transaction> smallestTransaction = transactions.stream()
.reduce((t1, t2) -> t1.getValue() < t2.getValue() ? t1 : t2);

System.out.println(smallestTransaction.get());


System.out.println(transactions.stream()
.min(Comparator.comparing(Transaction::getValue)).get());
}
}


其他

除了上面的Stream的简单的API操作,该书中还列举了Stream的高级操作,涉及但不仅限于分区、分组、规约等。

以及第七章中,利用Stream流的特点,相关的分支/合并框架能够更加高效的处理数据。


3、接口默认方法

这个功能可太重要了,在日常的开发工作中,我就常常需要在接口中添加对应的方法,但是由于接口一般都由一个抽象类先封装一层,导致我被迫需要在抽象类中再处理一遍方法(部门JDK6没办法)。新增加一个接口后,与之对应的抽象类就被污染了,不妥。

这种代码侵入式,在一些设计模式,如策略模式、模板方法模式的代码中尤为头疼(目前我只看到这两种,不代表只有这两种)。


4、Optional

英国一位名叫Tony Hoare的计算机科学家在它涉及的ALGOL W语言中,称空指针是“价值百万的失误”。实际上,Hoare的这段话低估了过去五十年来数百万程序员为修复空引用所耗费的代码。

JDK8引入了Optional,可以理解为就是用来避免空指针问题。

如下面的测试代码:

public static void main(String[] args) {
Optional<Integer> optional = Optional.empty();
System.out.println(optional.stream().findAny());
System.out.println(optional.stream().findAny().stream().findAny());
}

执行结果:

Optional.empty
Optional.empty


5、CompletableFuture异步任务

异步任务就是为了榨干多核处理器的算力,不浪费每一个核,丰富的API也让它能够运用于很多的业务场景

具体的使用,可以参考我之前的博客:​​CompletableFuture异步任务的简单使用​​


6、LocalDate新日期API

JDK8中引入了新的日期API,以下是书中原文



《Java 8实战》读书笔记_spring_07


《Java 8实战》读书笔记_开发语言_08


测试代码:

public static void main(String[] args) {

LocalDate date = LocalDate.of(2022, 2, 26);
System.out.println(date.getYear());
System.out.println(date.getMonth());
System.out.println(date.getDayOfWeek());
System.out.println(date.getDayOfMonth());
System.out.println(date.getDayOfYear());
}

测试结果:

2022
FEBRUARY
SATURDAY
26
57


7、函数式编程

我认为函数式编程的思想是整个JDK8的核心,值得反复品味

函数式编程贯穿于Lambda表达式和Stream流,甚至是并发高可用的理论基础。不过我目前吃的并不是很透彻,希望随着我编程经验的增加,能慢慢理解函数式编程的妙处。


分享书中的吃透了的一个小案例。

笔者工作中也有过类似的困扰,一个方法有返回值,理论上我关注的应该是返回值的情况,但事实是,执行了这个方法后,参数a对应的数据也发生了变化,以至于后来莫名其妙的发现变量a的值不对劲,又开始花费时间往回调试,看看是哪一步把a的值修改了。(代码少应该还好,但是业务流程一多,调试起来就比较麻烦)

不过就像书中所说的,这种更纯粹的写法,在代码的编写上需要花费更多的代价



《Java 8实战》读书笔记_System_09


8、其他

主要就是最后两章,开阔自己的技术广度,增加茶余饭后的谈资。

技术变革除了带来编码方便的改变,更重要的是思想的转变


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java