Java8学习最全教程
2020-10-13 16:52:05修改,丰富Arrays.asList内容
Java8新特性简介
说明:本文全面讲解java8新特性,有任何问题欢迎留言
- 注意点:HashMap:采用Hash算法求索引 数组+链表 Java8:数组+链表+红黑树
红黑树:除了添加之外其它效率更高
其它知识点
Objects
Objects.requireNonNull(Object object);
检查对象是否为空,若为空则抛出NullPointerException
Arrays.asList
/**
*
* 本类演示了Arrays类中的asList方法
* 通过四个段落来演示,体现出了该方法的相关特性.
*
* (1) 该方法对于基本数据类型的数组支持并不好,当数组是基本数据类型时不建议使用
* (2) 当使用asList()方法时,数组就和列表链接在一起了.
* 当更新其中之一时,另一个将自动获得更新。
* 注意:仅仅针对对象数组类型,基本数据类型数组不具备该特性
* (3) asList得到的数组是的没有add和remove方法的
*
* 阅读相关:通过查看Arrays类的源码可以知道,asList返回的List是Array中的实现的
* 内部类,而该类并没有定义add和remove方法.另外,为什么修改其中一个,另一个也自动
* 获得更新了,因为asList获得List实际引用的就是数组
*/
String[] s = {"aa","bb","cc"};
List<String> strlist = Arrays.asList(s);
for(String str:strlist){
System.out.println(str);
}
System.out.println("------------------------");
//基本数据类型结果打印为一个元素
int[] i ={11,22,33};
List intlist = Arrays.asList(i);
for(Object o:intlist){
System.out.println(o.toString());
}
System.out.println("------------------------");
Integer[] ob = {11,22,33};
List<Integer> oblist = Arrays.asList(ob);
for(int a:oblist){
System.out.println(a);
}
System.out.println("------------------------");
注:扩展阅读
Arrays.asList常见错误使用及修改建议
Lambda表达式
引入了新的操作符 “->” 该操作符把语句分为两步 参数列表+语句块
语法格式一:无参+无返回值 ()-> System.out.println();
二:有参+无返回值 (param)-> Systm.out.pringln(param); 小括号可以不写
三:多参+有返回值
Comparator com = (x,y)->{
System.out.println(“lambda”);
return Integer.compare(x,y);
};
四:多参+有返回值(一条语句) {}和return均可省略
五:数据类型可以省略,因为JVM可以通过上下文推断数据类型
lambda需要函数式接口的支持 接口只有一个抽象方法的接口 称为函数式接口 @FunctionalInterface
重点:只有一个抽象方法(静态方法,默认实现的方法,Object类的public方法,均不是抽象方法)
JAVA8接口中有多个方法
- 默认方法:default修饰的实现类方法
- 静态方法:static修饰
- Object的public方法
- 抽象方法(不加任何修饰符)
一个变量
- public static final 常量
什么是函数式接口(Functional Interface)
其实之前在讲Lambda表达式的时候提到过,所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。
这种类型的接口也称为SAM接口,即Single Abstract Method interfaces。
函数式接口用途
它们主要用在Lambda表达式和方法引用(实际上也可认为是Lambda表达式)上。
如定义了一个函数式接口如下:
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}
那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
GreetingService greetService1 = message -> System.out.println("Hello " + message);
关于@FunctionalInterface注解
Java 8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。
正确例子,没有报错:
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
}
错误例子,接口中包含了两个抽象方法,违反了函数式接口的定义,Eclipse报错提示其不是函数式接口。
Java 8 函数式接口 - Functional Interface
提醒:加不加@FunctionalInterface对于接口是不是函数式接口没有影响,该注解知识提醒编译器去检查该接口是否仅包含一个抽象方法
函数式接口里允许定义默认方法
函数式接口里是可以包含默认方法,因为默认方法不是抽象方法,其有一个默认实现,所以是符合函数式接口的定义的;
如下代码不会报错:
复制代码
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
default void doSomeMoreWork1()
{
// Method body
}
default void doSomeMoreWork2()
{
// Method body
}
}
复制代码
函数式接口里允许定义静态方法
函数式接口里是可以包含静态方法,因为静态方法不能是抽象方法,是一个已经实现了的方法,所以是符合函数式接口的定义的;
如下代码不会报错:
复制代码
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
static void printHello(){
System.out.println("Hello");
}
}
复制代码
函数式接口里允许定义java.lang.Object里的public方法
函数式接口里是可以包含Object里的public方法,这些方法对于函数式接口来说,不被当成是抽象方法(虽然它们是抽象方法);因为任何一个函数式接口的实现,默认都继承了Object类,包含了来自java.lang.Object里对这些抽象方法的实现;
如下代码不会报错:
复制代码
@FunctionalInterface
interface GreetingService
{
void sayMessage(String message);
@Override
boolean equals(Object obj);
}
复制代码
类型推断在8之前就已经存在
lambda应用
// 1. 先按照年龄排序,再按照薪水排序
public class AES {
public static void main(String[] args) {
Employee e1 = new Employee(43, 60000);
Employee e2 = new Employee(43, 50000);
Employee e3 = new Employee(54, 120000);
Employee e4 = new Employee(12, 220000);
Employee e5 = new Employee(67, 40000);
Employee e6 = new Employee(43, 70000);
List<Employee> list = Arrays.asList(e1, e2, e3, e4, e5, e6); //此list无add,remove方法,为Arrays内部定义的List匿名类
Collections.sort(list, (o1, o2) -> {//初次使用lambda表达式
return (Integer.compare(o1.age, o2.age) == 0) ?
(Integer.compare(o1.salary, o2.salary)) : Integer.compare(o1.age, o2.age);
});
list.forEach(o1 -> {//第二次使用lambda
System.out.println(o1.toString());
}
}
}
//输出
Employee(age=12, salary=220000)
Employee(age=43, salary=50000)
Employee(age=43, salary=60000)
Employee(age=43, salary=70000)
Employee(age=54, salary=120000)
Employee(age=67, salary=40000)
//Collections.sort()源码
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
Lambda取代的是
new Consumer<Employee>() {
@Override
public void accept(Employee employee) {
}
}
// 2 自定义一个函数式接口 含 String getValue(String str);
//写一个TestLambda A:实现字符串转大写 B:字符串截取 提示:应该先在类中定义一个实现了getValue的方法
public class TestLambda {
public static void main(String[] args) {
String st = "welcome to my blog,jian";
getValue(st,str -> {//第二个str不能用st,否则会报错,即不能传一个确定值
System.out.println(str);
str = str.toUpperCase();
System.out.println("转换后" + str);
return str;
});
getValue(st,str -> {
System.out.println(str);
str = str.substring(2,4);
System.out.println("转换后" + str);
return str;
});
}
public static String getValue(String str,FuncInterfaceDemo demo) {
return demo.getValue(str);
}
}
@FunctionalInterface
public interface FuncInterfaceDemo {
String getValue(String value);
}
内置四大核心式函数接口
利用四大内置的函数式接口,可以实现我们需要的绝大部分功能,我们不必再自己编写函数式接口
- Consumer:消费型接口(void accept(T t))
有参数,无返回值类型的接口。 - Supplier:供给型接口(T get())
只有产出,没人输入,就是只有返回值,没有入参 - Function<T, R>:函数型接口(R apply(T t))
输入一个类型得参数,输出一个类型得参数,当然两种类型可以一致。 - Predicate:断言型接口(boolean test(T t))
输入一个参数,输出一个boolean类型得返回值。
public class TestLambda {
//Consumer 消费型 无返回值S
public static void main(String[] args) {
Integer mo = 14;
testConsumer(mo, (h) ->
System.out.println("HELLO CONSUMER" + h)
);
testSupplier(mo, () -> {
return (int) Math.random() * 100;
});
String str = "HELLO FUNCTION";
testFunction(str, (h) -> {
return str.substring(2, 4);
});
List<String> list = Arrays.asList("do not give","say good bye","ok");
list = filterStr(list,(h)->{
return h.length()>3;
} );
System.out.println(Arrays.toString(list.toArray()));
}
static void testConsumer(Integer money, Consumer con) {
System.out.println("消费型接口测试");
con.accept(money);
}
//供给型接口
static void testSupplier(Integer money, Supplier<Integer> supplier) {
int h = supplier.get() + money;
System.out.println(h);
}
//函数型接口
static void testFunction(String str, Function<String, String> fun) {
System.out.println("函数型接口测试 输入T,返回R型");
String st = fun.apply(str);
System.out.println(st);
}
//断言型接口
//过滤保留长度大于3的字符串,并保存到集合中
static List<String> filterStr(List<String> list, Predicate<String> predicate) {
List<String> list2 = new ArrayList<>();
list.forEach(str -> {
if (predicate.test(str)) {
list2.add(str);
}
});
return list2;
}
}
方法引用
普通方法引用
Description: 方法引用:已经实现的lambda 方法可以直接使用
一种更简洁的lambda方法调用,lambda中调用的方法返回值类型以及参数列表要与返回类型需要与函数式接口一致(参数,返回值类型一致)
- 1.类::实例方法名
- 2.类::静态方法名
- 3.对象::实例方法名
- 注意:若lambda 参数列表的第一个参数是调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method 类::实例方法名 如果不是两个变量,则不可以使用此方法
BiPredicate<String, String> predicate1 = (str1, str2) -> str1.startsWith(str2);
//类::实例方法名
//参数列表的第一个参数是调用者,而第二个参数是实例方法的参数
BiPredicate<String, String> predicate2 = String::startsWith;
//对象::实例方法名
PrintStream ps = System.out;
Consumer<String> con = ps::println;
//类::静态方法名
Comparator<Integer> com = (x, y)->Integer.compare(x,y);
Comparator<Integer> com1 = Integer::compare;
构造方法引用
//构造器引用 接口自动匹配相应构造器
//ClassName::new
Supplier<Employee> supplier = Employee::new;
Function<Integer,Employee> supplier1 = Employee::new;
System.out.println(supplier1.apply(15).toString());//Employee(age=15, salary=0)
@Data
public class Employee {
public int age;
public int salary;
Employee(int age, int salary) {
this.age = age;
this.salary = salary;
}
Employee(int age){
this.age = age;
}
Employee(){
}
}
数组方法引用
Function<Integer,String[]> func = String[]::new;
Stream
Stream三步:
创建Stream,中间操作,终止操作
注意:中间操作不会立即执行,只会等到终止操作出现时,一系列中间操作开始执行(懒执行)
创建Stream:
/**
* 1.通过Collection系列提供的stream()或parallelStream()
* 2.通过Arrays 中的静态方法stream()获取数组流
* 3.通过stream类提供的静态方法 of
* 4.创建无限流
* 迭代 生成
*/
public void create() {
//1.通过Collection系列提供的stream()或parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
//2.通过Arrays 中的静态方法stream()获取数组流
String[] str = {"aa", "bb", "cc"};
Stream<String> stream1 = Arrays.stream(str);
//3.通过stream类提供的静态方法 of
Stream<String> stream2 = Stream.of("aa", "bb", "cc", "dd");
//4.创建无限流 未添加代码
}
使用中间流
//使用中间操作 中间操作不会与任何执行结果
/**
* 筛选与切片
* filter(Predicate<? super T> predicate) --- 筛选
* limit(long maxSize) --- 限制 取到maxSize后即停止
* skip(long n) --- 跳过 n 个符合条件的
* distinct --- 通过hashcode和equals筛选
*/
public void use() {
List<Employee> list = Arrays.asList(new Employee(25, 5000), new Employee(35, 7000), new Employee(27, 9000), new Employee(56, 9800));
Stream<Employee> stream = list.stream().filter(e->{
System.out.println("短路");
return e.getAge()>30;
}).limit(1);
//终止操作(使用完后流将关闭)
stream.forEach(e-> System.out.println(e.toString()));
//stream.forEach(System.out::println);//此处报异常,因为 stream has already been operated upon or closed
}
public static void main(String[] args) {
/**
* 中间操作 sort
* sorted() --- 自然排序(Comparable)
* sorted(Comparator com) --- 指定排序
*/
Stream.of("aaaa", "b","hhhhh", "ddddd","zzzzz")
.sorted()
.forEach(e -> System.out.println(e));
/*
* aaaa
b
ddddd
hhhhh
zzzzz
*
* */
}
public static void use2(String[] args) {
/**
* 中间操作 map
* flatMap:暂时未理解
*/
Stream.of("an", "b", "hello")
.map(item -> item.toUpperCase())
.forEach(e -> System.out.println(e));
}
终止操作
归约(reduce)
将流中的元素依次结合起来,得到一个新的值
三个重载的方法:
1.Optional<T> reduce(BinaryOperator<T> accumulator);
2.T reduce(T identity, BinaryOperator<T> accumulator)
3.<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
前面两种在下面有些例子介绍,但是第三种研究了半天也没搞懂意思,在查阅资料后发现,它是用在流并发操作的时候,将每个线程前两个参数形成的结果result集合并为一个。所以第三个参数是一个BinaryOperator函数接口
//归约
@Test
public void test5() {
//reduce(String identity, BinaryOperator<String> accumulator) 第一个参数相当于起始值 ,第二个参数是二元运算函数接口
String s1 = custs.stream().map(Cust::getCustName).reduce("", (x,y)->x+","+y).replaceFirst(",", "");
System.out.println(s1);
System.out.println("###################################");
//reduce(BinaryOperator<String> accumulator) 参数是二元运算函数接口,因为没有给默认值,所以为了避免使用时空指针异常,返回的是Optional
Optional<String> opt = custs.stream().map(Cust::getCustName).reduce((x,y)->x+","+y);
String s2 = opt.get().replaceFirst(",", "");
/*
* 从年龄大于40的人中去操作,filter的结果会为空
* Optional<String> opt1 = custs.stream().filter((x)->x.getAge()>40).map(Cust::getCustName).reduce((x,y)->x+","+y);
* String s3 = opt1.get().replaceFirst(",", "");
*
* 这段代码要返回异常:java.util.NoSuchElementException: No value present
* 仿佛并没有多大用处似的,看来需要再去了解一下Optional的使用方法
*/
}
收集
将数据源经过过滤、筛选等操作收集到对应的集合或者Map,或者收集他们的统计信息,如求和、平均值、最大值、最小值、记录数等
图片来自别人的教材
开始写例子。。
@Test
public void test6() {
List<Integer> cl1 = custs.stream().map(Cust::getCustId).collect(Collectors.toList());
System.out.println(cl1);
System.out.println("###############################");
//如果收集为SET,则具有排重功能
Set<Integer> st1 = custs.stream().map(Cust::getCustId).collect(Collectors.toSet());
System.out.println(st1);
System.out.println("###############################");
//使用时注意不能有重复的值
Map<String,Integer> m1 = custs.stream().distinct().collect(Collectors.toMap((x)->x.getCustName(), (x)->x.getAge()));
System.out.println(m1);
//这个重载的方法比上面多出来的参数是用来处理冲突数据
Map<String,Integer> m2 = custs.stream().collect(Collectors.toMap((x)->x.getCustName(), (x)->x.getAge(), (x,y)->x*y));
System.out.println(m2);
//在上面方法基础上,还可以把其他Map中的数据合并到结果中
Map<String,Integer> m3 = custs.stream().collect(Collectors.toMap((x)->x.getCustName(), (x)->x.getAge(), (x,y)->x*y,()->{
Map<String,Integer> mt = new HashMap();
mt.put("武磊", 2600000);
mt.put("郜林", 2200000);
return mt;
}));
System.out.println(m3);
System.out.println("###############################");
//toConcurrentMap用来并发开发当中,其他2个和toMap用法一致,如果在并发开发中推荐使用toConcurrentMap,效率更高
ConcurrentMap<String,Integer> m4 = custs.stream().distinct().collect(Collectors.toConcurrentMap((x)->x.getCustName(), (x)->x.getAge()));
System.out.println(m4);
//将结果收集到想要的集合当中
LinkedList<String> ll1 = custs.stream().map(Cust::getCustName).limit(3).distinct().collect(Collectors.toCollection(LinkedList::new));
System.out.println(ll1);
//统计记录数-相当于Oracle的count
Long lc = custs.stream().collect(Collectors.counting());
System.out.println(lc);
//统计所有记录的年龄-相当于Oracle的sum
Long ls = custs.stream().collect(Collectors.summingLong(x->x.getAge()));
System.out.println(ls);
//求所有记录的年龄的平均值,因为经过除法之后会变成Double,所以即使方式使Long类型,其返回结果是Double
Double ls1 = custs.stream().collect(Collectors.averagingLong(x->x.getAge()));
System.out.println(ls1);
//收集统计信息---这个功能包含了常用的统计结果,使用起来很方便,但是如果源数据很多,且使用不到那么多统计结果的话,为了考虑程序效率,还是要什么用什么比较好
DoubleSummaryStatistics dss = custs.stream().collect(Collectors.summarizingDouble(x->x.getAge()));
System.out.println(dss.getAverage()+","+dss.getCount()+","+dss.getMax());
}
## 并行流串行流
fork-join框架:
**核心思想**:把大任务拆分为非常小的任务(具体的粒度由自己把握),利用多核CPU优势
注意:
1. 一定要有ForkJoinPool支持
2. RecursiveTask 有返回值 RecursiveAction 无返回值
在这里插入代码片
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* Created with IntelliJ IDEA.
*
* @author: mason
* @date: 2019-09-15
* Time: 10:21
* Description:累计相加
*/
public class ForkJoinWork extends RecursiveTask<Long> {
private long start = 0L;
private long end = 10000000L;
public static final long lingjie = 10000L;
public ForkJoinWork(long start,long end){
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if(start-end<lingjie){//此处的条件可以根据实际业务需求变更,比如根据集合大小拆分
Long sum = 0L;
for(long i = start;i<end;i++){
sum = Long.sum(sum,i);
}
return sum;
}else{
long middle = (start+end)/2;
ForkJoinWork left = new ForkJoinWork(start,middle);
left.fork();
ForkJoinWork right = new ForkJoinWork(middle+1,end);
right.fork();
return right.join() + left.join();
}
}
public static void main(String[] args) {
long l = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();//实现ForkJoin 就必须有ForkJoinPool的支持
ForkJoinTask<Long> task = new ForkJoinWork(0L,10000000000L);//参数为起始值与结束值
Long invoke = forkJoinPool.invoke(task);
long l1 = System.currentTimeMillis();
System.out.println("invoke = " + invoke+" time: " + (l1-l));
}
}
Java8的支持
LongStream.rangeClosed(1,10000000L).reduce(Long::sum);
Optional
简介
Optional对null提供了一个更优雅的实现。Java8新特性之一
简单教程
Optional常用方法:
1.ofNullable(),empty(),of()均为创建对象时使用,但是第一个可以创建空,of()不能创建空
2.map()可以用来进行映射,进行部分属性修改
3.filter():进行过滤,不修改原值
4.orElse和orElseGet都是实现当Optional为空时,给对象赋值。orElse参数为赋值对象,orElseGet为Supplier函数接口。
@Slf4j
public class OptionalTest {
public static void main(String[] args) {
Optional<Employee> op = Optional.ofNullable(new Employee(28, 5500));
Employee em = op.orElse(new Employee());
log.info("修改前:" + op.get().toString());
Optional<Employee> op1 = op.filter(e -> e.getAge() > 30);
log.info("修改后:" + op.get().toString());
op.map(e -> {
e.setAge(e.getAge() + 10);
return e.getAge();
});
Optional<Employee> op2 = op.filter(e -> e.getAge() > 30);
// log.info("op1:"+op1.get().toString()); //会报NosuchStatement异常
log.info("opmap后:" + op.get().toString());
log.info("op2:" + op2.get().toString());
}
}
2019-09-15 12:17:00.090 [main] INFO com.example.demo.Utils.OptionalTest - 修改前:Employee(age=28, salary=5500)
2019-09-15 12:17:00.139 [main] INFO com.example.demo.Utils.OptionalTest - 修改后:Employee(age=28, salary=5500)
2019-09-15 12:17:00.140 [main] INFO com.example.demo.Utils.OptionalTest - opmap后:Employee(age=38, salary=5500)
2019-09-15 12:17:00.141 [main] INFO com.example.demo.Utils.OptionalTest - op2:Employee(age=38, salary=5500)
接口方法 (默认方法+静态方法+静态方法+Object的public方法)
默认方法:接口中default修饰的实现方法
类优先原则,当类和方法中均有实现的某个同名方法时,优先使用类中的方法
若两个接口中有两个同名方法,则需要明确使用哪个接口中的方法
全新的日期API(java.time.* java.time)
java7之前日期线程不安全,可变,java8全新的日期API不可变(线程安全)
LocalDate(日期) LocalTime(时间) LocalDateTime(日期+时间)
import java.time.*;
public class TestLocalDateTime {
public static void main(String[] args) {
LocalDate date = LocalDate.now();
LocalDate date2 = LocalDate.of(2018, 12, 31);
date2 = date2.plusDays(-1);
System.out.println(date);//2019-09-15
System.out.println(date2);//2018-12-30
System.out.println(date2.getDayOfMonth());//30
System.out.println(date2.getDayOfYear());//364
System.out.println(date2.getMonthValue());//12
System.out.println(date2.getMonth());//DECEMBER
}
/**
* Duration:计算两个时间间隔
* Period:计算两个日期间隔
*/
@Test
public void duration() {
Instant ins1 = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
Instant ins2 = Instant.now();
Duration duration = Duration.between(ins1, ins2);
System.out.println(duration.toMillis());//1086
}
@Test
public void period() {
LocalDate date1 = LocalDate.of(1998,1,5);
LocalDate date2 = LocalDate.now();
System.out.println(Period.between(date1,date2));//P21Y8M10D
}
}
Java8 日期、时间操作
一、简介
在Java8之前,日期时间API一直被开发者诟病,包括:java.util.Date是可变类型,SimpleDateFormat非线程安全等问题。故此,Java8引入了一套全新的日期时间处理API,新的API基于ISO标准日历系统。
二、日期初识
示例1: 获取当天日期
Java 8中的 LocalDate 用于表示当天日期。和java.util.Date不同,它只有日期,不包含时间。
public static void main(String[] args) {
LocalDate date = LocalDate.now();
System.out.println(“当前日期=” + date);
}
示例2: 构造指定日期
调用工厂方法LocalDate.of()创建任意日期, 该方法需要传入年、月、日做参数,返回对应的LocalDate实例。这个方法的好处是没再犯老API的设计错误,比如年度起始于1900,月份是从0开 始等等
public static void main(String[] args) {
LocalDate date = LocalDate.of(2000, 1, 1);
System.out.println(“千禧年=” + date);
}
示例3: 获取年月日信息
public static void main(String[] args) {
LocalDate date = LocalDate.now();
System.out.printf(“年=%d, 月=%d, 日=%d”, date.getYear(), date.getMonthValue(), date.getDayOfMonth());
}
示例4: 比较两个日期是否相等
public static void main(String[] args) {
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2018, 9, 24);
System.out.println(“日期是否相等=” + now.equals(date));
}
三、时间初识
示例: 获取当前时间
Java 8中的 LocalTime 用于表示当天时间。和java.util.Date不同,它只有时间,不包含日期。
public static void main(String[] args) {
LocalTime time = LocalTime.now();
System.out.println(“当前时间=” + time);
}
四、比较与计算
示例1: 日期时间计算
Java8提供了新的plusXxx()方法用于计算日期时间增量值,替代了原来的add()方法。新的API将返回一个全新的日期时间示例,需要使用新的对象进行接收。
复制代码
public static void main(String[] args) {
// 时间增量
LocalTime time = LocalTime.now();
LocalTime newTime = time.plusHours(2);
System.out.println(“newTime=” + newTime);
// 日期增量
LocalDate date = LocalDate.now();
LocalDate newDate = date.plus(1, ChronoUnit.WEEKS);
System.out.println(“newDate=” + newDate);
}
示例2: 日期时间比较
Java8提供了isAfter()、isBefore()用于判断当前日期时间和指定日期时间的比较
复制代码
public static void main(String[] args) {
LocalDate now = LocalDate.now();
LocalDate date1 = LocalDate.of(2000, 1, 1);
if (now.isAfter(date1)) {
System.out.println("千禧年已经过去了");
}
LocalDate date2 = LocalDate.of(2020, 1, 1);
if (now.isBefore(date2)) {
System.out.println("2020年还未到来");
}
}
复制代码
五、时区
示例: 创建带有时区的日期时间
Java 8不仅分离了日期和时间,也把时区分离出来了。现在有一系列单独的类如ZoneId来处理特定时区,ZoneDateTime类来表示某时区下的时间。
复制代码
public static void main(String[] args) {
// 上海时间
ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
ZonedDateTime shanghaiZonedDateTime = ZonedDateTime.now(shanghaiZoneId);
// 东京时间
ZoneId tokyoZoneId = ZoneId.of("Asia/Tokyo");
ZonedDateTime tokyoZonedDateTime = ZonedDateTime.now(tokyoZoneId);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println("上海时间: " + shanghaiZonedDateTime.format(formatter));
System.out.println("东京时间: " + tokyoZonedDateTime.format(formatter));
}
复制代码
六、格式化
示例1: 使用预定义格式解析与格式化日期
复制代码
public static void main(String[] args) {
// 解析日期
String dateText = "20180924";
LocalDate date = LocalDate.parse(dateText, DateTimeFormatter.BASIC_ISO_DATE);
System.out.println("格式化之后的日期=" + date);
// 格式化日期
dateText = date.format(DateTimeFormatter.ISO_DATE);
System.out.println("dateText=" + dateText);
}
复制代码
示例2: 日期和字符串的相互转换
复制代码
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 日期时间转字符串
LocalDateTime now = LocalDateTime.now();
String nowText = now.format(formatter);
System.out.println("nowText=" + nowText);
// 字符串转日期时间
String datetimeText = "1999-12-31 23:59:59";
LocalDateTime datetime = LocalDateTime.parse(datetimeText, formatter);
System.out.println(datetime);
}
复制代码
七、相关类说明
复制代码
Instant 时间戳
Duration 持续时间、时间差
LocalDate 只包含日期,比如:2018-09-24
LocalTime 只包含时间,比如:10:32:10
LocalDateTime 包含日期和时间,比如:2018-09-24 10:32:10
Peroid 时间段
ZoneOffset 时区偏移量,比如:+8:00
ZonedDateTime 带时区的日期时间
Clock 时钟,可用于获取当前时间戳
java.time.format.DateTimeFormatter 时间格式化类