Lambda

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个 lambda 表达式。这种接口称为函数式接口。
在 java.util.function 包中定义了很多通用的函数式接口。
// 函数式接口可以用 @FunctionalInterface 注解,如果接口中声明了不只一个抽象方法会报错。

java.util.function 下几大常用的接口

Consumer<T> // 消费性接口
    void accept(T t);
Supplier<T> // 供给型接口
    T get();
Function<T, R> // 函数型接口
    R apply(T t);
Predicate<T> // 断言型接口
    boolean test(T t);

语法

public void test1() {
    Runnable runnable = new Runnable() { // Runnable接口中只有一个抽象方法
        @Override
        public void run() { System.out.println("run"); }
    };
    runnable.run();
    
    // 可以简化为下面这种写法
    Runnable runnable = () -> System.out.println("run1"); // 这是lambda表达式
    runnable.run();
}
public void test2() {
    Consumer<String> consumer = new Consumer<String>() { // Consumer是函数式接口
        @Override
        public void accept(String s) { System.out.println(s); }
    };
    consumer.accept("consumer1");

    // 简化一下
    Consumer<String> c = (s) -> System.out.println(s); // 这里的 s 没有任何用处
    c.accept("hello");
    
    // 所以再次简化
    Consumer<String> consumer = System.out::println;
    consumer.accept("consumer2");
}
// interface Hah {
//     void hello(String name);
//     void hi(String you);
// }
public void test3() {
    Hah hah = new Hah() { // Hah中声明了两个抽象方法,所以不可以简化为lambda表达式
        @Override
        public void hello(String name) { System.out.println(name); }
        @Override
        public void hi(String you) { System.out.println(you); }
    };
}

方法引用

对象::实例方法
class Test {
    public void jump() {
        // 注意: System.out::println 方法的返回类型与 Consumer 中的抽象方法的返回类型要可以对接
        // 在这里,即:public void println(String x); 和 void accept(T t); 返回类型都是 void
        Consumer<String> consumer2 = System.out::println; // 方法引用
        consumer2.accept("hello");
    }
}
类::静态方法
public void test2() {
    Integer[] integers = new Integer[]{23, 32, 8, 90};
    Arrays.sort(integers, Integer::compare); // compare 是一个静态方法
    for (Integer i : integers) {
        System.out.println(i);
    }
}
类::实例方法
public void test3() {
    BiPredicate<String, String> b = (x, y) -> x.equals(y);
    BiPredicate<String, String> b2 = String::equals; // 类为什么可以直接调用方法,就这么规定可以的
}

类::实例方法

参考如下

https://blog.csdn.net/weixin_41126303/article/details/81187002

https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

https://stackoverflow.com/questions/32855138/how-does-a-method-reference-to-an-instance-method-of-an-arbitrary-object-of-a-p

你可能会对这种调用感到奇怪,其实这种使用有下面两个限制

第一点:接口方法的参数要比引用方法的参数多一个
第二点:接口方法第一个参数是调用方法所在类或其子类
class Love {
    public boolean is(Love love){ // 实例方法:只有一个参数
        return true;
    }
}

@FunctionalInterface
public interface BiPredicate<T, U> {
    boolean test(T t, U u); // 有两个参数(参数多一个)
}

// 上面俩,返回类型一样,都是boolean
// 调用如下
BiPredicate<Love, Love> b4 = Love::is;
class Love {
    public void say(Integer param1,String param2){
        System.out.println("晴天");
    }
    public static void main(String[] args) {
        World world = Love::say;
        world.good(new MyLove(), 3, ""); // 晴天
    }
}

class MyLove extends Love {} // 子类 MyLove

@FunctionalInterface
interface World {
    void good(MyLove myLove, Integer param1, String param2); // 第一个参数:本类或子类,但不能是父类
}

Comparator 案例分析

@FunctionalInterface
public interface Comparator<T> { 
	int compare(T o1, T o2);
}

class Love { // 只是一个普通类
    public static int you(Love love, Love love1) { // 必须有两个参数
        return 0;
    }
}

class Test {
    public void test1() { // 可以赋值成功
        // 调用方法
        Comparator<Love> hello = Love::you; // 没有传递这两个参数,(我想有点延迟加载的意思)
    }
}

灵感来源于下面的使用

public final class Integer extends Number implements Comparable<Integer> { // 普通的Integer类
	public static int compare(int x, int y) { // Integer::compare ,返回类型是 int
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
    // ... 还用很多其他方法
}

public class Arrays {
    public static <T> void sort(T[] a, Comparator<? super T> c) {}
    // Comparator是一个函数式接口,要2个参数,方法的返回类型是 int
}

class Test {
    public void test1() {
        Integer[] integers = new Integer[]{23, 32, 8, 90};
        Arrays.sort(integers, Integer::compare); // 这里Integer::compare方法有两个参数,但是没传递
    }
}

构造器引用

Function<T, R> // 函数型接口
    R apply(T t);

class Employee {}

public void test() {
    Function<Integer, Employee[]> function = Employee[]::new; // 因为类中只有无参构造器,会调用无参构造器
    Employee[] apply = function.apply(3);
    System.out.println(apply.length);
}
public void test() {
    Function<Integer, Double[]> function = Double[]::new; // 数组也可以这样使用
    Double[] apply = function.apply(3);
    System.out.println(apply.length);
}

变量作用域

// 1、lambda表达式中不能引用可以在外部改变的变量
for (int i = 0; i < 3; i++) {
    ActionListener ac = e -> System.out.println(i); // ERROR: 这里 i 会在外部变化
}

// 2、不能改变捕获的变量,如果改变在并发中不安全
public void coi(boolean life) { // 传入的参数 life
    Consumer<String> consumer = (w) -> {
        life = false; // ERROR: 不能改变传入的参数
    };
}

总结:lambda表达式捕获的变量(如:i life)必须是事实最终变量。
事实最终变量:指这个变量初始化后就不再为它赋新值。
// lambda表达式的体与嵌套块有相同的作用域,所以不可以声明与方法内相同的变量。
// this会使用Person对象的toString()方法,而不是say()实例的方法
class Person {
    public void say() { 
        ActionListener ac = e -> System.out.println(this.toString());
    }
    public String toString(){}
}

Stream API

Stream API 使用的三个步骤

创建 Stream
    一个数据源,如:集合、数组
中间操作
    对数据源的数据进行处理
终止操作
    执行中间操作链,并产生结果

创建Stream

// 创建流
public void createStream() {
    // Java 8 中的 Collection 接口被扩展,提供了两个获取流的方法
    // default Stream<E> parallelStream() // 返回一个并行流
    // default Stream<E> stream() // 返回一个顺序流
    List<String> list = new ArrayList<>(); // public interface List<E> extends Collection<E>
    Stream<String> stream1 = list.stream();

    // 通过Arrays中的静态方法stream()获取数组流
    String[] strings = new String[10];
    Stream<String> stream2 = Arrays.stream(strings);

    // 通过Stream类中的静态方法Stream.of(...)
    Stream<Integer> stream3 = Stream.of(1, 2, 3);

    //无限流,可以一直生成
    //迭代
    Stream<Integer> stream4 = Stream.iterate(0, i -> i + 2);
    stream4.forEach(System.out::println); // 终止操作
    // 创建流
    Stream<Double> generate = Stream.generate(Math::random);
}

中间操作

多个中间操作连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,称为“惰性求值”。

筛选 / 切片

  • filter:接收 Lambda ,从流中排除某些元素
  • limit:截断流,使其元素不超过给定数量
  • skip(n):跳过元素,返回一个舍弃了前n个元素的流;若流中元素不足n个,则返回一个空流;与 limit(n) 互补
  • distinct:筛选,通过流所生成的 hashCode() 与 equals() 取除重复元素
enum Status {
    FREE, BUSY, VOCATION;
}
List<Employee> employees = Arrays.asList(
    new Employee("张三", 12, 333.33, Status.FREE),
    new Employee("李四", 89, 444.44, Status.BUSY),
    new Employee("王五", 34, 555.55, Status.VOCATION),
    new Employee("赵六", 54, 666.66, Status.FREE),
    new Employee("田七", 66, 777.77, Status.BUSY)
);
@Test
public void test01(){
    employees.stream()
        	 .filter(x -> x.getAge() > 50)
      		 .limit(3) //短路?达到满足不再内部迭代
       		 .distinct() // 去重,hashCode() 与 equals()
      		 .skip(1) // 舍弃满足条件的前 n 个元素
      		 .forEach(System.out::println);
}
@Test
public void test3() {
    employees.stream()
        .filter(employee -> {
            int i = 0;
            System.out.println("短路" + ++i);
            return employee.getAge() > 50;
        })
        .limit(2) // 迭代操作只执行了 4 次,打印出满足条件的之后就不再迭代了
        .forEach(System.out::println);
  /*短路1
    短路1
    Employee{name='李四', age=89, salary=444.44}
    短路1
    短路1
    Employee{name='赵六', age=54, salary=666.66}*/
}

映射

  • map:接收 Lambda ,将元素转换为其他形式或提取信息;接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
  • flatMap:接收一个函数作为参数,将流中每一个值都换成另一个流,然后把所有流重新连接成一个流
public void test4() { // map
    List<String> list = Arrays.asList("aa", "ab", "ac");
    list.stream()
        .map(String::toUpperCase)
        .forEach(System.out::println);
}
public static Stream<Character> getChar(String s) {
    List<Character> list = new ArrayList<>();
    for (Character c : s.toCharArray()) {
        list.add(c);
    }
    return list.stream();
}
@Test
public void test5() {
    List<String> list = Arrays.asList("aa", "ab", "ac");
    // Stream<String> stream = list.stream();
    // Stream<Character> getC = getChar("");
    Stream<Stream<Character>> streamStream = list.stream()
        .map(StreamAPI::getChar); // StreamAPI是getChar所在的类名
    streamStream.forEach(s -> s.forEach(System.out::println));
    // System.out.println("-----------------------------------------------");
    list.stream()
        .flatMap(StreamAPI::getChar) // flatMap,直接转化为了最终结果
        .forEach(System.out::println);
}

排序

  • sorted():自然排序 ( Comparable )
  • sorted(Comparator c):定制排序 ( Comparator )
public void test(){
    List<Integer> list = Arrays.asList(1,2,3,4,5);
    list.stream()
        .sorted()
        .forEach(System.out::println);
}
public void test(){
    employees.stream()
             .sorted((e1, e2) -> {
                 if (e1.getAge().equals(e2.getAge())){
                     return e1.getName().compareTo(e2.getName());
                 } else {
                     return e1.getAge().compareTo(e2.getAge());
                 }
             })
        	 .forEach(System.out::println);
}

终止操作

查找 / 匹配

  • allMatch:检查是否匹配所有元素
  • anyMatch:检查是否至少匹配一个元素
  • noneMatch:检查是否没有匹配所有元素
  • findFirst:返回第一个元素
  • findAny:返回当前流中的任意元素
  • count:返回流中元素的总个数
  • max:返回流中最大值
  • min:返回流中最小值
public void test6() {
    Boolean b1 = emps.stream()
        .allMatch(employee -> employee.getStatus() == Status.BUSY);
    System.out.println(b1);

    Boolean b2 = emps.stream()
        .anyMatch(employee -> employee.getStatus() == Status.BUSY);
    System.out.println(b2);

    Boolean b3 = emps.stream()
        .noneMatch(employee -> employee.getStatus() == Status.BUSY);
    System.out.println(b3);

    // Java 8 为了尽可能避免空指针异常,把对象封装到容器里,就可以使用容器中的方法避免问题
    Optional<Employee> first = emps.stream()
        .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
        .findFirst();
    System.out.println(first.get());

    Optional<Employee> any = emps.stream() // .parallelStream() // 启用多线程查找
        .filter(e -> e.getStatus() == Status.FREE)
        .findAny(); // 随便获取一个 Status.FREE
    System.out.println(any.get());

    long count = emps.stream()
        .count();
    System.out.println(count); // 获取数量

    Optional<Employee> max = emps.stream()
        .max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
    System.out.println(max.get()); // 获取最大工资

    Optional<Double> min = emps.stream()
        .map(Employee::getSalary)
        .min(Double::compare); 
    System.out.println(min.get()); // 获取最小的工资
    
    // 获取每个字符串然后计数
    // Complaint::getMatter 获取一个一个的字符串
    Map<String, Long> map = list.stream().map(Complaint::getMatter)
                    .collect(Collectors.groupingBy(String::toString, Collectors.counting()));
}

归约 / 收集

  • 归约:reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中的数据反复结合起来,得到一个值
  • 收集:collect 将流转换成其他形式;接收一个 Collector 接口的实现,用于给流中元素做汇总的方法
public void test7() {
    List<Integer> list = Arrays.asList(2, 3, 6, 8, 9);
    Integer reduce = list.stream()
        .reduce(100, (x, y) -> x + y); // 把 100和所有数相加
    System.out.println(reduce);

    Optional<Double> reduce1 = emps.stream()  // map-reduce
        .map(Employee::getSalary) // map就是收集其中的某些信息,如工资
        .reduce(Double::sum); // reduce就是把收集到的信息进行处理,如相加
    System.out.println(reduce1.get());

    List<String> collect = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toList()); // 对得到的信息进行加工
    collect.forEach(System.out::println);
    System.out.println("----");

    Set<String> collect1 = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toSet());
    collect1.forEach(System.out::println);
    System.out.println("----");

    HashSet<String> collect2 = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toCollection(HashSet::new)); // 比如收集到自定义集合
    collect2.forEach(System.out::println);
}

public void test8() {
    // 对象总和
    Long sum = emps.stream()
        .collect(Collectors.counting());
    System.out.println(sum);
    // 工资平均
    Double avg = emps.stream()
        .collect(Collectors.averagingDouble(Employee::getSalary));
    System.out.println(avg);
    // 工资总和
    Double sumSalary = emps.stream()
        .collect(Collectors.summingDouble(Employee::getSalary));
    System.out.println(sumSalary);
    // 分组
    Map<Status, List<Employee>> group = emps.stream()
        .collect(Collectors.groupingBy(Employee::getStatus));
    System.out.println(group); // Map没有ForEach
    // 多级分组
    Map<Status, Map<String, List<Employee>>> mapMap = emps.stream()
        .collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {
            if (e.getAge() <= 35)
                return "青年";
            else if (e.getAge() <= 60)
                return "中年";
            return "老年";
        })));
    System.out.println(mapMap);
    // 分区
    Map<Boolean, List<Employee>> partition = emps.stream()
        .collect(Collectors.partitioningBy(employee -> employee.getSalary() > 500));
    System.out.println(partition);
}

并行流与顺序流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流

Java 8 中将并行流进行了优化,我们可以很容易地对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。

Fork / Join 框架

Fork / Join框架:就是在必要地时候,将一个大任务进行拆分 (fork) 成若干个小任务(拆到不可再拆时),再将小任务的结果进行 join 汇总。

image-20210130121830715

Fork/Join框架与传统线程池的区别
采用“工作窃取“模式(work-stealing) :当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
相对于一般的线程池实现, fork / join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork / join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能。

public class ForkJoinCalculate extends RecursiveTask<Long> {

    private final long start;
    private final long end;

    private static final long THRESHPLD = 10_000L;

    public ForkJoinCalculate(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {

        long length = end - start;

        if (length <= THRESHPLD) {
            long sum = 0;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            long middle = (start + end) / 2;

            ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
            left.fork(); //拆分子任务 压入线程队列

            ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
            right.fork();

            return left.join() + right.join();
        }
    }
}

class ForkJoin {
    // 1、普通的处理方法
    @Test
    public void test2() {
        Instant start = Instant.now();
        long sum = 0;
        for (long i = 0; i < 10_000_000_000L; i++) {
            sum += i;
        }
        Instant end = Instant.now();
        System.out.println(Duration.between(start, end).toMillis());
    }
    // 2、ForkJoin 框架
    @Test
    public void test1() {
        Instant start = Instant.now();

        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinCalculate(0, 10_000_000_000L);

        Long sum = pool.invoke(task);
        System.out.println(sum);

        Instant end = Instant.now();
        System.out.println(Duration.between(start, end).toMillis());
    }
    // 3、Java 8 并行流
    @Test
    public void test3() {
        Instant start = Instant.now();
        //串行流(单线程):切换为并行流 parallel()
        //并行流:切换为串行流 sequential()
        LongStream.rangeClosed(0, 10_000_000_000L)
                .parallel() //底层:ForkJoin
                .reduce(0, Long::sum);
        Instant end = Instant.now();
        System.out.println(Duration.between(start, end).toMillis());
    }
}

Optional 类

定义:Optionaljava.util.Optional 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在用 Optional 可以更好的表达这个概念;并且可以避免空指针异常

常用方法:

  • Optional.of(T t):创建一个 Optional 实例
  • Optional.empty(T t):创建一个空的 Optional 实例
  • Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则空实例
  • isPresent():判断是否包含某值
  • orElse(T t):如果调用对象包含值,返回该值,否则返回 t
  • orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回 s 获取的值
  • map(Function f):如果有值对其处理,并返回处理后的 Optional,否则返回 Optional.empty()
  • flatmap(Function mapper):与 map 相似,要求返回值必须是 Optional

Optional.of(T t)

@Test
public void test01(){
    Optional<Employee> op = Optional.of(new Employee());
    Employee employee = op.get();
}

Optional.empty(T t)

@Test
public void test02(){
    Optional<Employee> op = Optional.empty();
    Employee employee = op.get();
}

Optional.ofNullable(T t)

@Test
public void test03(){
    Optional<Employee> op = Optional.ofNullable(new Employee());
    Employee employee = op.get();
}

isPresent()

@Test
public void test03(){
    Optional<Employee> op = Optional.ofNullable(new Employee());
    if (op.isPresent()) {
        Employee employee = op.get();
    }
}

Collector

public interface Collector<T, A, R> { ... }
public final class Collectors { ... }

日期、时间

旧的日期时间

import org.junit.jupiter.api.Test;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

class OldDate {
    
    private Callable<Date> task;
    // 存在线程安全问题
    public void haveProblem() throws ExecutionException, InterruptedException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        task = new Callable<Date>() { // 这里可以转为 Lambda表达式
            @Override
            public Date call() throws Exception {
                return sdf.parse("20030623");
            }
        };
        oldDate();
    }
    // 用锁
    public void solveProblem() throws ExecutionException, InterruptedException {
        task = () -> { // lambda 表达式怎么抛异常
            return DateFormatThreadLocal.covert("20030623");
        };
        oldDate();
    }
    // 公共函数
    public void oldDate() throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        List<Future<Date>> results = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            results.add(pool.submit(task));
        }
        for (Future<Date> future : results) {
            System.out.println(future.get());
        }
        pool.shutdown();
    }
}

class DateFormatThreadLocal { // 多线程异常,用锁
    private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyyMMdd");
        }
    };
    public static Date covert(String source) throws ParseException {
        return df.get().parse(source);
    }
}

java 8 的日期时间

java.time 包下

本地日期

使用 LocalDate LocalTime LocalDateTime
这些类的实例是不可变的对象
@Test
public void test1() {
    //获取当前时间日期 now
    LocalDateTime ldt1 = LocalDateTime.now();
    System.out.println(ldt1); // 2021-03-31T17:02:47.289

    //指定时间日期 of
    LocalDateTime ldt2 = LocalDateTime.of(2020, 5, 17, 16, 24, 33);
    System.out.println(ldt2); // 2020-05-17T16:24:33

    //加 plus
    LocalDateTime ldt3 = ldt2.plusYears(2);
    System.out.println(ldt3); // 2022-05-17T16:24:33

    //减 minus
    LocalDateTime ldt4 = ldt2.minusMonths(3);
    System.out.println(ldt4); // 2020-02-17T16:24:33

    //获取指定的你年月日时分秒... get
    System.out.println(ldt2.getDayOfYear()); // 138
    System.out.println(ldt2.getHour()); // 16
    System.out.println(ldt2.getSecond()); // 33
}

@Test
public void test02(){
    // 默认获取 UTC 时区 (UTC:世界协调时间)
    Instant ins1 = Instant.now();
    System.out.println(ins1); // 2021-01-30T07:50:48.451Z

    //带偏移量的时间日期 (如:UTC + 8)
    OffsetDateTime odt1 = ins1.atOffset(ZoneOffset.ofHours(8));
    System.out.println(odt1); // 2021-01-30T15:50:48.451+08:00

    //转换成对应的毫秒值
    long milli1 = ins1.toEpochMilli();
    System.out.println(milli1); // 1611993048451

    //构建时间戳
    Instant ins2 = Instant.ofEpochSecond(1000); // 就是计算机时间加 1000秒
    System.out.println(ins2); // 1970-01-01T00:16:40Z
}


  • Duration:计算两个时间之间的间隔
  • Period:计算两个日期之间的间隔
@Test // 时间间隔
public void test03() throws InterruptedException {
    //计算两个时间之间的间隔 between
    Instant ins1 = Instant.now();
    Thread.sleep(1000); // 毫秒
    Instant ins2 = Instant.now();
    Duration dura1 = Duration.between(ins1, ins2);
    System.out.println(dura1.getSeconds()); // 1
    System.out.println(dura1.toMillis()); // 1003
}

@Test // 日期间隔
public void test04(){
    LocalDate ld1 = LocalDate.of(2016, 9, 1);
    LocalDate ld2 = LocalDate.now();
    Period period = Period.between(ld1, ld2);  // ISO 标准
    System.out.println(period.getYears()); // 4
    System.out.println(period.toTotalMonths()); // 54
}

时间校正器

TemporalAdjuster:时间校正器。如:调整到”下个周日“等
TemporalAdjusters:它通过静态方法提供了大量的常用 TemporalAdjuster的实现。
@Test
public void test01(){
    LocalDateTime ldt1 = LocalDateTime.now(); // 获取现在的时间
    System.out.println(ldt1); // 2021-03-31T17:12:27.646

    // 指定日期时间中的 年 月 日 ...
    LocalDateTime ldt2 = ldt1.withDayOfMonth(10);
    System.out.println(ldt2); // 2021-03-10T17:12:27.646

    // 指定时间校正器
    LocalDateTime ldt3 = ldt1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)); // 下个星期日
    System.out.println(ldt3); // 2021-04-04T17:12:27.646

    // 自定义时间校正器
    LocalDateTime ldt5 = ldt1.with((ta) -> {
        LocalDateTime ldt4 = (LocalDateTime) ta;
        DayOfWeek dow1 = ldt4.getDayOfWeek(); // 获取现在是星期几
        if (dow1.equals(DayOfWeek.FRIDAY)) {
            return ldt4.plusDays(3);
        } else if (dow1.equals(DayOfWeek.SATURDAY)) {
            return ldt4.plusDays(2);
        } else {
            return ldt4.plusDays(1);
        }
    });
    System.out.println(ldt5); // 2021-04-01T17:12:27.646
}

时间格式化

@Test
public void test9(){
    // 默认格式化
    DateTimeFormatter dtf1 = DateTimeFormatter.ISO_DATE_TIME;
    LocalDateTime ldt1 = LocalDateTime.now();
    String str1 = ldt1.format(dtf1);
    System.out.println(str1); // 2021-03-31T17:29:36.498
    
    // 解析
    LocalDateTime newDate = LocalDateTime.parse(str1, dtf1);
    System.out.println(newDate); // 2021-03-31T17:29:36.498

    // 自定义格式化 ofPattern
    DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    LocalDateTime ldt2 = LocalDateTime.now();
    String str2 = ldt2.format(dtf2);
    System.out.println(str2); // 2021-03-31 17:29:36

    // 向人类表示的时间
    DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
    ZonedDateTime zonedDateTime = ZonedDateTime.now(); // 时区时间
    String format = formatter.format(zonedDateTime);
    System.out.println(format); // // 21-3-31 下午5:44
	// FormatStyle.FULL  -->  2021年3月31日 星期三 下午05时43分08秒 CST
}

时区时间

API 设计者们不推荐使用时区时间

  • ZonedDate
  • ZonedTime
  • ZonedDateTime
@Test
public void test02(){
    //查看支持的时区
    Set<String> set = ZoneId.getAvailableZoneIds();
    set.forEach(System.out::println);

    //指定时区
    LocalDateTime ldt1 = LocalDateTime.now(ZoneId.of("Europe/Tallinn")); // Europe/Tallinn 叫做时区ID
    System.out.println(ldt1); // 2021-03-31T11:40:32.454
    ldt1 = LocalDateTime.now(ZoneId.of("Asia/Chongqing"));
    System.out.println(ldt1); // 2021-03-31T16:40:32.454
    System.out.println(set.size()); // 601个时区ID

    //在已构建好的日期时间上指定时区
    LocalDateTime ldt2 = LocalDateTime.now(ZoneId.of("Europe/Tallinn"));
    ZonedDateTime zdt1 = ldt2.atZone(ZoneId.of("Europe/Tallinn"));
    System.out.println(zdt1);
}
@Test
public void test03(){
    // Date 转 LocalDateTime
    Date date = new Date();
    Instant instant = date.toInstant();
    ZoneId zoneId = ZoneId.systemDefault();
    LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDateTime();

    // LocalDateTime 转 Date
    LocalDateTime localDateTime = LocalDateTime.now();
    ZoneId zoneId = ZoneId.systemDefault();
    ZonedDateTime zdt = localDateTime.atZone(zoneId);
    Date date = Date.from(zdt.toInstant());

    // 原则:利用 时间戳Instant
}

注解

重复注解与类型注解