1.HashMap和HashSet
如果说当某一个链表的大小大于8,总容量大于64,那么就会把链表转换成红黑树结构(二叉树的一种),除了添加以外其他的效率都比链表快,所以现在的结构是数组加链表加红黑树
2.ConcurrentHashMap<K,V>
使用大量的cas算法,且取消原来的16段机制,采用了链表和红黑树
3.方法区
原来称为非堆(但是占用jvm空间),现在直接分离出来了叫做元空间,直接占用物理内存。
4.lambda表达式
函数式接口:只包含一个抽象方法的接口,称为函数式接口,使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口。
适用前提条件:必须是一个函数式接口
定义:lambda表达式表示的是一个匿名的函数,Lambda 操作符“->”左侧:指定了Lambda 表达式需要的所有参数,右侧:指定了Lambda 体(匿名函数方法的方法体),即Lambda 表达式要执行的功能。
左侧:在无参的条件下(),有参只有一个参数的前提下可以写成(x),当然也可以去掉()写成x,多个参数的情况下都需要带()
右侧:无返回值只用一条语句可以不带{},多条语句需要带{},有返回值只有一条语句无{}情况下无需写return,多条或者有{}时需要写rreturn
原因:在加载编译的时候会自判断后编译成class字节码文件,在效率上在以前的基础上并没有提升,只是书写较为方便
public class LambdaTest1 { public static void testA(LambdaTest1A la){ la.testA(); } public static void main(String[] args) { testA(()->System.out.println("hello testA")); ArrayList<Integer> list = new ArrayList<>(); list.add(321); list.add(123); list.add(342); list.add(112); //Collections.sort(list,(x,y)->x-y);从小到大 Collections.sort(list,(x,y)->y-x);//从大到小 System.out.println(list); } } interface LambdaTest1A{ void testA(); }
//策略设计模式 public class LambdaTest2 { public static List<LambdaTest2Person> test(List<LambdaTest2Person> list, Predicate<LambdaTest2Person> t) { ArrayList<LambdaTest2Person> list2 = new ArrayList<>(); for (LambdaTest2Person lambdaTest2Person : list) { if (t.test(lambdaTest2Person)) { list2.add(lambdaTest2Person); } } return list2; } public static void main(String[] args) { ArrayList<LambdaTest2Person> list = new ArrayList<>(); list.add(new LambdaTest2Person("张三", 24, 3333)); list.add(new LambdaTest2Person("李四", 34, 8888)); list.add(new LambdaTest2Person("王五", 15, 5555)); list.add(new LambdaTest2Person("赵六", 34, 6666)); //List<LambdaTest2Person> test = test(list, (x) -> x.getAge() > 30); //System.out.println(test);// 年龄大于35岁的 Collections.sort(list, (x, y) -> { if (x.getAge() == y.getAge()) { return x.getSalary() - y.getSalary(); } return x.getAge() - y.getAge(); });// 先按年龄从小到大排列,年龄相同那么按工资从小到大排列 for (LambdaTest2Person lambdaTest2Person : list) { System.out.println(lambdaTest2Person); } } } class LambdaTest2Person { private String name; private int age; private int salary; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } public LambdaTest2Person(String name, int age, int salary) { this.name = name; this.age = age; this.salary = salary; } public LambdaTest2Person() { } @Override public String toString() { return "LambdaTest2Person [name=" + name + ", age=" + age + ", salary=" + salary + "]"; } }
4.java8内置的函数式接口
Consumer<T> void accept(T t)
Supplier<T> T get()
Function<T, R> R apply(T t)
Predicate<T> boolean test(T t);
BiFunction<T,U,R> R apply(T t,U u)
UnaryOperator<T> T apply(T t)
BinaryOperator<T> T apply(T t1,T t2)
BiConsumer<T,U> void accept(T t,U u)
ToIntFunction<T> 参数T 返回值 int
IntFunction<R> 参数int 返回R
public class LambdaTest3 { public static void main(String[] args) { Consumer<String> cm=(x)->System.out.println(x); cm.accept("consumer"); Supplier<String> sp=()->"Supplier"; System.out.println(sp.get()); Function<String, Integer> fu= x-> Integer.valueOf(x); System.out.println(fu.apply("12")); Predicate<Integer> pr=(x)-> x>0; System.out.println(pr.test(5)); BiFunction<Integer,Integer, Integer> bi=(x,y)->x+y; System.out.println(bi.apply(2, 3)); } }
5.方法引用与构造器引用及数组引用
方法引用
对象::实例方法 ; 类::静态方法 ;类::实例方法 ; 构造器引用 ; 类名 :: new ; 数组引用 ; 类型[] :: new
public class LambdaTest4 { public static void main(String[] args) { //自我推测是建立在编译的前提下的,编译时一行一行的,例如如下 String [] str={"a","b"};//成立 //str={"a","b"};//编译不通过 //编译时能自我推测 对象::实例方法 Consumer<String> cm=System.out::println; cm.accept("Consumer"); //类::静态方法,编译时一行一行的 BiFunction<Integer,Integer, Integer> bi=Integer::compare; System.out.println(bi.apply(1, 2)); //类名实例方法 Function<LambdaTest2Person, Integer> fu=LambdaTest2Person::getAge; System.out.println(fu.apply(new LambdaTest2Person("张三", 23, 3333))); //类名::new Supplier<LambdaTest2Person> person=LambdaTest2Person::new; //无参构造 LambdaTest2Person lambdaTest2Person = person.get(); System.out.println(lambdaTest2Person); //类型[] :: new Function<Integer, LambdaTest2Person[]> fun2 = LambdaTest2Person[] :: new; LambdaTest2Person[] emps = fun2.apply(20); System.out.println(emps.length); } }
6.stream流
定义:数据渠道,用于操作数据源(集合、数组等)所生成的元素序列
特点:Stream 自己不会存储元素。
Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
也就是说需要结束以后才有结果
public class StreamTest2 { public static void main(String[] args) { //获取流的方式1 ArrayList<String> list = new ArrayList<>(); Stream<String> stream = list.stream(); // 获取流的方式2 String[] strs = new String[10]; Stream<String> stream2 = Arrays.stream(strs); // 获取流的方式3 Stream<String> of = Stream.of("a", "b"); // 创建无线流 // 迭代 static <T> UnaryOperator<T> identity() {return t -> t;},0表示起始值 Stream<Integer> iterate = Stream.iterate(0, (x) -> x - 2); iterate.limit(10).forEach(System.out::println); System.out.println("-----------------"); // 创建无限流 // 生成 //T get(); Stream<Double> generate = Stream.generate(() -> Math.random()); generate.limit(10).forEach(System.out::println); } }
public class StreamTest1 { public static void main(String[] args) { ArrayList<LambdaTest2Person> list = new ArrayList<>(); list.add(new LambdaTest2Person("张三", 24, 3333)); list.add(new LambdaTest2Person("李四", 34, 8888)); list.add(new LambdaTest2Person("王五", 15, 5555)); list.add(new LambdaTest2Person("赵六", 34, 6666)); Stream<LambdaTest2Person> stream = list.stream(); Stream<LambdaTest2Person> stream1 = list.stream(); Stream<LambdaTest2Person> stream2 = list.stream(); Stream<LambdaTest2Person> stream3 = list.stream(); // filter-- boolean test(T t); stream.filter((x)->x.getAge()>25).forEach(System.out::println); System.out.println("-----------------------"); /* * LambdaTest2Person [name=李四, age=34, salary=8888] * LambdaTest2Person [name=赵六, age=34, salary=6666] */ //limit 显示的个数 stream1.filter((x)->x.getAge()>25).limit(1).forEach(System.out::println); //LambdaTest2Person [name=李四, age=34, salary=8888] System.out.println("-----------------------"); //skip跳过几个 stream2.filter((x)->x.getAge()>25).skip(1).forEach(System.out::println); //LambdaTest2Person [name=赵六, age=34, salary=6666] System.out.println("-----------------------"); //distinct()根据hashcode去重,需要重写hashcode,这里就根据相同年龄去重 stream3.filter((x)->x.getAge()>25).distinct().forEach(System.out::println); //LambdaTest2Person [name=李四, age=34, salary=8888] } }
class LambdaTest2Person { private String name; private int age; private int salary; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } public LambdaTest2Person(String name, int age, int salary) { this.name = name; this.age = age; this.salary = salary; } public LambdaTest2Person() { } @Override public String toString() { return "LambdaTest2Person [name=" + name + ", age=" + age + ", salary=" + salary + "]"; } //hashcode根据年龄去重 @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; LambdaTest2Person other = (LambdaTest2Person) obj; if (age != other.age) return false; return true; } }
public class StreamTest2 { public static Stream<Character> toUpper(String str){ ArrayList<Character> list = new ArrayList<>(); for (Character character : str.toUpperCase().toCharArray()) { list.add(character); } return list.stream(); } public static void main(String[] args) { System.out.println("----------"); ArrayList<LambdaTest2Person> list = new ArrayList<>(); list.add(new LambdaTest2Person("张三", 24, 3333)); list.add(new LambdaTest2Person("李四", 34, 8888)); list.add(new LambdaTest2Person("王五", 15, 5555)); list.add(new LambdaTest2Person("赵六", 34, 6666)); // map逐个遍历映射 获取名字 张三 李四 王五 赵六 List<String> collect2 = list.stream().map((x) -> x.getName()).collect(Collectors.toList()); collect2.stream().forEach(System.out::println); System.out.println("----------"); //flatMap与map的区别 //首先是map 返回的是 Stream<Stream<Character>> Stream<Stream<Character>> map = Stream.of("aa","bb").map(StreamTest2::toUpper); //然后是flatMap Stream<Character> Stream<Character> flatMap = Stream.of("aa","bb").flatMap(StreamTest2::toUpper); // sort排序 按年龄排序 王五 张三 李四 赵六 System.out.println("----------"); list.stream().sorted((x, y) -> x.getAge() - y.getAge()).map((x) -> x.getName()).collect(Collectors.toList()) .forEach(System.out::println); //count 统计 2 2 Long collect = Stream.of(1, 2, 3, 4, 5).filter((x) -> x > 3).map((x) -> 1).collect(Collectors.counting()); long count = Stream.of(1, 2, 3, 4, 5).filter((x) -> x > 3).map((x) -> 1).count(); System.out.println(collect); System.out.println(count); System.out.println("----------"); //allMatch 是否所有的都匹配 false boolean allMatch = list.stream().allMatch((x)-> "张三".equals (x.getName())); System.out.println(allMatch); //anyMatch 是否一个匹配 true boolean anyMatch = list.stream().anyMatch((x)-> "张三".equals (x.getName())); System.out.println(anyMatch); //noneMatch 没有匹配 false boolean noneMatch = list.stream().noneMatch((x)-> "张三".equals (x.getName())); System.out.println(noneMatch); System.out.println("----------"); //findFirst() 返回第一个元素 张三 String string = list.stream().map((x)->x.getName()).findFirst().get(); System.out.println(string); //findAny() 返回年龄为34的其中的任何一个人 LambdaTest2Person lambdaTest2Person = list.stream().filter((x)->x.getAge()==34).findAny().get(); System.out.println(lambdaTest2Person); //返回最大值(也有可能是最小值) 这个是根据排序来的 Integer integer = Stream.of(1,3,2,4,10,3,2).max((x,y)->x-y).get(); System.out.println(integer); //返回最小值(也有可能是最大值) 这个是根据排序来的 Integer integer1 = Stream.of(1,3,2,4,10,3,2).max(Integer::compare).get(); System.out.println(integer1); System.out.println("----------"); //reduce 根据所写的逐个计算,下面是求和 21 31 Integer integer2 = Stream.of(1,2,3,4,5,6).reduce((x,y)->x+y).get(); System.out.println(integer2); //10表示起始值 Integer integer3 = Stream.of(1,2,3,4,5,6).reduce(10,(x,y)->x+y); System.out.println(integer3); } }
public class StreamTest4 { public static void main(String[] args) { // collect(Collector c) 这个可以求和,平均值,求最大值,分组,保存到集合等 // 求平均值 Double collect = Stream.of(1, 3, 2, 4, 6).filter((x) -> x > 3).collect(Collectors.averagingInt((x) -> x)); System.out.println(collect); System.out.println("----------"); // 去重1 2 3 4 6 Stream.of(1, 3, 2, 4, 6, 6, 6, 4).collect(Collectors.toSet()).forEach(System.out::println); ; System.out.println("----------"); // 先去重后分组 {大于3=[4, 6], 小于3=[1, 2, 3]} Map<String, List<Integer>> collect2 = Stream.of(1, 3, 2, 4, 6, 6, 6, 4).collect(Collectors.toSet()).stream() .collect(Collectors.groupingBy((x) -> { if (x > 3) { return "大于3"; } else { return "小于3"; } })); System.out.println(collect2); // 多级分组 ArrayList<LambdaTest2Person> list = new ArrayList<>(); list.add(new LambdaTest2Person("张三", 24, 3333)); list.add(new LambdaTest2Person("李四", 34, 8888)); list.add(new LambdaTest2Person("王五", 15, 5555)); list.add(new LambdaTest2Person("赵六", 34, 6666)); Map<String, Map<String, List<LambdaTest2Person>>> collect3 = list.stream().collect(Collectors.groupingBy((x) -> { if (x.getAge() > 25) { return "大于25"; } else { return "小于25"; } }, Collectors.groupingBy((x) -> { if (x.getSalary() > 4000) { return "大于4000"; }else { return "小于4000"; } }))); //{大于25={大于4000=[LambdaTest2Person [name=李四, age=34, salary=8888], LambdaTest2Person [name=赵六, age=34, salary=6666]]},
//小于25={大于4000=[LambdaTest2Person [name=王五, age=15, salary=5555]], 小于4000=[LambdaTest2Person [name=张三, age=24, salary=3333]]}} System.out.println(collect3); } }
7.java8的局部内部类的变更
public static void main(String[] args) { //当局部内部类使用外部的变量是会自动的添加final,在jdk1.7之前需要手动的添加 int i=3;//在jdk1.7之前 这个前面需要添加 final ,但在jdk1.8后会自己添加 // i=5//编译错误 class LocalInnerTest{ public void test(){ System.out.println(i); } } }
8.fork/join框架
fork/join,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行join 汇总.
工作窃取模式,充分利用多核线程;
工作窃取:当一个线程执行完了,别的没有执行完,那么该线程会随机的去别的线程的末尾进行窃取工作任务,前提是多核cpu
//jdk1.7之前的算法 传统的算法 public class ForkJoinCalculate extends RecursiveTask<Long> { public static void main(String[] args) { long start = System.currentTimeMillis(); ForkJoinPool pool = new ForkJoinPool(); ForkJoinTask<Long> task = new ForkJoinCalculate(0L, 10000000000L); long sum = pool.invoke(task); System.out.println(sum); long end = System.currentTimeMillis(); System.out.println("耗费的时间为: " + (end - start)); } private static final long serialVersionUID = 13475679780L; private long start; private long end; private static final long THRESHOLD = 100000L; // 临界值 public ForkJoinCalculate(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; ForkJoinCalculate left = new ForkJoinCalculate(start, middle); left.fork(); // 拆分,并将该子任务压入线程队列 ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end); right.fork(); return left.join() + right.join(); } } }
public class NewForkJoin { @Test //顺序流,单线程的 //parallel() 与sequential() 在并行流与顺序流之间进行切换。 public void serialStream(){ long asLong = LongStream.rangeClosed(0, 10000000L).reduce(Long::sum).getAsLong(); long sum = LongStream.rangeClosed(0, 10000000L).sum(); System.out.println(asLong); System.out.println(sum); } @Test //并行流 多线程的 public void parallelStream1(){ long sum = LongStream.rangeClosed(0, 10000000L).parallel().sum(); System.out.println(sum); } }
9.optional
目的:尽量的减少空指针异常,属于一个容器类,表示一个值存在或者不存在
public class OptionalTest { public static void main(String[] args) { //Optional<String> of = Optional.of(null);// 创建一个 Optional 实例,也会报异常,指定在这一行 Optional<Object> empty = Optional.empty();//创建一个空的 Optional 实例 System.out.println(empty.toString());//Optional.empty Optional<Object> ofNullable = Optional.ofNullable(null);//若 t 不为 null,创建 Optional 实例,否则创建空实例 Object orElse = Optional.ofNullable(null).orElse("大帅哥");//如果调用对象包含值,返回该值,否则返回t System.out.println(orElse);//大帅哥 boolean present = Optional.ofNullable(null).isPresent();// 判断是否包含值 System.out.println(present);//false Object orElseGet = Optional.ofNullable(null).orElseGet(()->"大帅哥");//orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值 System.out.println(orElseGet);//大帅哥 //map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty() //flatMap(Function mapper):与 map 类似,要求返回值必须是Optional } }
10.接口中的新定义
类优先,接口中都实现,那么就按需加载(需要时指定),在Java 8中,在接口中添加静态方法带来了一个限制 :这些方法不能由实现它的类继承。
public class InterfaceTest extends InterfaceTestC implements InterfaceTestA,InterfaceTestB{ public static void main(String[] args) { InterfaceTest interfaceTest = new InterfaceTest(); interfaceTest.testA();//InterfaceTestC:testA优先使用类中的方法 interfaceTest.testB();//InterfaceTestB---Override--testB,优先使用类中的方法 } @Override public void testB() { System.out.println("InterfaceTestB---Override--testB"); } @Override public void testC() { InterfaceTestA.super.testC();//多个需要重写一个使用的方法,静态方法需要用接口名调用 } } interface InterfaceTestA { static void testA() {// 没有默认会添加public,只能是public的 System.out.println("InterfaceTestA:testA"); } default void testB() {/// 没有默认会添加public,只能是public的 System.out.println("InterfaceTestA:testB"); } default void testC() {// 没有默认会添加public,只能是public的 System.out.println("InterfaceTestA:testC"); } } interface InterfaceTestB { void testB();// 默认添加public abstract default void testC() {// 没有默认会添加public,只能是public的 System.out.println("InterfaceTestB:testC"); } } class InterfaceTestC { void testA() {// 没有默认会添加public,只能是public的 System.out.println("InterfaceTestC:testA"); } }
11.重复注解
java8后可以重复注解@Repeatable
public class AnnotationTest { @MyAnnotation("a") // @Repeatable没有这个之前,不能按如下这么写 @MyAnnotation("b") public void test() { } public static void main(String[] args) throws NoSuchMethodException, SecurityException {// 获取方法上的注解 Class<? extends AnnotationTest> class1 = new AnnotationTest().getClass(); Method method = class1.getMethod("test"); // MyAnnotation annotation = // method.getAnnotation(MyAnnotation.class);//只有一次注解的时候能这么做,重复注解的则不行null MyAnnotation[] annotationsByType = method.getAnnotationsByType(MyAnnotation.class);// 重复注解可以这样 for (MyAnnotation myAnnotation : annotationsByType) { System.out.println(myAnnotation.value()); } } } @Repeatable(MyAnnotations.class)//添加该注解,需要一个该注解的容器,例如@interface MyAnnotations MyAnnotation[] value(); @Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation { String value() default "adu"; } @Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotations { MyAnnotation[] value(); }
12.新时间api
java8的时间api先线程安全的,而以前的是线程不安全的
//存在线程安全问题,java.util.concurrent.ExecutionException异常偶尔会出现; public class OldTimeTest { public static void main(String[] args) throws InterruptedException, ExecutionException { SimpleDateFormat sdf = new SimpleDateFormat(); sdf.applyPattern("yyyy-MM-dd"); FutureTask<Date> futureTask = new FutureTask<>(() -> { return sdf.parse("2018-5-5"); }); ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10); Future<?> submit2 = newFixedThreadPool.submit(futureTask); System.out.println(submit2.get());// null Callable<Date> task = new Callable<Date>() { @Override public Date call() throws Exception { return sdf.parse("2018-5-5"); } }; List<Future<Date>> list = new ArrayList<>(); for (int i = 0; i < 9; i++) { list.add(newFixedThreadPool.submit(task)); } newFixedThreadPool.shutdown(); for (Future<Date> future : list) { System.out.println(future.get());// callable的不为空 } } }
old的解决方法
public class ThreadLocalTest { private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd");//每次都产生一个新的DateFormat解决线程安全问题 } }; /** * format:yyyy-MM-dd * * @param str * @throws ParseException * @return */ public static Date getDate(String str) throws ParseException{ return threadLocal.get().parse(str); } }
public class OldTimeTest { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10); Callable<Date> task = new Callable<Date>() { @Override public Date call() throws Exception { return ThreadLocalTest.getDate("2018-5-5"); } }; List<Future<Date>> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(newFixedThreadPool.submit(task)); } newFixedThreadPool.shutdown(); for (Future<Date> future : list) { System.out.println(future.get()); } } }
新的时间api 使用ISO-8601标准
//DateTimeFormatter LocalDate public class NewLocalDateTime { public static void main(String[] args) throws InterruptedException, ExecutionException { DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd"); ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10); Callable<LocalDate> task = new Callable<LocalDate>() { @Override public LocalDate call() throws Exception { return LocalDate.parse("2018-05-05", df);//格式需要完全一致"2018-5-5"就不行 } }; List<Future<LocalDate>> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(newFixedThreadPool.submit(task)) ; } for (Future<LocalDate> future : list) { System.out.println(future.get()); } newFixedThreadPool.shutdown(); } }
//DateTimeFormatter LocalDate public void main(String[] args) throws InterruptedException, ExecutionException { Instant now = Instant.now();//默认使用 UTC 时区 +8h System.out.println(now);//2018-08-05T07:23:22.110Z OffsetDateTime atOffset = now.atOffset(ZoneOffset.ofHours(8)); long l = atOffset.toInstant().toEpochMilli(); System.out.println(atOffset);//2018-08-05T15:23:22.110+08:00 long epochMilli = now.toEpochMilli(); System.out.println(epochMilli);// System.currentTimeMillis() new Date().getTime() System.out.println("----------------"); LocalDateTime localDateTime=LocalDateTime.now();//获取本地时间 DateTimeFormatter df=DateTimeFormatter.ofPattern("yyyy年MM月dd天hh时mm分ss秒"); String format = localDateTime.format(df);// System.out.println(format); System.out.println("----------------"); LocalDateTime parse = LocalDateTime.parse("2006年5月6日", df); Instant oldTime = Instant.now(); Thread.sleep(1000); Instant newTime = Instant.now(); Duration between = Duration.between(oldTime, newTime); System.out.println(between.getSeconds());//1s //Period.between(ld2, ld1); 年月天 时间差 //ZonedDate、ZonedTime、ZonedDateTime : 带时区的时间或日期 //LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); }