- 新增 .drone.yml 文件用于定义 CI/CD 流程 - 配置了基于 Docker 的部署步骤 - 设置了工作区和卷映射以支持持久化数据 - 添加了构建准备阶段和 Docker 部署阶段 - 定义了环境变量和代理设置 - 配置了 artifacts 目录的处理逻辑 - 添加了 timezone 映射以确保时间同步 - 设置了 docker.sock 映射以支持 Docker in Docker
562 lines
17 KiB
Markdown
562 lines
17 KiB
Markdown
---
|
||
title: JDK8 特性(一)
|
||
date: 2022-05-23
|
||
sidebar: 'auto'
|
||
tags:
|
||
- JDK8
|
||
categories:
|
||
- Java
|
||
---
|
||
## 1 Lambda表达式
|
||
|
||
### 1.1 Lambda标准格式
|
||
|
||
- Lambda省去面向对象的条条框框, Lambda的标准格式格式由3个部分组成
|
||
- 格式说明:
|
||
- `(...args)`: 参数列表
|
||
- `->`: 分隔或连接参数与方法体的标识符
|
||
- `{...;}`: 方法体, 主要的代码逻辑
|
||
- eg:
|
||
|
||
```java
|
||
class Demo{
|
||
public static void main(String[] args) {
|
||
new Thread(() -> {
|
||
System.out.println("线程启动");
|
||
}).start();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 1.2 Lambda初体验
|
||
|
||
#### 无参无返回值
|
||
|
||
```java
|
||
public class Demo1 {
|
||
public static void main(String[] args) {
|
||
fishDo(() -> System.out.println("小鱼在欢乐的游着..."));
|
||
}
|
||
|
||
private static void fishDo(Fish fish){
|
||
fish.swim();
|
||
}
|
||
}
|
||
/**
|
||
* 定义一个接口, 并且该接口只有一个需要实现的方法
|
||
**/
|
||
interface Fish{
|
||
void swim();
|
||
}
|
||
```
|
||
|
||
#### 有参有返回值
|
||
|
||
```java
|
||
public class Demo1 {
|
||
public static void main(String[] args) {
|
||
catEat(foodName -> {
|
||
System.out.println("小猫在吃" + foodName);
|
||
return 3;
|
||
});
|
||
}
|
||
private static void catEat(Cat cat){
|
||
System.out.println("小猫吃了" + cat.eat("🐟") + "分钟");
|
||
}
|
||
}
|
||
/**
|
||
* 定义一个接口, 并且该接口只有一个需要实现的方法
|
||
**/
|
||
interface Cat{
|
||
int eat(String foodName);
|
||
}
|
||
```
|
||
|
||
#### 小结
|
||
|
||
以后我们调用方法时,看到参数是接口就可以考虑使用Lambda表达式,Lambda表达式相当于是对接口中抽象方法的重写
|
||
|
||
### 1.3 Lambda实现原理
|
||
|
||
现有以下类
|
||
|
||
- 接口类
|
||
|
||
```java
|
||
public interface Swimable {
|
||
void swimming();
|
||
}
|
||
```
|
||
|
||
- main入口
|
||
|
||
```java
|
||
public class SwimImplDemo {
|
||
public static void main(String[] args) {
|
||
goSwimming(new Swimable() {
|
||
@Override
|
||
public void swimming() {
|
||
System.out.println("去匿名内部类游泳了");
|
||
}
|
||
});
|
||
}
|
||
|
||
private static void goSwimming(Swimable swim){
|
||
swim.swimming();
|
||
}
|
||
}
|
||
```
|
||
|
||
- 将以上的main方法进行执行后, 会编译生成以下字节码文件
|
||
|
||
- 
|
||
|
||
- 将内部类的字节码文件通过 [XJad] 进行反编译
|
||
|
||
- 匿名内部类在编译后会形成一个新的类.$
|
||
|
||
```java
|
||
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
|
||
// Jad home page: http://kpdus.tripod.com/jad.html
|
||
// Decompiler options: packimports(3) fieldsfirst ansi space
|
||
// Source File Name: SwimImplDemo.java
|
||
|
||
package com.zhuhjay.lambda;
|
||
|
||
import java.io.PrintStream;
|
||
|
||
// Referenced classes of package com.zhuhjay.lambda:
|
||
// Swimable, SwimImplDemo
|
||
|
||
static class SwimImplDemo$1 implements Swimable{
|
||
|
||
public void swimming(){
|
||
System.out.println("去匿名内部类游泳了");
|
||
}
|
||
|
||
SwimImplDemo$1(){}
|
||
}
|
||
```
|
||
|
||
- main入口改为使用Lambda表达式
|
||
|
||
```java
|
||
public class SwimImplDemo {
|
||
public static void main(String[] args) {
|
||
goSwimming(() -> {
|
||
System.out.println("去Lambda游泳了");
|
||
});
|
||
}
|
||
|
||
private static void goSwimming(Swimable swim){
|
||
swim.swimming();
|
||
}
|
||
}
|
||
```
|
||
|
||
- 将以上的main方法进行执行后, 不会生成多余的字节码文件
|
||
|
||
- 使用 [XJad] 反编译工具会失败
|
||
- 使用JDK工具来对Lambda表达式的字节码进行反汇编
|
||
|
||
```text
|
||
javap -c -p 文件名.class
|
||
-c:表示对代码进行反汇编 -p:显示所有类和成员
|
||
```
|
||
|
||
- 对Lambda表达式的字节码文件进行反汇编
|
||
|
||
```S
|
||
public class com.zhuhjay.lambda.SwimImplDemo {
|
||
public com.zhuhjay.lambda.SwimImplDemo();
|
||
Code:
|
||
0: aload_0
|
||
1: invokespecial #1 // Method java/lang/Object."<init>":()V
|
||
4: return
|
||
|
||
public static void main(java.lang.String[]);
|
||
Code:
|
||
0: invokedynamic #2, 0 // InvokeDynamic #0:swimming:()Lcom/zhuhjay/lambda/Swimable;
|
||
5: invokestatic #3 // Method goSwimming:(Lcom/zhuhjay/lambda/Swimable;)V
|
||
8: return
|
||
|
||
private static void goSwimming(com.zhuhjay.lambda.Swimable);
|
||
Code:
|
||
0: aload_0
|
||
1: invokeinterface #4, 1 // InterfaceMethod com/zhuhjay/lambda/Swimable.swimming:()V
|
||
6: return
|
||
|
||
private static void lambda$main$0();
|
||
Code:
|
||
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
|
||
3: ldc #6 // String 去Lambda游泳了
|
||
5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
|
||
8: return
|
||
}
|
||
```
|
||
|
||
- 通过与源代码的比较后, 发现多了静态方法`lambda$main$0`
|
||
|
||
- 得出: Lambda表达式会在类中新生成一个私有的静态方法, 命名为`lambda$方法名$序列`
|
||
|
||
- 通过断点调试Lambda也可以从调用栈中发现该静态方法的生成(当然不会显式的在源码中出现)
|
||
|
||
- 验证在Lambda表达式运行中会生成一个内部类
|
||
|
||
- 命令格式: `java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名`
|
||
|
||
- 执行(需要退出包运行该命令): `java -Djdk.internal.lambda.dumpProxyClasses com.zhuhjay.lambda.SwimImplDemo`
|
||
|
||
- 
|
||
- 查看反编译结果
|
||
|
||
```java
|
||
final class SwimImplDemo$$Lambda$1
|
||
implements Swimable {
|
||
|
||
public void swimming() {
|
||
SwimImplDemo.lambda$main$0();
|
||
}
|
||
|
||
private SwimImplDemo$$Lambda$1() {}
|
||
}
|
||
```
|
||
|
||
- 对以上结论来推断Lambda生成的字节码文件
|
||
|
||
```java
|
||
public class SwimImplDemo {
|
||
public static void main(String[] args) {
|
||
// 也是相当于Lambda生成了一个匿名内部类, 来调用Lambda生成的静态方法
|
||
goSwimming(new Swimable() {
|
||
public void swimming(){
|
||
SwimImplDemo.lambda$main$0();
|
||
}
|
||
});
|
||
}
|
||
/** Lambda生成的私有静态方法 **/
|
||
private static void lambda$main$0(){
|
||
System.out.println("去Lambda游泳了");
|
||
}
|
||
|
||
private static void goSwimming(Swimable swim){
|
||
swim.swimming();
|
||
}
|
||
}
|
||
```
|
||
|
||
- 小结
|
||
- 匿名内部类在编译的时候会一个class文件
|
||
- Lambda在程序运行的时候形成一个类
|
||
1. 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
|
||
2. 还会形成一个匿名内部类, 实现接口, 重写抽象方法
|
||
3. 在接口的重写方法中会调用新生成的方法.
|
||
|
||
### 1.4 Lambda省略格式
|
||
|
||
在Lambda标准格式的基础上,使用省略写法的规则为:
|
||
|
||
1. 小括号内参数的类型可以省略
|
||
2. 如果小括号内有且仅有一个参数,则小括号可以省略
|
||
3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
|
||
|
||
### 1.5 Lambda前提条件
|
||
|
||
Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
|
||
|
||
1. 方法的参数或局部变量类型必须为接口才能使用Lambda
|
||
2. 接口中有且仅有一个抽象方法(@FunctionalInterface用来检测接口是否为函数式接口)
|
||
|
||
### 1.6 Lambda和匿名内部类对比
|
||
|
||
1. 所需的类型不一样
|
||
- 匿名内部类,需要的类型可以是类,抽象类,接口
|
||
- Lambda表达式,需要的类型必须是接口
|
||
2. 抽象方法的数量不一样
|
||
- 匿名内部类所需的接口中抽象方法的数量随意
|
||
- Lambda表达式所需的接口只能有一个抽象方法
|
||
3. 实现原理不同
|
||
- 匿名内部类是在编译后会形成class
|
||
- Lambda表达式是在程序运行的时候动态生成class
|
||
|
||
## 2 JDK8接口增强
|
||
|
||
### 2.1 增强介绍
|
||
|
||
- JDK8以前的接口:
|
||
|
||
```text
|
||
interface 接口名 {
|
||
静态常量;
|
||
抽象方法;
|
||
}
|
||
```
|
||
|
||
- JDK8的接口:
|
||
|
||
```text
|
||
interface 接口名 {
|
||
静态常量;
|
||
抽象方法;
|
||
默认方法;
|
||
静态方法;
|
||
}
|
||
```
|
||
|
||
### 2.2 接口默认方法和静态方法的区别
|
||
|
||
1. 默认方法通过实例调用,静态方法通过接口名调用。
|
||
2. 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
|
||
3. 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用。
|
||
|
||
## 3 常用内置函数式接口
|
||
|
||
### 3.1 内置函数式接口的由来
|
||
|
||
我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。
|
||
|
||
### 3.2 常用函数式接口的介绍
|
||
|
||
它们主要在`java.util.function`包中。下面是最常用的几个接口。
|
||
|
||
#### Supplier:生产者
|
||
|
||
`java.util.function.Supplier<T>`接口,它意味着"供给", 对应的Lambda表达式需要"对外提供"一个符合泛型类型的对象数据。
|
||
|
||
```java
|
||
@FunctionalInterface
|
||
public interface Supplier<T> {
|
||
public abstract T get();
|
||
}
|
||
```
|
||
|
||
#### Consumer:消费者
|
||
|
||
`java.util.function.Consumer<T>`接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
|
||
|
||
```java
|
||
@FunctionalInterface
|
||
public interface Consumer<T> {
|
||
public abstract void accept(T t);
|
||
/** 该方法使得两个Consumer先后调用 c1.andThen(c2).accept(t) **/
|
||
default Consumer<T> andThen(Consumer<? super T> after) {
|
||
Objects.requireNonNull(after);
|
||
return (T t) -> { accept(t); after.accept(t); };
|
||
}
|
||
}
|
||
```
|
||
|
||
- 默认方法: andThen
|
||
- 如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen
|
||
- 要想实现组合,需要两个或多个Lambda表达式即可,而 andThen 的语义正是"一步接一步"操作
|
||
|
||
#### Function:类型转换
|
||
|
||
`java.util.function.Function<T,R>`接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有参数有返回值。
|
||
|
||
```java
|
||
@FunctionalInterface
|
||
public interface Function<T, R> {
|
||
public abstract R apply(T t);
|
||
|
||
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
|
||
Objects.requireNonNull(before);
|
||
return (V v) -> apply(before.apply(v));
|
||
}
|
||
}
|
||
```
|
||
|
||
- 默认方法: andThen
|
||
- Function 接口中有一个默认的 andThen 方法,用来进行组合操作
|
||
- 该方法同样用于"先做什么,再做什么"的场景,和 Consumer 中的 andThen 差不多
|
||
|
||
#### Predicate:判断
|
||
|
||
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用`java.util.function.Predicate<T>`接口。
|
||
|
||
```java
|
||
@FunctionalInterface
|
||
public interface Predicate<T> {
|
||
public abstract boolean test(T t);
|
||
/** && **/
|
||
default Predicate<T> and(Predicate<? super T> other) {
|
||
Objects.requireNonNull(other);
|
||
return (t) -> test(t) && other.test(t);
|
||
}
|
||
/** ! **/
|
||
default Predicate<T> negate() {
|
||
return (t) -> !test(t);
|
||
}
|
||
/** || **/
|
||
default Predicate<T> or(Predicate<? super T> other) {
|
||
Objects.requireNonNull(other);
|
||
return (t) -> test(t) || other.test(t);
|
||
}
|
||
|
||
static <T> Predicate<T> isEqual(Object targetRef) {
|
||
return (null == targetRef)
|
||
? Objects::isNull
|
||
: object -> targetRef.equals(object);
|
||
}
|
||
}
|
||
```
|
||
|
||
- 默认方法: and
|
||
- 既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用"与"逻辑连接起来实现"并且"的效果时,可以使用default方法 and
|
||
|
||
- 默认方法: or
|
||
- 与 and 的"与"类似,默认方法 or 实现逻辑关系中的"或"
|
||
|
||
- 默认方法: negate
|
||
- "与"、"或"已经了解了,剩下的"非"(取反)也会简单。它是执行了test方法之后,对结果boolean值进行"!"取反而已。一定要在 test 方法调用之前调用 negate 方法
|
||
|
||
## 4 方法引用
|
||
|
||
方法引用的注意事项
|
||
|
||
1. 被引用的方法,参数要和接口中抽象方法的参数一样
|
||
2. 当接口抽象方法有返回值时,被引用的方法也必须有返回值
|
||
|
||
### 4.1 方法引用简化Lambda
|
||
|
||
使用Lambda表达式求一个数组的和
|
||
|
||
```java
|
||
public class Demo2 {
|
||
public static void main(String[] args) {
|
||
// 将数组进行求和
|
||
printSum((arr) -> {
|
||
int sum = 0;
|
||
for (int i : arr) {
|
||
sum += i;
|
||
}
|
||
System.out.println("sum = " + sum);
|
||
});
|
||
// 使用已有的方法进行方法引用(让已实现的方法复用)
|
||
// 类名::静态方法
|
||
printSum(Demo2::getSum);
|
||
}
|
||
/** 已有的求和方法 **/
|
||
private static void getSum(int[] arr){
|
||
int sum = 0;
|
||
for (int i : arr) {
|
||
sum += i;
|
||
}
|
||
System.out.println("sum = " + sum);
|
||
}
|
||
/** 使用消费者函数式接口 **/
|
||
private static void printSum(Consumer<int[]> consumer){
|
||
int[] arr = new int[]{11, 22, 33, 44};
|
||
consumer.accept(arr);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.2 方法引用格式
|
||
|
||
- 符号表示: `::`
|
||
- 符号说明: 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
|
||
- 应用场景: 如果Lambda所要实现的方案 , 已经有其他方法存在相同方案,那么则可以使用方法引用
|
||
|
||
### 4.3 常见引用方式
|
||
|
||
方法引用在JDK 8中使用方式相当灵活,有以下几种形式:
|
||
|
||
1. `instanceName::methodName` 对象::方法名
|
||
2. `ClassName::staticMethodName` 类名::静态方法
|
||
3. `ClassName::methodName` 类名::普通方法
|
||
4. `ClassName::new` 类名::new 调用的构造器
|
||
5. `TypeName[]::new` String[]::new 调用数组的构造器
|
||
|
||
#### 对象::成员方法
|
||
|
||
这是最常见的一种用法。如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法,代码为:
|
||
|
||
```java
|
||
public class MethodRefDemo {
|
||
public static void main(String[] args) {
|
||
Date now = new Date();
|
||
// 使用Lambda表达式获取当前时间
|
||
Supplier<Long> su1 = () -> now.getTime();
|
||
System.out.println(su1.get());
|
||
// 使用方法引用获取当前时间
|
||
Supplier<Long> su2 = now::getTime;
|
||
System.out.println(su2.get());
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 类名::静态方法
|
||
|
||
由于在`java.lang.System`类中已经存在了静态方法 currentTimeMillis,所以当我们需要通过Lambda来调用该方法时,可以使用方法引用, 写法是:
|
||
|
||
```java
|
||
public class MethodRefDemo {
|
||
public static void main(String[] args) {
|
||
Supplier<Long> su3 = () -> System.currentTimeMillis();
|
||
// 等同于, 调用该类的静态方法
|
||
Supplier<Long> su4 = System::currentTimeMillis;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 类名::引用实例方法
|
||
|
||
Java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者。
|
||
|
||
```java
|
||
public class MethodRefDemo {
|
||
public static void main(String[] args) {
|
||
Function<String, Integer> f1 = (str) -> str.length();
|
||
// 等同于, 将参数作为调用者去调用方法, 然后接收对应数据类型的返回值
|
||
Function<String, Integer> f2 = String::length;
|
||
|
||
BiFunction<String, Integer, String> f3 = String::substring;
|
||
// 等同于, 将第一个参数作为调用者, 第二个参数作为参数, 然后接收对应数据类型的返回值
|
||
BiFunction<String, Integer, String> f4 = (str, index) -> str.substring(index);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 类名::new
|
||
|
||
由于构造器的名称与类名完全一样。所以构造器引用使用`类名称::new`的格式表示
|
||
|
||
```java
|
||
public class MethodRefDemo {
|
||
public static void main(String[] args) {
|
||
// 使用无参构造器实例一个String类
|
||
Supplier<String> s1 = String::new;
|
||
// 等同于
|
||
Supplier<String> s2 = () -> new String();
|
||
s1.get();
|
||
|
||
// 把具体地调用体现在了接口上
|
||
|
||
// 使用一个参数的构造器实例一个String类
|
||
Function<String, String> s3 = String::new;
|
||
// 等同于
|
||
Function<String, String> s4 = (str) -> new String(str);
|
||
s3.apply("张三");
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 数组::new
|
||
|
||
数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同
|
||
|
||
```java
|
||
public class MethodRefDemo {
|
||
public static void main(String[] args) {
|
||
Function<Integer, int[]> f = length -> new int[length];
|
||
// 等同于
|
||
Function<Integer, int[]> ff = int[]::new;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 小结
|
||
|
||
方法引用是对Lambda表达式符合特定情况下的一种缩写,它使得我们的Lambda表达式更加的精简,也可以理解为Lambda表达式的缩写形式, 不过要注意的是方法引用只能"引用"已经存在的方法!
|