1、Lambda
Lambda是一个匿名函数,可以理解为一段可以传递的代码(将代码像数据一样传递);可以写出更简洁、更灵活的代码;作为一种更紧凑的代码风格,是Java语言表达能力得到提升。
1.1 匿名内部类
@Test
public void test01(){
//匿名内部类
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
@Override
public boolean equals(Object obj) {
return false;
}
};
//调用
TreeSet<Integer> set = new TreeSet<>(comparator);
}
1.2 Lambda使用
@Test
public void test02(){
// Lambda 表达式
Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
TreeSet<Integer> set = new TreeSet<>(comparator);
}
演变过程:垃圾代码-->策略模式-->匿名内部类-->Lambda表达式
基础语法:
-操作符:->
-左侧:参数列表
-右侧:执行代码块 / Lambda 体
语法格式1:无参数,无返回值:()->要执行的代码
public class Test02 {
int num = 10; //jdk 1.7以前 必须final修饰,
//1.7之后不用加final但匿名内部类中还是不可以修改同级别局部变量
@Test
public void test01(){
//匿名内部类
Runnabale r =new Runnable() {
@Override
public void run() {
//在局部类中引用同级局部变量
//只读
System.out.println("Hello World" + num);
}
};
}
@Test
public void test02(){
//语法糖
Runnable runnable = () -> {
System.out.println("Hello Lambda");
};
}
}
语法格式2:有一个参数,无返回值:(x)->要执行的代码,左侧小括号可以省略
@Test
public void test03(){
Consumer<String> consumer = (a) -> System.out.println(a);
//括号可以省略Consumer<String> consumer = a -> System.out.println(a);
consumer.accept("我觉得还行!");
}
语法格式3:有两个及以上的参数,左侧小括号不能省略。有返回值,并且 Lambda 体中有多条语句要使用大括号,并return返回值
@Test
public void test04(){
Comparator<Integer> comparator = (a, b) -> {
System.out.println("比较接口");
return Integer.compare(a, b);
};
}
Lambda 表达式 参数的数据类型可以省略不写 Jvm可以自动进行 “类型推断”
1.3 函数式接口
函数式接口:接口中只有一个抽象方法的接口 @FunctionalIterface。@FunctionalInterface可检查接口是否是函数式接口,如果一个接口不只有一个函数,编译就会不通过。
Comparator中有两个方法的疑问:
根据Java语言规范的定义,一个使用了该注释的接口类型声明将被视为一个函数式接口。从概念上讲,一个函数式接口有且只有一个抽象方法。由于默认方法已经有了实现,所以它们不是抽象方法。如果一个接口中声明的抽象方法是重写了超类Object类中任意一个public方法,那么这些抽象方法并不会算入接口的抽象方法数量中。因为任何接口的实现都会从其父类Object或其它地方获得这些方法的实现。
如果一个类型使用了该注释,那么编译器将会生成一个错误信息,除非这个类型是一个接口类型,而不是一个注释类型、枚举或类。同时使用该注释的接口满足函数式接口的要求,即一个函数式接口有且只有一个抽象方法。
但是编译器会将所有定义为函数式接口(满足函数式接口要求)的接口视为函数式接口,而不管这个接口声明中是否使用了函数式接口的注释(即@FunctionalInterface)。
从中我们可以知道:一个函数式接口有且只有一个抽象方法。默认方法不是抽象方法,因为它们已经实现了。重写了超类Object类中任意一个public方法的方法并不算接口中的抽象方法。所以虽然Comparator接口中有两个抽象方法compare和equals,但equals并不算入接口中的抽象方法,所以Comparator接口还是满足函数式接口的要求,Comparator接口是一个函数式接口。
测试:
@FunctionalInterface
public interface MyFun {
Integer getValue(Integer num);
}
@Test
public void test(){
Integer num = operation(100,x->x*x);
System.out.println(num);
}
public Integer opertation(Integer num,MyFun mf){
return mf.getValue(num);
}
2、Java 8 内置的四大函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer<T> 消费型接口 | T | void | 对类型为T的对象应用操作: void accept(T t) |
Supplier<T> 提供型接口 | 无 | T | 返回类型为T的对象:T get() |
Function<T, R> 函数型接口 | T | R | 对类型为T的对象应用操作,并返回结果为R类型的对象:R apply(T t) |
Predicate<T> 断言型接口 | T | boolean | 确定类型为T的对象是否满足某约束,并返回boolean值:boolean test(T t) |
2.1 消费型接口
@Test
public void test01(){
conString("abc",(x)->System.out.println(x));
}
public void conString(String str,Consumer<String> con){
con.accept(str);
}
2.2 提供型接口
@Test
public void test02(){
List<Ingeter> list = getNumList(10,()->(int)(Math.random()*100));
}
public List<Integer> getNumList(int num,Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();
for(int i = 0;i<num;i++){
Integer n = sup.get();
list.add(n);
}
return list;
}
2.3 函数型接口
@Test
public void test03(){
String newStr = strHandler("abc123456xyz",(str)->str.strim());
}
public String strHandler(String str,Function<String,String> fun){
return fun.apply(str);
}
2.4 断言型接口
@Test
public void test04(){
List<String> list = Arrays.asList("aaa","bbbb","cccccc");
List newList = filterStr(list,(str)->str.length()>2);
}
public List<String> filterStr(List<String> list,Predicate<String> pre){
List<String> strlist = new ArrayList<>();
for(String str:list){
if(pre.test(str))
strlist.add(str);
}
return strlist;
}
3、引用
3.1 方法引用
若Lambda表达式体中的内容已有方法实现,则我们可以使用方法引用。
语法格式:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
对象::实例方法
@Test
public void test01(){
PrintStream ps = System.out;
Consumer<String> con1 = (s) -> ps.println(s);
con1.accept("aaa");
//对象::实例方法
Consumer<String> con2 = ps::println;
con2.accept("bbb");
}
注意:Lambda 表达实体中调用方法的参数列表、返回类型必须和函数式接口中抽象方法保持一致
类::静态方法
@Test
public void test02(){
Comparator<Integer> com1 = (x, y) -> Integer.compare(x, y);
System.out.println(com1.compare(1, 2));
//类::静态方法
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(2, 1));
}
类::实例方法
@Test
public void test03(){
BiPredicate<String, String> bp1 = (x, y) -> x.equals(y);
System.out.println(bp1.test("a","b"));
//类::实例方法
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("c","c"));
}
条件:Lambda 参数列表中的第一个参数是方法的调用者,第二个参数是方法的参数时,才能使用 ClassName :: Method。若只有一个参数,调用者也是这个参数也可以使用。
例如(e)->e.getName()可以写成Employee::getName
3.2 构造器引用
语法格式:ClassName :: new
@Test
public void test04(){
Supplier<List> sup1 = () -> new Employee();
//类名::new 若有多个构造函数会根据函数式接口中的参数列表和返回类型进行匹配
//无参构造函数
Supplier<List> sup2 = Employee::new;
Employee emp1 = sup2.get();
//只有一个参数的构造函数
Function<Integer,Employee> fun1 = (x)->new Employee(x);
Function<Integer,Employee> fun2 = Employee::new;
Employee emp2 = fun2.apply(100);
//两个参数的构造函数
BiFunction<Integer,Integer,Employee> bf1= (x,y)->new Employee(x,y);
Function<Integer,Integer,Employee> bf2= Employee::new;
Employee emp3 = bf2.apply(100);
}
注意:需要调用的构造器的参数列表要与函数时接口中抽象方法的参数列表保持一致
3.3 数组引用
语法格式:Type[]:new
public void test(){
Function<Integer,String[]> fun1 = (x)->new String[x];
String[] strs1 = fun1.apply(10);
//数组引用
Function<Integer,String[]> fun2 = String[]::new;
String[] strs2 = fun2.apply(10);
}
4、Stream API
流(Stream)是数据渠道,用于操作数据(集合、数组等)所生成的元素序列。
集合讲的是数据,流讲的是计算。流是带有类型信息的,所以流操作中的Lambda表达式不用写参数类型,会根据流的数据类型进行推断。
注意:
- Stream自己不会存储数据
- Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream操作时延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream操作步骤
- 创建Stream:一个数据源(如集合、数组),获取一个流。
- 中间操作:一个中间操作链,对数据源的数据进行处理。
- 终止操作:一个种植操作,执行中间操作链,并产生结果。
4.1 创建流
/**
* 创建流
*/
@Test
public void test01(){
/**
* 集合流
* - Collection.stream() 串行流
* - Collection.parallelStream() 并行流
*/
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//数组流
//Arrays.stream(array)
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+i++);
stream4.forEach(System.out::println);
//生成
Stream.generate(() -> Math.random())
.limit(5)
.forEach(System.out::println);
}
4.2 中间操作
- filter:接收 Lambda ,从流中排除某些元素
- limit:截断流,使其元素不超过给定数量
- skip(n):跳过元素,返回一个舍弃了前n个元素的流;若流中元素不足n个,则返回一个空流;与 limit(n) 互补
- distinct:筛选,通过流所生成的 hashCode() 与 equals() 去除重复元素
List<Employee> emps = Arrays.asList(
new Employee(101, "Z3", 19, 9999.99),
new Employee(102, "L4", 20, 7777.77),
new Employee(103, "W5", 35, 6666.66),
new Employee(104, "Tom", 44, 1111.11),
new Employee(105, "Jerry", 60, 4444.44)
);
@Test
public void test01(){
emps.stream()
.filter((x) -> x.getAge() > 35)
.limit(3) //短路?达到满足不再内部迭代
.distinct()
.skip(1)
.forEach(System.out::println);
}
Stream的中间操作:多个中间操作连接起来形成一个流水线,除非流水线上出发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为惰性求值。
- 内部迭代:迭代操作由Stream API完成
- 外部迭代:自己通过迭代器完成
4.2.1 映射
- map:接收 Lambda ,将元素转换为其他形式或提取信息;接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
- flatMap:接收一个函数作为参数,将流中每一个值都换成另一个流,然后把所有流重新连接成一个流
map:
@Test
public void test02(){
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
list.stream()
.map((str) -> str.toUpperCase())
.forEach(System.out::println);
emps.stream()
.map(Employee::getName)
.forEach(System.out::println);
}
flatMap:
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (char c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
@Test
public void test03(){
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
//不用flatMap的方法,比较麻烦
Stream<Stream<Character>> stream = list.stream()
.map(TestStream::filterCharacter);
stream.forEach((sm)->sm.forEach(System.out::println))
//使用flatMap,会将多个Stream<Character>流组合成一个流
Stream<Character> sm= list.stream()
.flatMap(TestStream::filterCharacter)
sm.forEach(System.out::println);
}
4.2.2 排序
- sorted()--自然排序(Comparable)
- sorted(Comparator com)--定制排序(Comparator)
@Test
public void test05(){
emps.stream()
.sorted((e1, e2) -> { //compara()
if (e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
} else {
return e1.getAge().compareTo(e2.getAge());
}
})
.forEach(System.out::println);
}
4.3 终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer甚至是void。
4.3.1 查找与匹配
allMatch:检查是否匹配所有元素
anyMatch:检查是否至少匹配一个元素
noneMatch:检查是否没有匹配所有元素
findFirst:返回第一个元素
findAny:返回当前流中的任意元素
count:返回流中元素的总个数
max:返回流中最大值
min:返回流中最小值
public enum Status {
FREE, BUSY, VOCATION;
}
@Test
public void test01(){
//是否所有员工状态都为BUSY
boolean b1 = employees.stream()
.allMatch((e)->e.getStatus().equals(Status.BUSY));
System.out.println(b1);
//是否至少有一个员工状态为BUSY
boolean b2 = employees.stream()
.anyMatch((e)->e.getStatus().equals(Status.BUSY));
//是否没有一个员工状态为BUSY
boolean b3 = employees.stream()
.noneMatch((e)->e.getStatus().equals(Status.BUSY));
//结果有可能为空的结果用Optional封装,防止空指针异常
Optional<Employee> op = employees.stream()
.sorted((e1,e2)->
Double.compare(e1.getSalary(),e2.getSalary()))
.findFirst();
System.out.println(op.get());
Optional<Employee> op2 = employees.stream()
.filter((e)->e.getStatus.equals(Status.FREE))
.findAny();
System.out.println(op2.get());
}
@Test
public void test02(){
Long count = employees.stream()
.count();
//获取工资最高的员工信息
Opitonal<Employee> op1 = employees.stream()
.max((e1,e2)->Double.compare(e1.getSalary(),e2.getSalary()));
System.out.println(op1.get());
//获取工资最低的值
Opitonal<Double> op2 = employees.stream()
.map(Employee::getSalary)
.min(Double::compare);
System.out.println(op2.get());
}
4.3.2 归约和收集
- 归约:reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中的数据反复结合起来,得到一个值。
/**
* Java:
* - reduce:需提供默认值(初始值)
*/
@Test
public void test01(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
//0是初始值,所以结果不会为空,顺序是初始值和列表第一个元素相加,结果再和第二个相加...
Integer integer = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(integer);
employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
}
- 收集:collect 将流转换成其他形式;接收一个 Collector 接口的实现,用于给流中元素做汇总的方法
collect(Collector c):Collector接口方法的实现决定了如何对流执行手机操作(如收集到List、Set、Map)。但是Collectors实用类提供了很多静态方法,可以很方便的创建收集器实例,具体方法如下:
@Test
public void test02(){
//放入List
List<String> list = emps.stream()
.map(Employee::getName)
.collect(Collectors.toList());
list.forEach(System.out::println);
//放入Set
Set<String> set = emps.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
set.forEach(System.out::println);
//放入LinkedHashSet
LinkedHashSet<String> linkedHashSet = emps.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(LinkedHashSet::new));
linkedHashSet.forEach(System.out::println);
}
@Test
//collect
public void test2(){
//总数
Long count = employees.stream()
.collect(Collectors.counting());
System.out.println(count);
//平均值
Double avg = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avg);
//总和
Double sum = employees.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(sum);
//最大值
Optional<Employee> max = employees.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(max.get().getSalary());
//最小值
Optional<Double> min = employees.stream()
.map(Employee::getSalary)
.collect(Collectors.minBy(Double::compare));
System.out.println(min.get());
}
@Test
public void test3(){
//按照状态分组,List默认ArrayList
Map<Status,List<Employee>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(map);
System.out.println(map.get(Status.BUSY).getClass().toString());
//多级分组
Map<Status,Map<String,List<Employee>>> map2 = employees.stream() .collect(Collectors.groupingBy(Employee::getStatus,Collectors.groupingBy(e->{
if(e.getSalary()<50)
return "低工资";
else
return "高工资";
})));
System.out.println(map2);
//分区:满足条件的一个区,不满足的一个区
Map<Boolean,List<Employee>> map3 =employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 80));
System.out.println(map3);
}
@Test
//另一种方式
public void Test4(){
DoubleSummaryStatistics dss = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(dss.getSum());
System.out.println(dss.getMax());
System.out.println(dss.getAverage());
System.out.println(dss.getMin());
}
@Test
public void Test5(){
String allName = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(",","start:"," end"));
System.out.println(allName);
}
4.4 并行流
- 并行流:就是把一个内容分成几个数据块,并用不同的线程分别处理每个数据块的流
- Java 8 中将并行进行了优化,我们可以很容易的对数据进行操作;Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与串行流之间切换
Fork/Join实现累加:
public class ForkJoinCalculate extends RecursiveTask<Long> {
private static final long serialVersionUID = 1234567890L;
private long start;
private long end;
private static final long THRESHPLD = 10000;
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, end);
left.fork(); //拆分子任务 压入线程队列
ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
right.fork();
return left.join() + right.join();
}
return null;
}
}
public class TestForkJoin {
/**
* ForkJoin 框架
*/
@Test
public void test01(){
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinCalculate task = new ForkJoinCalculate(0, 100000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getNano());
}
/**
* 普通 for循环
*/
@Test
public void test02(){
Instant start = Instant.now();
Long sum = 0L;
for (long i = 0; i < 100000000L; i++) {
sum += i;
}
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getNano());
}
}
Java 8 并行流
@Test
public void test03(){
//串行流(单线程):切换为并行流 parallel()
//并行流:切换为串行流 sequential()
LongStream.rangeClosed(0, 100000000L)
.parallel() //底层:ForkJoin
.reduce(0, Long::sum);
}
5、 Optional类
Optional 类 (java.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();
}
}
6、接口中的默认方法和静态方法
public interface MyFun {
default void getName(){
System.out.println("MyFun1");
}
static void staticFun(){
System.out.println("static Fun");
}
}
接口默认方法的类优先原则:
第一种:如果一个类继承了一个父类SubClass和一个接口MyFun,SubClass和MyFun有一个同名方法,这个方法在SubClass中实现,在MyFun中式默认方法,那么子类不需要实现,SubClass会默认覆盖MyFun的方法。
第二种:如果一个类继承了两个接口MyFun1和MyFun2,两个接口实现了同名的默认方法,那么子类继承时必须重写,在里面指定使用MyFun1或者MyFun2的方法,或者完全重写。
7、Date
7.1 安全问题
一、jdk8与之前的日期和时间处理类的不同:
1. Java的java.util.Date和java.util.Calendar类易用性差,不支持时区,并且是可变的,也就意味着他们都不是线程安全的;
2. 用于格式化日期的类DateFormat被放在java.text包中,它是一个抽象类,所以我们需要实例化一个SimpleDateFormat对象来处理日期格式化,并且DateFormat也是非线程安全,这意味着如果你在多线程程序中调用同一个DateFormat对象,会得到意想不到的结果。
3. 对日期的计算方式繁琐,而且容易出错,因为月份是从0开始的,这意味着从Calendar中获取的月份需要加一才能表示当前月
传统的日期格式化:
@Test
public void test01(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Callable<Date> task = () -> sdf.parse("20200517");
ExecutorService pool = Executors.newFixedThreadPool(10);
ArrayList<Future<Date>> result = new ArrayList<>();
for (int i = 0; i < 10; i++) {
result.add(pool.submit(task));
}
for (Future<Date> future : result) {
try {
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
pool.shutdown();
}
加锁:
public class DateFormatThreadLocal {
private static final ThreadLocal<DateFormat> df = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static Date convert(String source) throws ParseException{
return df.get().parse(source);
}
}
@Test
public void test02(){
Callable<Date> task = () -> DateFormatThreadLocal.convert("20200517");
ExecutorService pool = Executors.newFixedThreadPool(10);
ArrayList<Future<Date>> result = new ArrayList<>();
for (int i = 0; i < 10; i++) {
result.add(pool.submit(task));
}
for (Future<Date> future : result) {
try {
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
pool.shutdown();
}
DateTimeFormatter:
@Test
public void test03(){
DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;
Callable<LocalDate> task = () -> LocalDate.parse("20200517",dtf);
ExecutorService pool = Executors.newFixedThreadPool(10);
ArrayList<Future<LocalDate>> result = new ArrayList<>();
for (int i = 0; i < 10; i++) {
result.add(pool.submit(task));
}
for (Future<LocalDate> future : result) {
try {
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
pool.shutdown();
}
7.2 本地时间/日期
@Test
public void test01(){
//获取当前时间日期 now
LocalDateTime ldt1 = LocalDateTime.now();
System.out.println(ldt1);
//指定时间日期 of
LocalDateTime ldt2 = LocalDateTime.of(2020, 05, 17, 16, 24, 33);
System.out.println(ldt2);
//加 plus
LocalDateTime ldt3 = ldt2.plusYears(2);
System.out.println(ldt3);
//减 minus
LocalDateTime ldt4 = ldt2.minusMonths(3);
System.out.println(ldt4);
//获取指定的你年月日时分秒... get
System.out.println(ldt2.getDayOfYear());
System.out.println(ldt2.getHour());
System.out.println(ldt2.getSecond());
}
8.3 时间戳
Instant:以 Unix 元年 1970-01-01 00:00:00 到某个时间之间的毫秒值
@Test
public void test02(){
// 默认获取 UTC 时区 (UTC:世界协调时间)
Instant ins1 = Instant.now();
System.out.println(ins1);
//带偏移量的时间日期 (如:UTC + 8)
OffsetDateTime odt1 = ins1.atOffset(ZoneOffset.ofHours(8));
System.out.println(odt1);
//转换成对应的毫秒值
long milli1 = ins1.toEpochMilli();
System.out.println(milli1);
//构建时间戳
Instant ins2 = Instant.ofEpochSecond(60);
System.out.println(ins2);
}