build(www): 添加 Drone CI 流水线配置

- 新增 .drone.yml 文件用于定义 CI/CD 流程
- 配置了基于 Docker 的部署步骤
- 设置了工作区和卷映射以支持持久化数据
- 添加了构建准备阶段和 Docker 部署阶段
- 定义了环境变量和代理设置
- 配置了 artifacts 目录的处理逻辑
- 添加了 timezone 映射以确保时间同步
- 设置了 docker.sock 映射以支持 Docker in Docker
This commit is contained in:
2025-11-01 13:36:00 +08:00
commit 22e48d9558
272 changed files with 44007 additions and 0 deletions

561
blogs/java/java8/java8-1.md Normal file
View File

@@ -0,0 +1,561 @@
---
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方法进行执行后, 会编译生成以下字节码文件
- ![](/java8/匿名内部类生成的字节码.png)
- 将内部类的字节码文件通过 [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`
- ![](/java8/获得运行时Lambda内部类.png)
- 查看反编译结果
```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表达式的缩写形式, 不过要注意的是方法引用只能"引用"已经存在的方法!

810
blogs/java/java8/java8-2.md Normal file
View File

@@ -0,0 +1,810 @@
---
title: JDK8 特性(二)
date: 2022-05-24
sidebar: 'auto'
tags:
- JDK8
categories:
- Java
---
## 1 Stream流
### 1.1 集合处理数据的弊端
- 案例引入:
1. 首先筛选所有姓张的人;
2. 然后筛选名字有三个字的人;
3. 最后进行对结果进行打印输出。
- 传统实现
```java
public class StreamDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三丰", "周芷若", "张无忌", "赵敏", "张强");
// 1. 首先筛选所有姓张的人;
List<String> zhangList = new ArrayList<>();
for (String name : list) {
if(name.contains("张")){
zhangList.add(name);
}
}
// 2. 然后筛选名字有三个字的人;
List<String> threeList = new ArrayList<>();
for (String name : zhangList) {
if(name.length() == 3){
threeList.add(name);
}
}
// 3. 最后进行对结果进行打印输出。
for (String name : threeList) {
System.out.println(name);
}
}
}
```
- Stream流式实现
```java
public class StreamDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张三丰", "周芷若", "张无忌", "赵敏", "张强");
// 1. 首先筛选所有姓张的人; 2. 然后筛选名字有三个字的人; 3. 最后进行对结果进行打印输出
list.stream()
.filter(str -> str.contains("张"))
.filter(str -> str.length() == 3)
.forEach(System.out::println);
}
}
```
### 1.2 Stream流式思想概述
**注意Stream和IO流(InputStream/OutputStream)没有任何关系请暂时忘记对传统IO流的固有印象**
Stream流式思想类似于工厂车间的“生产流水线”Stream流不是一种数据结构不保存数据而是对数据进行加工处理。Stream可以看作是流水线上的一个工序。在流水线上通过多个工序让一个原材料加工成一个商品。
![](/java8/Stream流式思想.png)
![](/java8/Stream流式思想1.png)
Stream API能让我们快速完成许多复杂的操作如筛选、切片、映射、查找、去除重复统计匹配和归约。
### 1.3 获取Stream流的两种方式
- 方式一
- Collection集合接口中有默认方法`default Stream<E> stream(){...}`
- 也就是说实现该接口的集合类都可以直接调用stream()方法获取流对象
- 方式二
- Stream中的静态方法`static<T> Stream<T> of(T t){...}`
- 可以调用该方法获取流对象
### 1.4 了解Stream流的常用方法和注意事项
- Stream常用方法
| 方法名 | 方法作用 | 返回值类型 | 方法种类 |
| ------- | ---------- | ---------- | -------- |
| count | 统计个数 | long | 终结 |
| forEach | 逐一处理 | void | 终结 |
| filter | 过滤 | Stream | 函数拼接 |
| limit | 取用前几个 | Stream | 函数拼接 |
| skip | 跳过前几个 | Stream | 函数拼接 |
| map | 映射 | Stream | 函数拼接 |
| concat | 组合 | Stream | 函数拼接 |
- 终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。
- 非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结方法。)
- Stream注意事项
1. Stream只能操作一次
2. Stream方法返回的是新的流
3. Stream不调用终结方法中间的操作不会执行
#### forEach
- forEach用来遍历流中的数据 -> 终结方法
```text
void forEach(Consumer<? super T> action);
```
- 该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void forEach(){
list.stream().forEach(System.out::println);
}
}
```
#### count
- count方法来统计其中的元素个数 -> 终结方法
```text
long count();
```
- 该方法返回一个long值代表元素个数。基本使用
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void count(){
System.out.println(list.stream().count());
}
}
```
#### filter
- filter用于过滤数据返回符合过滤条件的数据 -> 非终结方法
```text
Stream<T> filter(Predicate<? super T> predicate);
```
- ![](/java8/filter.png)
- 该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void filter(){
// 计算名字长度为3的人数
System.out.println(list.stream().filter(name -> name.length() == 3).count());
}
}
```
#### limit
- limit 方法可以对流进行截取只取用前n个 -> 非终结方法
```text
Stream<T> limit(long maxSize);
```
- 参数是一个long型如果集合当前长度大于参数则进行截取。否则不进行操作
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void limit(){
list.stream().limit(3).forEach(System.out::println);
}
}
```
#### skip
- 如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流 -> 非终结方法
```text
Stream<T> skip(long n);
```
- 如果流的当前长度大于n则跳过前n个; 否则将会得到一个长度为0的空流。
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void skip(){
list.stream().skip(3).forEach(System.out::println);
}
}
```
#### map
- 如果需要将流中的元素映射到另一个流中,可以使用 map 方法 -> 非终结方法
```text
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
```
- 该接口需要一个 Function 函数式接口参数可以将当前流中的T类型数据转换为另一种R类型的流
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void map(){
list.stream()
// 类型增强
.map(name -> "name: " + name)
.forEach(System.out::println);
}
}
```
#### sorted
- 如果需要将数据排序,可以使用 sorted 方法。 -> 非终结方法
```text
Stream<T> sorted(); // 根据元素的自然顺序排序
Stream<T> sorted(Comparator<? super T> comparator); // 自定义比较器排序
```
- 该方法可以有两种不同地排序实现
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void sorted(){
list.stream()
// 默认自然序
.sorted()
// 执行比较器进行排序
.sorted(((o1, o2) -> o1.length() - o2.length()))
.forEach(System.out::println);
}
}
```
#### distinct
- 如果需要去除重复数据,可以使用 distinct 方法 -> 非终结方法
- 自定义类型是根据对象的 hashCode 和 equals 来去除重复元素的
```text
Stream<T> distinct();
```
- 基本使用
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void distinct(){
list.add("迪丽热巴");
list.add("张无忌");
list.stream()
.distinct()
.forEach(System.out::println);
}
}
```
#### match
- 如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法 -> 终结方法
```text
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
```
- 基本使用
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void match(){
// allMatch 所有元素都需要满足
System.out.println(list.stream()
.allMatch(name -> name.contains("张")));
// anyMatch 如果有元素满足即可
System.out.println(list.stream()
.anyMatch(name -> name.contains("张")));
// noneMatch 所有元素都不满足
System.out.println(list.stream()
.noneMatch(name -> name.contains("男")));
}
}
```
#### find
- 如果需要找到某些数据,可以使用 find 相关方法 -> 终结方法
```text
Optional<T> findFirst();
Optional<T> findAny();
```
- 基本使用
```java
public class StreamTest {
private List<String> list;
@Before
public void before(){
list = new ArrayList<>();
Collections.addAll(list, "迪丽热巴", "张三丰", "周芷若", "张无忌", "赵敏", "张强");
}
@Test
public void find(){
// 都是获取第一个元素
Optional<String> optional = list.stream().findAny();
Optional<String> optional1 = list.stream().findFirst();
System.out.println(optional.get());
System.out.println(optional1.get());
}
}
```
#### max && min
- 如果需要获取最大和最小值,可以使用 max 和 min 方法 -> 终结方法
```text
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
```
- 基本使用
```java
public class StreamTest {
@Test
public void min_max(){
// 传入一个比较器, 排序完后获取最后一个
Optional<Integer> max = Stream.of(4, 2, 7, 1).max((o1, o2) -> o1 - o2);
System.out.println(max.get());
// 传入一个比较器, 排序完后获取第一个
Optional<Integer> min = Stream.of(4, 2, 7, 1).min((o1, o2) -> o1 - o2);
System.out.println(min.get());
}
}
```
#### reduce
- 如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法 -> 终结方法
```text
T reduce(T identity, BinaryOperator<T> accumulator);
```
- 基本使用
```java
public class StreamTest {
@Test
public void reduce(){
Integer reduce = Stream.of(4, 5, 3, 9)
// T identity: 默认值
// BinaryOperator<T> accumulator: 对数据的处理方式
// 获取最大值, 初始值为0
.reduce(0, Integer::max);
System.out.println(reduce);
reduce = Stream.of(4, 5, 3, 9)
// T identity: 默认值
// BinaryOperator<T> accumulator: 对数据的处理方式
// 获取加和结果, 初始值为0
.reduce(0, Integer::sum);
System.out.println(reduce);
}
}
```
![](/java8/reduce执行流程.png)
#### map && reduce结合
- 使用案例
```java
public class StreamTest {
@Test
public void reduceAndMap(){
// 获取最大年龄
Optional<Integer> maxAge = Stream.of(
new Person("张三", 18),
new Person("李四", 20),
new Person("王五", 17),
new Person("小红", 19),
new Person("小明", 18))
.map(Person::getAge)
.reduce(Integer::max);
System.out.println(maxAge.get());
// 获取年龄总和
Optional<Integer> totalAge = Stream.of(
new Person("张三", 18),
new Person("李四", 22),
new Person("王五", 17),
new Person("小红", 19),
new Person("小明", 21))
.map(Person::getAge)
.reduce(Integer::sum);
System.out.println(totalAge.get());
}
}
```
#### mapToInt
- 如果需要将Stream中的Integer类型数据转成int类型可以使用 mapToInt 方法 -> 终结方法
```text
IntStream mapToInt(ToIntFunction<? super T> mapper);
```
- ![](/java8/filter.png)
- 该接口需要一个 Function 函数式接口参数可以将当前流中的T类型数据转换为另一种R类型的流
```java
public class StreamTest {
@Test
public void mapToInt(){
// Integer占用的内存比int多, 在Stream流中会自动拆箱装箱
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
// 将Integer类型转换为int类型, 可以节省空间
IntStream intStream = stream
.mapToInt(Integer::intValue);
}
}
```
#### concat
- 如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat -> 终结方法
- 这是一个静态方法,与 `java.lang.String` 当中的 concat 方法是不同的
```text
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b){...}
```
- 基本使用
```java
public class StreamTest {
@Test
public void concat(){
Stream<String> streamA = Stream.of("张三");
Stream<String> streamB = Stream.of("李四");
// 将以上流合并, 合并的流就不能够继续操作了
Stream.concat(streamA, streamB).forEach(System.out::println);
}
}
```
### 1.5 Stream综合案例
- 现在有两个 ArrayList 集合存储队伍当中的多个成员姓名要求使用Stream实现若干操作步骤
1. 第一个队伍只要名字为3个字的成员姓名
2. 第一个队伍筛选之后只要前3个人
3. 第二个队伍只要姓张的成员姓名;
4. 第二个队伍筛选之后不要前2个人
5. 将两个队伍合并为一个队伍;
6. 根据姓名创建 Person 对象;
7. 打印整个队伍的Person对象信息。
```java
public class StreamCaseDemo {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
List<String> two = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七公");
Collections.addAll(two, "古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
Stream<String> streamOne = one.stream()
// 1. 第一个队伍只要名字为3个字的成员姓名
.filter(name -> name.length() == 3)
// 2. 第一个队伍筛选之后只要前3个人
.limit(3);
Stream<String> streamTwo = two.stream()
// 3. 第二个队伍只要姓张的成员姓名
.filter(name -> name.startsWith("张"))
// 4. 第二个队伍筛选之后不要前2个人
.skip(2);
// 5. 将两个队伍合并为一个队伍
Stream<String> stream = Stream.concat(streamOne, streamTwo);
// 6. 根据姓名创建 Person 对象
stream.map(Person::new)
// 7. 打印整个队伍的Person对象信息
.forEach(System.out::println);
}
}
```
### 1.6 收集Stream流中的结果
- 对流操作完成之后,如果需要将流的结果保存到数组或集合中,可以收集流中的数据
#### 收集结果到集合中
- Stream流提供 collect 方法,其参数需要一个`java.util.stream.Collector<T,A, R>`接口对象来指定收集到哪种集合中。`java.util.stream.Collectors`类提供一些方法可以作为Collector`接口的实例:
- `public static <T> Collector<T, ?, List<T>> toList()`: 转换为 List 集合
- `public static <T> Collector<T, ?, Set<T>> toSet()`: 转换为 Set 集合
- `public static <T, C extends Collection<T>>
Collector<T, ?, C> toCollection(Supplier<C> collectionFactory)`: 收集到指定的集合中
#### 收集结果到数组中
- Stream提供 toArray 方法来将结果放到一个数组中
```text
Object[] toArray(); // 返回Object数组
<A> A[] toArray(IntFunction<A[]> generator); // 返回指定数组
```
#### 对流中数据进行聚合计算
- 当我们使用Stream流处理数据后可以像数据库的聚合函数一样对某个字段进行操作。比如获取最大值获取最小值求总和平均值统计数量。
```java
public class StreamCollectTest {
@Test
public void aggregation(){
List<Person> personList = Stream.of(
new Person("张三", 18),
new Person("李四", 22),
new Person("王五", 17),
new Person("小红", 19),
new Person("小明", 21))
.collect(Collectors.toList());
// 获取最大值
Optional<Person> optional = personList.stream()
.collect(Collectors.maxBy((o1, o2) -> o1.getAge() - o2.getAge()));
System.out.println("最大值: " + optional.get());
// 获取最小值
Optional<Person> optionalMin = personList.stream()
.collect(Collectors.minBy((o1, o2) -> o1.getAge() - o2.getAge()));
System.out.println("最小值: " + optionalMin.get());
// 求总和
Integer totalAge = personList.stream()
.collect(Collectors.summingInt(Person::getAge));
System.out.println("总和: " + totalAge);
// 平均值
Double avg = personList.stream()
.collect(Collectors.averagingInt(Person::getAge));
System.out.println("平均值: " + avg);
// 统计数量
Long count = personList.stream()
.collect(Collectors.counting());
System.out.println("总共: " + count);
// 以上方法聚合
IntSummaryStatistics summaryStatistics = personList.stream()
.collect(Collectors.summarizingInt(Person::getAge));
System.out.println("上面所有方法的聚合: " + summaryStatistics);
}
}
```
#### 对流中数据进行分组
- 当我们使用Stream流处理数据后可以根据某个属性将数据分组
```java
public class StreamCollectTest {
@Test
public void group(){
List<Person> personList = Stream.of(
new Person("张三", 18),
new Person("李四", 22),
new Person("王五", 18),
new Person("小红", 18),
new Person("小明", 21))
.collect(Collectors.toList());
// 通过年龄来进行分组
personList.stream()
.collect(Collectors.groupingBy(Person::getAge))
// 分组结果为键值对
.forEach((key, value) -> System.out.println(key + "::" + value));
// 将年龄大于19的分为一组, 小于19分为一组
personList.stream()
.collect(Collectors.groupingBy(s -> {
if(s.getAge() > 19){
return ">=19";
}else{
return "<19";
}
}))
// 分组结果为键值对, 键为groupingBy返回的数据
.forEach((k, v) -> System.out.println(k + "::" + v));
}
}
```
#### 对流中数据进行多级分组
- 还可以对数据进行多级分组:
```java
public class StreamCollectTest {
@Test
public void multiGroup(){
List<Person> personList = Stream.of(
new Person("张三丰", 18),
new Person("迪丽热巴", 22),
new Person("古力娜扎", 18),
new Person("迪迦奥特曼", 18),
new Person("宇宙无敌法外狂徒张三", 21))
.collect(Collectors.toList());
// 先根据年龄分组, 每组中再根据名字的长度分组
personList.stream()
.collect(
// 根据年龄分组
Collectors.groupingBy(p -> {
if(p.getAge() > 19){
return "大于19";
}else{
return "小于等于19";
}
},
// 根据名字长度分组
Collectors.groupingBy(p -> {
if(p.getName().length() > 4){
return "较长的名字";
}else{
return "较短的名字";
}
}))
)
// 结果的类型为: Map<String, Map<String, Person>>
.forEach((oneK, oneV) -> {
System.out.println("年龄" + oneK);
oneV.forEach((twoK, twoV) -> {
System.out.println("\t" + twoK + "::" + twoV);
});
});
/*
result -> {
年龄小于等于19
较长的名字::[Person{name='迪迦奥特曼', age=18}]
较短的名字::[Person{name='张三丰', age=18}, Person{name='古力娜扎', age=18}]
年龄大于19
较长的名字::[Person{name='宇宙无敌法外狂徒张三', age=21}]
较短的名字::[Person{name='迪丽热巴', age=22}]
}
*/
}
}
```
#### 对流中数据进行分区
- `Collectors.partitioningBy`会根据值是否为true把集合分割为两个列表一个true列表一个false列表
- ![](/java8/流数据分区.png)
```java
public class StreamCollectTest {
@Test
public void partition(){
List<Person> personList = Stream.of(
new Person("张三", 18),
new Person("李四", 22),
new Person("王五", 18),
new Person("小红", 18),
new Person("小明", 21))
.collect(Collectors.toList());
personList.stream()
// 将结果分为, true和false两个分区
.collect(Collectors.partitioningBy(p -> p.getAge() > 19))
.forEach((k, v) -> System.out.println(k + "::" + v));
}
}
```
#### 对流中数据进行拼接
- `Collectors.joining`会根据指定的连接符,将所有元素连接成一个字符串
```java
public class StreamCollectTest {
@Test
public void join(){
List<Person> personList = Stream.of(
new Person("张三", 18),
new Person("李四", 22),
new Person("王五", 18),
new Person("小红", 18),
new Person("小明", 21))
.collect(Collectors.toList());
// 根据一个字符拼接
String names = personList.stream()
.map(Person::getName)
.collect(Collectors.joining("-"));
System.out.println(names);
// 根据三个字符拼接
names = personList.stream()
.map(Person::getName)
// 参数说明: 分隔符, 前缀, 后缀
.collect(Collectors.joining(",", "{", "}"));
System.out.println(names);
}
}
```

258
blogs/java/java8/java8-3.md Normal file
View File

@@ -0,0 +1,258 @@
---
title: JDK8 特性(三)
date: 2022-05-25
sidebar: 'auto'
tags:
- JDK8
categories:
- Java
---
## 1 Stream流-续
### 1.7 并行的Stream流
- 串行的Stream流
- 目前我们使用的Stream流是串行的就是在一个线程上执行。
- 并行的Stream流
- parallelStream其实就是一个并行执行的流。它通过默认的ForkJoinPool可能提高多线程任务的速度。
#### 获取并行Stream流的两种方式
1. 直接获取并行的流
```java
public class StreamParallelTest {
@Test
public void parallel(){
ArrayList<Integer> list = new ArrayList<>();
// 直接获取并行的流
Stream<Integer> stream = list.parallelStream();
}
}
```
2. 将串行流转成并行流
```java
public class StreamParallelTest {
@Test
public void serialToParallel(){
Stream.of(9, 1, 34, 5, 3, 21, 56, 9)
// 转成并行流
.parallel()
.filter(i -> {
System.out.println(Thread.currentThread() + "::" + i);
return i > 50;
})
.forEach(System.out::println);
}
}
```
#### 串行和并行效率的比较
- 使用for循环串行Stream流并行Stream流来对10亿个数字求和。看消耗的时间。
```java
public class StreamParallelTest {
private static final int TIMES = 1000000000;
long start;
@Before
public void init(){
start = System.currentTimeMillis();
}
@Test
public void useFor(){
// 消耗时间: 339ms
int sum = 0;
for (int i = 0; i < TIMES; i++) {
sum += i;
}
}
@Test
public void useSerialStream(){
// 获取足够长度的串行流进行加和
// 消耗时间: 631ms
LongStream.rangeClosed(0, TIMES)
.reduce(0, Long::sum);
}
@Test
public void useParallelStream(){
// 获取足够长度的并行流进行加和
// 消耗时间: 317ms
LongStream.rangeClosed(0, TIMES)
.parallel()
.reduce(0, Long::sum);
}
@After
public void destroy(){
System.out.println("消耗时间: " +
(System.currentTimeMillis() - start) +
"ms");
}
}
```
- 我们可以看到parallelStream的效率是最高的。
- Stream并行处理的过程会分而治之也就是将一个大任务切分成多个小任务这表示每个任务都是一个操作。
#### ParallelStream线程安全问题
```java
public class StreamParallelTest {
@Test
public void parallelStreamNotice(){
List<Integer> list = new ArrayList<>(1000);
// 并行线程不安全
// list.size() = 947
IntStream.range(0, 1000)
.parallel()
.forEach(i -> list.add(i));
System.out.println("list.size() = " + list.size());
// 解决线程安全问题方案一: 使用同步代码块
Object lock = new Object();
IntStream.range(0, 1000)
.parallel()
.forEach(i -> {
synchronized (lock){
list.add(i);
}
});
System.out.println("list.size() = " + list.size());
// 解决线程安全问题方案二: 使用线程安全的集合
// 使用 Vector<Integer> 集合
Vector<Integer> v = new Vector<>();
IntStream.range(0, 1000)
.parallel()
.forEach(i -> v.add(i));
System.out.println("v.size() = " + v.size());
// 解决线程安全问题方案二: 使用线程安全的集合
// 使用集合工具类提供的线程安全的集合
List<Integer> synchronizedList = Collections.synchronizedList(list);
IntStream.range(0, 1000)
.parallel()
.forEach(i -> synchronizedList.add(i));
System.out.println("synchronizedList.size() = " + synchronizedList.size());
// 解决线程安全问题方案三: 调用Stream流的collect/toArray
// 使用Stream流的collect
List<Integer> collect = IntStream.range(0, 1000)
.parallel()
.boxed()
.collect(Collectors.toList());
System.out.println("collect.size() = " + collect.size());
// 解决线程安全问题方案三: 调用Stream流的collect/toArray
// 使用Stream流的toArray
Integer[] integers = IntStream.range(0, 1000)
.parallel()
.boxed()
.toArray(Integer[]::new);
System.out.println("integers.length = " + integers.length);
}
}
```
### 1.8 Fork/Join框架介绍
- parallelStream使用的是Fork/Join框架。Fork/Join框架自JDK 7引入。Fork/Join框架可以将一个大任务拆分为很多小 任务来异步执行。 Fork/Join框架主要包含三个模块
1. 线程池ForkJoinPool
2. 任务对象ForkJoinTask
3. 执行任务的线程ForkJoinWorkerThread
![](/java8/ForkJoin一览.png)
#### Fork/Join原理-分治法
- ForkJoinPool主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题。典型的应用比如快速排序算法ForkJoinPool需要使用相对少的线程来处理大量的任务。比如要对1000万个数据进行排序那么会将这个任务分割成 两个500万的排序任务和一个针对这两组500万数据的合并任务。以此类推对于500万的数据也会做出同样的分割处理到最后会设置一个阈值来规定当数据规模到多少时停止这样的分割处理。比如当元素的数量小于10时会停止分割转而使用插入排序对它们进行排序。那么到最后所有的任务加起来会有大概2000000+个。问题的关键在于,对于一个任务而言,只有当它所有的子任务完成之后,它才能够被执行。
![](/java8/ForkJoin-分治法.png)
#### Fork/Join原理-工作窃取算法
- Fork/Join最核心的地方就是利用了现代硬件设备多核在一个操作时候会有空闲的cpu那么如何利用好这个空闲的cpu就成了提高性能的关键而这里我们要提到的工作窃取work-stealing算法就是整个Fork/Join框架的核心理念 Fork/Join工作窃取work-stealing算法是指某个线程从其他队列里窃取任务来执行。
![](/java8/ForkJoin-工作窃取算法.png)
- 那么为什么需要使用工作窃取算法呢假如我们需要做一个比较大的任务我们可以把这个任务分割为若干互不依赖的子任务为了减少线程间的竞争于是把这些子任务分别放到不同的队列里并为每个队列创建一个单独的线程来执行队列里的任务线程和队列一一对应比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的 任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任 务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永 远从双端队列的尾部拿任务执行。
- 工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。
- 上文中已经提到了在Java 8引入了自动并行化的概念。它能够让一部分Java代码自动地以并行的方式执行也就是我们使用了ForkJoinPool的ParallelStream。
- 对于ForkJoinPool通用线程池的线程数量通常使用默认值就可以了即运行时计算机的处理器数量。可以通过设置系统属性`java.util.concurrent.ForkJoinPool.common.parallelism=N`N为线程数量来调整ForkJoinPool的线程数量可以尝试调整成不同的参数来观察每次的输出结果。
#### Fork/Join案例
- 需求使用Fork/Join计算1-10000的和当一个任务的计算数量大于3000时拆分任务数量小于3000时计算。
![](/java8/ForkJoin案例.png)
```java
public class ForkJoinDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
SumRecursiveTask task = new SumRecursiveTask(1, 10000L);
Long result = pool.invoke(task);
System.out.println("result = " + result);
long end = System.currentTimeMillis();
System.out.println("消耗的时间为: " + (end - start));
}
}
class SumRecursiveTask extends RecursiveTask<Long> {
private static final long THRESHOLD = 3000L;
private final long start;
private final long end;
public SumRecursiveTask(long start, long end){
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if (length <= THRESHOLD) {
// 任务不用再拆分了.可以计算了
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
// 数量大于预定的数量,任务还需要再拆分
long middle = (start + end) / 2;
SumRecursiveTask left = new SumRecursiveTask(start, middle);
left.fork();
SumRecursiveTask right = new SumRecursiveTask(middle + 1, end);
right.fork();
return left.join() + right.join();
}
}
}
```
### 1.9 小结
1. parallelStream是线程不安全的
2. parallelStream适用的场景是CPU密集型的只是做到别浪费CPU假如本身电脑CPU的负载很大那还到处用并行流那并不能起到作用
3. I/O密集型磁盘I/O、网络I/O都属于I/O操作这部分操作是较少消耗CPU资源一般并行流中不适用于I/O密集型的操作就比如使用并流行进行大批量的消息推送涉及到了大量I/O使用并行流反而慢了很多
4. 在使用并行流的时候是无法保证元素的顺序的,也就是即使你用了同步集合也只能保证元素都正确但无法保证其中的顺序

445
blogs/java/java8/java8-4.md Normal file
View File

@@ -0,0 +1,445 @@
---
title: JDK8 特性(四)
date: 2022-05-26
sidebar: 'auto'
tags:
- JDK8
categories:
- Java
---
## 1 Optional
### 1.1 Optional介绍
- Optional是一个没有子类的工具类Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查防止NullPointerException。
### 1.2 Optional基本使用
- Optional类的创建方式
```text
Optional.of(T t) : 创建一个 Optional 实例, 不能为空
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
```
- Optional类的常用方法
```text
isPresent() : 判断是否包含值,包含值返回true不包含值返回false
get() : 如果Optional有值则将其返回否则抛出NoSuchElementException
orElse(T t) : 如果调用对象包含值返回该值否则返回参数t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理并返回处理后的Optional否则返回 Optional.empty()
```
### 1.3 Optional高级使用
```java
public class OptionalTest {
@Test
public void ifPresent(){
// Optional<User> op = Optional.of(new User("张三", 18));
Optional<User> op = Optional.empty();
// 如果存在就做点什么, 没有就不做了
op.ifPresent(user -> {
System.out.println(user.toString());
});
}
@Test
public void map(){
User user = new User("迪丽热巴", 20);
// 判断如果存在, 那么就将名字重新拼接
Optional<User> optional = Optional.of(user);
System.out.println(getNewName(optional));
}
private String getNewName(Optional<User> optional){
return optional.map(User::getUsername)
.map(str -> str.substring(2))
.orElse("null");
}
}
```
## 2 新的日期时间API
- 旧版日期时间API存在的问题
1. 设计很差:在`java.util`和`java.sql`的包中都有日期类,`java.util.Date`同时包含日期和时间,而`java.sql.Date`仅包含日期。此外用于格式化和解析的类在`java.text`包中定义。
2. 非线程安全:`java.util.Date`是非线程安全的所有的日期类都是可变的这是Java日期类最大的问题之一。
3. 时区处理麻烦日期类并不提供国际化没有时区支持因此Java引入了`java.util.Calendar`和 `java.util.TimeZone`类,但他们同样存在上述所有的问题。
### 2.1 新的日期时间API介绍
- JDK 8中增加了一套全新的日期时间API这套API设计合理是线程安全的。新的日期及时间API位于 `java.time`包中,下面是一些关键类。
- LocalDate :表示日期,包含年月日,格式为 2019-10-16
- LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
- LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
- DateTimeFormatter :日期时间格式化类。
- Instant时间戳表示一个特定的时间瞬间。
- Duration用于计算2个时间(LocalTime时分秒)的距离
- Period用于计算2个日期(LocalDate年月日)的距离
- ZonedDateTime :包含时区的时间
- Java中使用的历法是ISO 8601日历系统它是世界民用历法也就是我们所说的公历。平年有365天闰年是366天。此外Java 8还提供了4套其他历法分别是
- ThaiBuddhistDate泰国佛教历
- MinguoDate中华民国历
- JapaneseDate日本历
- HijrahDate伊斯兰历
### 2.2 日期时间类
- LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象分别表示使用 ISO-8601 日历系统的日期、时 间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
- LocalDate: 获取日期的信息
```java
public class DateTest {
@Test
public void localDate(){
// 创建指定日期
LocalDate localDate = LocalDate.of(2022, 11, 11);
System.out.println("localDate = " + localDate); // localDate = 2022-11-11
// 得到当前日期
LocalDate now = LocalDate.now();
System.out.println("now = " + now); // now = 2022-05-23
// 获取日期信息
System.out.println("年: " + now.getYear()); // 年: 2022
System.out.println("月: " + now.getMonthValue()); // 月: 5
System.out.println("日: " + now.getDayOfMonth()); // 日: 23
System.out.println("星期: " + now.getDayOfWeek()); // 星期: MONDAY
}
}
```
- LocalTime: 获取时间信息
```java
public class DateTest {
@Test
public void localTime(){
// 得到指定的时间
LocalTime time = LocalTime.of(12, 12, 12);
System.out.println("time = " + time); // time = 12:12:12
// 得到当前时间
LocalTime now = LocalTime.now();
System.out.println("now = " + now); // now = 11:43:10.495
// 获取时间信息
System.out.println("小时: " + now.getHour()); // 小时: 11
System.out.println("分钟: " + now.getMinute()); // 分钟: 43
System.out.println("秒: " + now.getSecond()); // 秒: 10
System.out.println("纳秒: " + now.getNano()); // 纳秒: 495000000
}
}
```
- LocalDateTime: 获取日期时间类型
```java
public class DateTest {
@Test
public void localDateTime(){
LocalDateTime time = LocalDateTime.of(2022, 11, 11, 12, 12, 12);
System.out.println("time = " + time); // time = 2022-11-11T12:12:12
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); // now = 2022-05-23T11:47:29.308
// 获取日期信息
System.out.println("年: " + now.getYear()); // 年: 2022
System.out.println("月: " + now.getMonthValue()); // 月: 5
System.out.println("日: " + now.getDayOfMonth()); // 日: 23
System.out.println("时: " + now.getHour()); // 时: 11
System.out.println("分: " + now.getMinute()); // 分: 47
System.out.println("秒: " + now.getSecond()); // 秒: 29
System.out.println("纳秒: " + now.getNano()); // 纳秒: 308000000
}
}
```
- 对日期时间的修改对已存在的LocalDate对象创建它的修改版最简单的方式是使用withAttribute方法。 withAttribute方法会创建对象的一个副本并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象他们不会影响原来的对象。
```java
public class DateTest {
@Test
public void localDateTimeUpdate(){
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); // now = 2022-05-23T12:47:18.640
// 修改年份, 每份修改后的数据都是一个新的对象, 不会与原对象冲突
LocalDateTime setYear = now.withYear(2025);
System.out.println("修改年份后 = " + setYear); // 修改年份后 = 2025-05-23T12:47:18.640
System.out.println("setYear == now? " + (setYear == now)); // setYear == now? false
System.out.println("修改月份: " + now.withMonth(1)); // 修改月份: 2022-01-23T12:47:18.640
System.out.println("修改日: " + now.withDayOfMonth(1)); // 修改日: 2022-05-01T12:47:18.640
System.out.println("修改小时: " + now.withMonth(12)); // 修改小时: 2022-12-23T12:47:18.640
// 在当前对象的基础上加上或减去指定的时间
LocalDateTime localDateTime = now.plusDays(5);
System.out.println("五天后: " + localDateTime); // 五天后: 2022-05-28T12:47:18.640
System.out.println("10年后: " + now.plusYears(10)); // 10年后: 2032-05-23T12:47:18.640
System.out.println("20年后: " + now.plusYears(20)); // 20年后: 2042-05-23T12:47:18.640
System.out.println("3个月前: " + now.minusMonths(3)); // 3个月前: 2022-02-23T12:47:18.640
System.out.println("3分钟前: " + now.minusMinutes(3)); // 3分钟前: 2022-05-23T12:44:18.640
}
}
```
- 日期比较
```java
public class DateTest {
@Test
public void dateEqual(){
// 在JDK8中LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2011, 11, 11);
System.out.println(now.isBefore(date)); // false
System.out.println(date.isBefore(now)); // true
}
}
```
### 2.3 日期格式化与解析
- 通过`java.time.format.DateTimeFormatter`类可以进行日期时间解析与格式化。
```java
public class DateTest {
@Test
public void dateFormatAndParse(){
// 获得当前的日期
LocalDateTime now = LocalDateTime.now();
// 自定义日期格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd & HH:mm:ss");
// 将日期转换为对应的格式
String format = dateTimeFormatter.format(now);
System.out.println("format = " + format);
// 将字符串解析为时间
LocalDateTime time = LocalDateTime.parse(format, dateTimeFormatter);
System.out.println("time = " + time);
}
}
```
### 2.4 Instant类
- Instant 时间戳/时间线内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。
- 只能操作秒以下的级别, 用来程序做一些统计的类, 比如说计算运行时间等, 不是给用户使用的
```java
public class InstantTest {
@Test
public void instant(){
Instant now = Instant.now();
System.out.println("当前时间戳 = " + now);
// 获取从1970 01 01 00:00:00的秒
System.out.println(now.getNano());
System.out.println(now.getEpochSecond());
System.out.println(now.toEpochMilli());
System.out.println(System.currentTimeMillis());
System.out.println("5s以后: " + now.plusSeconds(5));
Instant instant = Instant.ofEpochSecond(5);
System.out.println("instant = " + instant);
}
}
```
### 2.5 计算日期时间差的类
- Duration/Period类: 计算日期时间差。
1. Duration用于计算2个时间(LocalTime时分秒)的距离
2. Period用于计算2个日期(LocalDate年月日)的距离
- 注意: 两个类的计算都是第二个参数减去第一个参数, 所以当参数对调时会产生负数的情况
```java
public class DifferenceDateTest {
@Test
public void test(){
// 获取当前时间
LocalTime now = LocalTime.now();
LocalTime of = LocalTime.of(12, 12, 12);
// 使用Duration计算时间的间隔
Duration duration = Duration.between(of, now);
System.out.println("相差的天数" + duration.toDays());
System.out.println("相差的小时数" + duration.toHours());
System.out.println("相差的分钟数" + duration.toMinutes());
System.out.println("相差的秒数" + duration.toMillis());
// 获取当前的日期
LocalDate date = LocalDate.now();
LocalDate localDate = LocalDate.of(2021, 2, 2);
// 使用Period计算日期的间隔
Period period = Period.between(localDate, date);
System.out.println("相差的年" + period.getYears());
System.out.println("相差的月" + period.getMonths());
System.out.println("相差的日" + period.getDays());
}
}
```
### 2.6 时间校正器
- 有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。
- TemporalAdjuster : 时间校正器。
- TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
```java
public class Demo{
@Test
public void temporalAdjuster(){
LocalDateTime now = LocalDateTime.now();
// 自定义时间调整器
LocalDateTime with = now.with(temporal -> {
LocalDateTime dateTime = (LocalDateTime) temporal;
// 修改到下个月1号
return dateTime.plusMonths(1).withDayOfMonth(1);
});
System.out.println("with = " + with);
}
}
```
### 2.7 设置日期时间的时区
- Java8 中加入了对时区的支持LocalDate、LocalTime、LocalDateTime是不带时区的带时区的日期时间类分别为ZonedDate、ZonedTime、ZonedDateTime。
- 其中每个时区都对应着IDID的格式为 “区域/城市” 。例如 Asia/Shanghai 等。
- ZoneId该类中包含了所有的时区信息。
```java
public class Demo{
@Test
public void test(){
// 获取所有的时区ID
ZoneId.getAvailableZoneIds().forEach(System.out::println);
// 不带时区获取计算机的当前时间
// 中国默认时区东八区, 比标准时间快8h
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);
// 操作带时区的类
// 创建世界标准时间, 不带参数则使用计算机默认的时区
ZonedDateTime zonedDateTime = ZonedDateTime.now(Clock.systemUTC());
System.out.println("zonedDateTime = " + zonedDateTime);
System.out.println("指定时区获取时间: " + ZonedDateTime.now(ZoneId.of("Europe/London")));
}
}
```
### 2.8 小结
- 详细学习了新的日期是时间相关类LocalDate表示日期,包含年月日,LocalTime表示时间,包含时分 秒,`LocalDateTime = LocalDate + LocalTime`,时间的格式化和解析,通过DateTimeFormatter类型进行.
- 学习了Instant类,方便操作秒和纳秒,一般是给程序使用的.学习Duration/Period计算日期或时间的距离,还使用时间调整器方便的调整时间,学习了带时区的3个类ZoneDate/ZoneTime/ZoneDateTime
- JDK 8新的日期和时间API的优势
1. 新版的日期和时间API中日期和时间对象是不可变的。操纵的日期不会影响老值而是新生成一个实例。
2. 新的API提供了两种不同的时间表示方式有效地区分了人和机器的不同需求。
3. TemporalAdjuster可以更精确的操纵日期还可以自定义日期调整器。
4. 是线程安全的
## 3 重复注解与类型注释
### 3.1 重复注解的使用
- 自从Java 5中引入注解以来注解开始变得非常流行并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念允许在同一个地方多次使用同一个注解。在JDK 8中使用`@Repeatable`注解定义重复注解。
- 重复注解的使用步骤:
1. 定义重复的注解容器注解
2. 定义一个可以重复的注解
3. 配置多个重复的注解
4. 解析得到指定注解
```java
/** 3. 配置多个重复的注解 **/
@MyTest("AAA")
@MyTest("BBB")
@MyTest("CCC")
public class RepeatableTest {
/** 3. 配置多个重复的注解 **/
@Test
@MyTest("DD")
@MyTest("EE")
public void test(){}
public static void main(String[] args) throws NoSuchMethodException {
// 4. 解析得到指定注解
// getAnnotationsByType是新增API用来获取重复注解的
// 获取类上的注解
MyTest[] annotation = RepeatableTest.class.getAnnotationsByType(MyTest.class);
for (MyTest myTest : annotation) {
System.out.println("myTest = " + myTest.value());
}
// 获取方法上的注解
MyTest[] tests = RepeatableTest.class.getMethod("test").getAnnotationsByType(MyTest.class);
for (MyTest myTest : tests) {
System.out.println("myTest = " + myTest.value());
}
}
}
/**
* 1. 定义重复的注解容器注解
*/
@Retention(RetentionPolicy.RUNTIME)
@interface MyTests{
MyTest[] value();
}
/**
* 2. 定义一个可以重复的注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest{
String value();
}
```
### 3.2 类型注解的使用
- JDK 8为@Target元注解新增了两种类型TYPE_PARAMETERTYPE_USE 。
- TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。
- TYPE_USE :表示注解可以再任何用到类型的地方使用。
```java
public class TypeAnnotationDemo<@TypeParam T> {
private @NotNull int one = 10;
public <@TypeParam E extends Integer> void test(@NotNull E ele){}
public static void main(String[] args) {
@NotNull int x = 2;
@NotNull String str = new @NotNull String();
}
}
/**
* 该注解能用到类型前面
*/
@Target(ElementType.TYPE_USE)
@interface NotNull {}
/**
* 该注解能写在泛型上
*/
@Target(ElementType.TYPE_PARAMETER)
@interface TypeParam {}
```