跟上 Java 8
by @王爵nice
学习、思考、运用 java 8
课程介绍
- 讲什么?
- 怎么讲?
- 怎么学?
跟上 java 8 - 01
Java 8 的发展
跟上 java 8 - 02
- 自动装箱与拆箱
- 枚举
- 静态导入
- 变长参数
- 泛型
- for-each循环
JDK5
Java 8 的发展
跟上 java 8 - 02
- Desktop类和SystemTray类
- 轻量级Http Server API
- 使用Compiler API
- 插入式注解处理API
- 用Console开发控制台程序
JDK6
Java 8 的发展
跟上 java 8 - 02
- 数字变量对下滑线的支持
- switch对String的支持
- try-with-resource
- 捕获多种异常
- 创建泛型时类型推断
JDK7
Java 8 的发展
跟上 java 8 - 02
- Lambda表达式和函数式接口
- 接口的默认方法和静态方法
- 更好的类型推断
- Optional
- Streams
- 新的日期时间 API
- Nashorn JavaScript引擎
- Base64
- 并行数组
2014年发布的 Java8
理解 lambda
跟上 java 8 - 03
- 命令式和函数式
- 行为参数化
- 传递代码、行为
- 匿名类的缺陷
理解 lambda
跟上 java 8 - 03
命令式编程:命令“机器”如何去做事情(how),这样不管你想要 的是什么(what),它都会按照你的命令实现。
声明式编程:告诉“机器”你想要的是什么(what),
让机器想出如何去做(how)。
理解 lambda
跟上 java 8 - 03
什么是函数式?
public static void main(String[] args){
// Calculate total points of all active tasks using sum()
final long totalPointsOfOpenTasks = tasks
.stream()
.filter(task -> task.getStatus() == Status.OPEN)
.mapToInt(Task::getPoints)
.sum();
}
理解 lambda
跟上 java 8 - 03
行为参数化
- 需求变更
- Github 项目筛选
- 更高层次抽象
理解 lambda
跟上 java 8 - 03
小结
- 行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不 同行为的能力。
- 行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。
- 传递代码,就是将新行为作为参数传递给方法。但在Java 8之前这实现起来很啰嗦。
- 为接口声明许多只用一次的实体类而造成的啰嗦代码,在Java 8之前可以用匿名类来减少。
- Java API包含很多可以用不同行为进行参数化的方法,包括排序、线程和GUI处理。
初尝 lambda
跟上 java 8 - 04
初尝 lambda
跟上 java 8 - 04
- lambda 管中窥豹
- 在哪里以及如何使用 lambda
- 函数描述符
- @FunctionalInterface
- 函数式接口,类型推断
本节大纲
初尝 lambda
跟上 java 8 - 04
- 匿名: 它不像普通的方法那样有一个明确的名称:写得少而想得多!
- 函数: lambda函数不像方法那样属于某个特定的类。但和方法一样,有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
- 传递: lambda表达式可以作为参数传递给方法或存储在变量中。
- 简洁: 无需像匿名类那样写很多模板代码。
初尝 lambda
跟上 java 8 - 04
1. (String s) -> s.length()
2. (Project a) -> a.getStars() > 1000
3. (int x, int y) -> {
System.out.println("Result:");
System.out.println(x + y);
}
3. () -> 42
4. () -> 42 (Project p1, Project p2) -> a1.getStars().compareTo(a2.getStars())
Java 8中有效的Lambda表达式
初尝 lambda
跟上 java 8 - 04
基本语法
(parameters) -> expression
或(请注意语句的花括号)
(parameters) -> { statements; }
初尝 lambda
跟上 java 8 - 04
哪个不是有效的Lambda表达式?
(1) () -> {}
(2) () -> "Raoul"
(3) () -> {return "Mario";}
(4) (Integer i) -> return "跟上Java8" + i;
(5) (String s) -> {"跟上Java8";}
给你十分钟思考!
初尝 lambda
跟上 java 8 - 04
在哪里以及如何使用Lambda
List<Project> javaProjects =
filter(data, (Project p) -> "java".equals(p.getLanaguage()));
public interface Predicate<T> {
boolean test (T t);
}
函数式接口
初尝 lambda
跟上 java 8 - 04
下面哪些接口是函数式接口?
public interface Adder{
int add(int a, int b);
}
public interface SmartAdder extends Adder{
int add(double a, double b);
}
public interface Nothing{
}
初尝 lambda
跟上 java 8 - 04
Runnable r1 = () -> System.out.println("Hello World 1”); <- 使用Lambda
Runnable r2 = new Runnable(){ <- 使用匿名类
public void run(){
System.out.println("Hello World 2");
}
};
public static void process(Runnable r){
r.run(); <- 打印 Hello World
}
用函数式接口可以干什么呢?
初尝 lambda
跟上 java 8 - 04
函数描述符
() -> void
(Project, Project) -> int
public void process(Runnable r){
r.run();
}
process(() -> System.out.println("妈妈喊我跟上 Java 8!!"));
初尝 lambda
跟上 java 8 - 04
(1) execute( () -> {} );
public void execute(Runnable r){
r.run();
}
(2) public Callable<String> fetch() {
return () -> "Fetch 示例";
}
(3) Predicate<Project> p = (Project p) -> p.getStars();
以下哪些是使用lambda表达式的有效方式?
初尝 lambda
跟上 java 8 - 04
@FunctionalInterface又是怎么回事?
@FunctionalInterface
public interface LearnJava8 {
// 开始学习 Java 8
String go();
}
初尝 lambda
跟上 java 8 - 04
函数式接口,类型推断 - 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);
初尝 lambda
跟上 java 8 - 04
函数式接口,类型推断 - Consumer
@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);
}
}
// Lambda是 Consumer 中 accept方法的实现
forEach(Arrays.asList(1,2,3,4,5), (Integer i) -> System.out.println(i) );
初尝 lambda
跟上 java 8 - 04
函数式接口,类型推断 - Function
@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]
// lambda是 Function 接口的 apply 方法的 实现
List<Integer> l = map(
Arrays.asList("lambdas","in","action"),
(String s) -> s.length()
);
lambda 进阶 💪
跟上 java 8 - 05
- 方法引用
- 构造函数引用
- 类型检查
- 编码实践
- lambda 小结
跟上 java 8 - 05
代码类型检查的过程
List<Project> starProjects = filter(data, (Project p) -> p.getStars() > 1000))
↓
① filter(data, (Project p) -> p.getStars() > 1000))
↓
② filter(List<Project> data, Predicate<Project> predicate)
↓
③ 目标类型
↓
④ boolean test(Project project)
↓
Project -> boolean
lambda 进阶 💪
lambda 进阶 💪
跟上 java 8 - 05
-
lambda 表达式:它没有名称,但有参数列表、函数主体、返回 类型,可能还有一个可以抛出的异常的列表。
-
lambda 表达式让你可以简洁地传递代码。
-
函数式接口就是仅仅声明了一个抽象方法的接口。
-
只有在接受函数式接口的地方才可以使用 lambda 表达式。
-
lambda 表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。
小结 ①
lambda 进阶 💪
跟上 java 8 - 05
-
Java 8自带一些常用的函数式接口,放在 java.util.function 包里,包括 Predicate<T>、Function<T,R>、Supplier<T>、Consumer<T> 和 BinaryOperator<T>。
-
Lambda表达式所需要代表的类型称为目标类型。
-
方法引用让你重复使用现有的方法实现并直接传递它们。
-
Comparator、Predicate和Function等函数式接口都有几个可以用来结合 lambda 表达式的默认方法。
小结 ②
默认方法的妙用 ⛳️
跟上 java 8 - 06
- 兼容旧的API
- 接口默认方法
- 接口静态方法
- 多实现冲突解决
- 小结
默认方法的妙用 ⛳️
跟上 java 8 - 06
这个测验里,假设你是Java语言和API的一个负责人。你收到了关于 removeIf 方法的很多请求,希望能为 ArrayList、TreeSet、LinkedList 以及其他集合类型添加 removeIf 方法。
removeIf 方法的功能是删除满足给定断言的所有元素。你的任务是找到添加这个新方法、优化Collection API的最佳途径。
默认方法小测验
默认方法的妙用 ⛳️
跟上 java 8 - 06
关于继承的一些错误观点
✅ 继承不应该成为你一谈到代码复用就试图倚靠的万精油。
默认方法的妙用 ⛳️
跟上 java 8 - 06
- 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优 先级。
- 如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择 拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
- 最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。
解决问题的三条规则
默认方法的妙用 ⛳️
跟上 java 8 - 06
- Java 8中的接口可以通过默认方法和静态方法提供方法的代码实现。
- 默认方法的开头以关键字default修饰,方法体与常规的类方法相同。
- 向发布的接口添加抽象方法不是源码兼容的。
- 默认方法的出现能帮助库的设计者以后向兼容的方式演进API。
- 默认方法可以用于创建可选方法和行为的多继承。
- 我们有办法解决由于一个类从多个接口中继承了拥有相同函数签名的方法而导致的冲突。
- 类或者父类中声明的方法的优先级高于任何默认方法。如果前一条无法解决冲突,那就选择同函数签名的方法中实现得最具体的那个接口的方法。
- 两个默认方法都同样具体时,你需要在类中覆盖该方法,显式地选择使用哪个接口中提供的默认方法。
小结
Optional 🤜 干掉空指针
跟上 java 8 - 07
- null 引发的问题
- 从防御式检查到 Optional
- 异常 VS Optional
- 使用 Optional
- Optional 实战
- 小结
Optional 🤜 干掉空指针
跟上 java 8 - 07
- 它是错误之源
- 它会使你的代码膨胀
- 它自身是毫无意义的
- 它破坏了Java的哲学
- 它在Java的类型系统上开了个口子
null 带来的种种问题
Optional 🤜 干掉空指针
跟上 java 8 - 07
对Optional对象进行过滤
假设在我们的 User/Address 模型中, User 还提供了一个方法可以取得User对象的年龄,请使用下面的签名改写getStreet方法:
String getStreet(Optional<User> user, int minAge)
找出年龄大于或者等于minAge参数的User所对应的街道地址。
Optional 🤜 干掉空指针
跟上 java 8 - 07
异常 VS Optional
- 抛出异常
- 使用 Optional
Optional 🤜 干掉空指针
跟上 java 8 - 07
public int readPoint(Properties props, String name) {
String value = props.getProperty(name);
if (value != null) { <- 确保名称对应的属性存在
try {
int i = Integer.parseInt(value); <- 将String属性转 换为数字类型
if (i > 0) { <- 检查返回的数字是否为正数
return i;
}
} catch (NumberFormatException nfe) {
}
}
return 0; <- 如果前述的条件都不满足,返回0
}
以命令式编程的方式从属性中读取point值
Optional 🤜 干掉空指针
跟上 java 8 - 07
小结
- null引用在历史上被引入到程序设计语言中,目的是为了表示变量值的缺失。
- Java 8中引入了一个新的类 java.util.Optional<T> , 对存在或缺失的变量值进行建模。
- Optional类支持多种方法,比如map、flatMap、filter,它们在概念上与Stream类中对应的方法十分相似。
- 使用Optional会迫使你更积极地解引用Optional对象,以应对变量值缺失的问题。
- 使用Optional能帮助你设计更好的API。
理解 Stream ♻️
跟上 java 8 - 08
- 什么是流?
- 集合与流
- 内部迭代和外部迭代
- 中间操作和终端操作
理解 Stream ♻️
跟上 java 8 - 08
List<Project> projects = new ArrayList<>();
for (Project project : projects) {
if(project.getStars() > 1000){
result.add(project);
}
}
Collections.sort(projects, new Comparator<Project>() {
@Override
public int compare(Project o1, Project o2) {
return o1.getStars().compareTo(o2.getStars());
}
});
List<String> names = new ArrayList<>();
for (Project project : projects) {
names.add(project.getName());
}
<- 用累加器筛选元素
<- 用匿名类排序
<- 处理排序后的项目
理解 Stream ♻️
跟上 java 8 - 08
List<String> names = projects.stream()
.filter(p -> p.getStars() > 1000) <- 筛选 star 大于1000的项目
.sorted(comparing(Project::getStars)) <- 排序
.map(Project::getName) <- 提取项目名称
.collect(toList()); <- 将名称保存在List中
- 代码是以声明性方式写的
- 你可以把几个基础操作链接起来
什么是流?
理解 Stream ♻️
跟上 java 8 - 08
什么是流?
理解 Stream ♻️
跟上 java 8 - 08
集合与流
- 元素序列
- 源
- 数据处理操作
- 流水线
- 内部迭代
理解 Stream ♻️
跟上 java 8 - 08
集合与流
List<String> names =
projects.stream() <- 建立操作流水线
.filter(p -> p.getStars() > 1000) <- 筛选出star比较高的项目
.map(Project::getName) <- 获取项目名称
.limit(3) <- 只选择前3个
.collect(Collectors.toList()); <- 将结果保存在另一个List中
System.out.println(names); <- 结果是[Blade, Tale, Vue.js]
理解 Stream ♻️
跟上 java 8 - 08
集合与流
理解 Stream ♻️
跟上 java 8 - 08
外部迭代与内部迭代
外部迭代:使用Collection接口需要用户去做迭代(比如用for-each)。
内部迭代:操作流水线没有结束
// 外部迭代
for (Project project : projects) {
if (language.equals(project.getLanguage())) {
result.add(project);
}
}
// 内部迭代
List<String> names =
projects.stream()
.filter(p -> p.getStars() > 1000)
.map(Project::getName)
.limit(3)
.collect(Collectors.toList());
理解 Stream ♻️
跟上 java 8 - 08
集合与流 - 内部迭代和外部迭代
理解 Stream ♻️
跟上 java 8 - 08
中间操作和终端操作
- 中间操作:filter或sorted返回另一个流
- 终端操作:从流的流水线生成结果
看代码!
理解 Stream ♻️
跟上 java 8 - 08
在下列流水线中,你能找出中间操作和收集操作吗?
long count = menu.stream()
.filter(d -> d.getCalories() > 300)
.distinct()
.limit(3)
.count();
理解 Stream ♻️
跟上 java 8 - 08
使用流
- 一个数据源(如集合)来执行一个查询
- 一个中间操作链,形成一条流的流水线
- 一个终端操作,执行流水线,并能生成结果。
接下来看一个表格 :P
理解 Stream ♻️
跟上 java 8 - 08
小结
- 流是“从支持数据处理操作的源生成的一系列元素”
- 流利用内部迭代:迭代通过filter、map、sorted等操作
- 流操作有两类:中间操作和终端操作
- filter和map等中间操作会返回一个流,可以用它们来设置一条流水线。
- forEach和count等终端操作会返回一个非流的值
- 流中的元素是按需计算的
Stream API(上)🔗
跟上 java 8 - 09
- 创建流 - Stream.of、Arrays.stream
- 筛选 - filter、limit、distinct
- 映射 - map、flatMap
- 匹配 - anyMatch、allMatch
- 归约 - reduce
- 数值流 - IntStream、LongStream
Stream API(上)🔗
跟上 java 8 - 09
Stream API(上)🔗
跟上 java 8 - 09
小结
- Streams API可以表达复杂的数据处理查询。
- 你可以使用filter、distinct、skip和limit对流做筛选和切片。
- 你可以使用map和flatMap提取或转换流中的元素。
- 你可以使用 findFirst 和 findAny 方法查找流中的元素。用 allMatch 、 noneMatch和anyMatch方法让流匹配给定的断言。
- reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大元素。
- filter和map等操作是无状态的,它们并不存储任何状态。
- 流有三种基本的原始类型特化:IntStream、DoubleStream和LongStream。
- 流不仅可以从集合创建,也可从值、数组、文件以及iterate等特定方法创建。
Stream API(下)🔗
跟上 java 8 - 10
- 用Collectors类创建和使用收集器
- 将数据流归约为一个值
- 汇总:归约的特殊情况
- 数据分组和分区
- 转换为集合和值
- 用并行流并行处理数据
Stream API(下)🔗
跟上 java 8 - 10
-
collect是一个终端操作,它接受的参数是将流中元素累积到汇总结果的各种方式(称 为收集器)。
-
预定义收集器包括将流元素归约和汇总到一个值,例如计算最小值、最大值或平均值。这些收集器总结在Github表格中。
-
预定义收集器可以用groupingBy对流中元素进行分组,或用partitioningBy进行分区。
-
收集器可以高效地复合起来,进行多级分组、分区和归约。
小结
新的日期时间 API
跟上 java 8 - 11
- 线程安全: Date和Calendar不是线程安全的,你需要编写额外的代码处理线程安全问题
- API设计和易用性: 由于Date和Calendar的设计不当你无法完成日常的日期操作
现有API存在的问题
新的日期时间 API
跟上 java 8 - 11
- ZoneId: 时区ID,用来确定Instant和LocalDateTime互相转换的规则
- Instant: 用来表示时间线上的一个点
- LocalDate: 表示没有时区的日期, LocalDate是不可变并且线程安全的
- LocalTime: 表示没有时区的时间, LocalTime是不可变并且线程安全的
- LocalDateTime: 表示没有时区的日期时间, LocalDateTime是不可变并且线程安全的
- Clock: 用于访问当前时刻、日期、时间,用到时区
- Duration: 用秒和纳秒表示时间的数量
JSR310 解决了这些问题
CompletableFuture
跟上 java 8 - 13
- 创建 CompletableFuture
- 处理计算结果
- 转换结果
- 异常处理
- 消耗型
- 组合、Either
- allOf、anyOf
CompletableFuture
跟上 java 8 - 13
1. 同步阻塞调用
即串行调用,响应时间为所有服务的响应时间总和;
2. 半异步(异步Future)
线程池,异步Future,使用场景:并发请求多服务,总耗时为最长响应时间;提升总响应时间,但是阻塞主请求线程,高并发时依然会造成线程数过多,CPU上下文切换;
3. 全异步(Callback)
Callback方式调用,使用场景:不考虑回调时间且只能对结果做简单处理,如果依赖服务是两个或两个以上服务,则不能合并两个服务的处理结果;不阻塞主请求线程,但使用场景有限。
4. 异步回调
异步回调链式编排(JDK8 CompletableFuture),使用场景:其实不是异步调用方式,只是对依赖多服务的Callback调用结果处理做结果编排,来弥补Callback的不足,从而实现全异步链式调用。
CompletableFuture
跟上 java 8 - 13
- CompletableFuture 是一种异步事件驱动的编程模型实现
- 底层使用 Fork/Join 框架管理线程
- 弥补了 Future 的缺点
跟上 Java 8
By biezhi
跟上 Java 8
跟上 Java 8 幻灯片
- 851