1995 年 5 月 23 日,Oak 语言改名为 Java,然后有了那句著名的口号——“Write Once,Run Anywhere”。

1996 年 JDK1.0发布了,标志着一个新的时代已经到来。

1998 年 Java 迎来 1.2 版本,有此诞生了我熟知的 J2ME、J2SE 以及 J2EE。ME 主要用于移动端(还记得大明湖畔诺基亚 S40 吗?不小心暴露了年龄),而 SE 做为标准版主要用于桌面程序,EE 则主要针对企业应用所打造,也是我们主要的研究对象。1.2 的发布标志着Java开始普及。

2000 年 1.3 发布,并得到了 Mac OS X 工业标准的支持。

2002 年,也就是中国首次进入世界杯(也是至今唯一的一次,扎铁了老心)的那年。这一年 1.4 问世,这一版本极大的丰富了 Java 的类库,如:XML、Socket、全新的 I/O API、正则、日志、断言等如今我们耳熟能详的功能。

2004 年,我们刚刚经历完非典,Java 也迎来了重要更新,为了突出这次更新的重要性,命名方式从原来的 J2XE 1.X 变成了现在的 JavaXE X,于是有了 JavaSE 5。这一版本增加了泛型、自动拆装箱、循环增强(foreach)、枚举、注解、可变参数等,堪称有史以来最重大的更新,Java 5 应该有姓名!

接下来(2006年)又发布了 Java 6 ,据说这个版本在国内很流行(不要告诉我你们公司还在用 JDK1.6 )。同年,发生了一件大事——Java 开源了!我爱开源!

2009 年 Oracle(就是前段时间裁员 N+6 的那家公司)收购了 Sun , Java 从此跟了后妈,过着寄人篱下的日子。

后来,在 2011 年发布了 Java 7 。

三年后发布了 Java 8 ,为我们带来了 Lambda 表达式、Stream 以及新的日期时间 API 。Java 8 应该也是目前被使用最多的版本。

后来相继发布了 9、10、11、12、13,今年(2020 年)3 月 17 日 Oracle 发布了 JDK 14。

在Java8中产生了许多重大更新。

1、Lambda表达式

Lambda轻松上手,快速传送门:Lambda表达式,你真的不了解一下吗?

2、Stream API

说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带

来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。

2.1 入门介绍

几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元

素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。例如:

import java.util.ArrayList;
import java.util.List;
public class Demo01ForEach {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
for (String name : list) {
System.out.println(name);
}
}
}

这是一段非常简单的集合遍历操作:对集合中的每一个字符串都进行打印输出操作。

循环遍历的弊端 :

Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行

了对比说明。现在,我们仔细体会一下上例代码,可以发现:

  • for循环的语法就是“怎么做”
  • for循环的循环体才是“做什么”

为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从

第一个到最后一个顺次处理的循环。前者是目的,后者是方式。

试想一下,如果希望对集合中的元素进行筛选过滤:

  1. 将集合A根据条件一过滤为子集B;

  2. 然后再根据条件二过滤为子集C。

可以进行多次遍历后筛选:

import java.util.ArrayList;
import java.util.List;
public class Demo02NormalFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
List<String> zhangList = new ArrayList<>();
//第一次筛选以‘张‘开始的name
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
//以第一次筛选的结果作为第二次筛选的输入
List<String> shortList = new ArrayList<>();
for (String name : zhangList) {
if (name.length() == 3) {
shortList.add(name);
}
}
for (String name : shortList) {
System.out.println(name);
}
}
}

这段代码中含有三个循环,每一个作用不同:

  1. 首先筛选所有姓张的人;
  2. 然后筛选名字有三个字的人;
  3. 最后进行对结果进行打印输出。

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循

环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使

用另一个循环从头开始。

那,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?

下面来看一下借助Java 8的Stream API,如果优雅的写代码:

import java.util.ArrayList;
import java.util.List;
public class Demo03StreamFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.stream()
.filter(s ‐> s.startsWith("张"))
.filter(s ‐> s.length() == 3)
.forEach(System.out::println);
}
}

直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码

中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。

2.2、什么是Stream流

流(Stream) 到底是什么呢?是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算! ”

注意:

  1. Stream不会存储元素。
  2. Stream不会改变元对象,相反,他们会返回一个持有结果的新Strean【类似建造者模式】。
  3. Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream的操作三部曲

  • 创建Stream:一个数据源(如:集合、数组),获取一个流。
  • 中间操作:一个中间操作链,对数据源的数据进行处理。
  • 终止操作:一个终止操作,执行中间操作链,得到结果。

2.3、Stream的创建

①、通过Collection接口获取

  • default Stream stream() : 返回一个顺序流
  • default Stream parallelStream() : 返回一个并行流
//1.通过Collection系列集合提供的Stream()[串行流]方法或者parallelStream()[并行流]
List<Integer>list=new ArrayList<>();
Stream<Integer> stream1 = list.stream();

②、由数组创建流

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • static Stream stream(T[] array): 返回一个流
 //2.通过Arrays中的静态方法Stream()获取数组流,各种数组转为Stream
int[] arr={1,2,3,4};
IntStream stream2= Arrays.stream(arr);

③、由值创建流

可以使用静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static Stream of(T... values) : 返回一个流
 Stream<String> stream3 = Stream.of("a", "b");

④、由函数创建流:创建无限流

可以使用静态方法 Stream.iterate() 和Stream.generate(), 创建无限流。 【流的初始大小未固定】

  • ①、迭代

    public static Stream iterate(final T seed, finalUnaryOperator f)

  • ②、生成

    public static Stream generate(Supplier s)

/**
* public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
* 参数 seed:初始值
* UnaryOperator<T> f函数式接口,单参数有返回值
*
*/
//①迭代
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
stream4.limit(10)
.forEach(System.out::println);
//②生成
Stream.generate(()->Math.random())
.limit(10)
.forEach(System.out::println);

2.4、Stream的中间操作

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

①、筛选与切片

  • filter-接受lambda表达式,从流中排除某些元素;
  • limit-截断流,试元素不超过给定的数量;
  • skip(n)-跳过元素,返回一个扔掉了前n个元素的流。若流中的元素不足n个,则返回一个空流,与limit(n)互补;
  • distinct-筛选,通过流所生成的hashCode()和equls()去掉重复元素。
 @Test
public void test01(){
list.stream()
.filter((x)->Integer.parseInt(x.getAge())>50)
.limit(1)
.forEach(System.out::println);
//终止操作:最后一次性执行全部内容,并不会先执行某句话再执行
}
@Test
public void test02(){
list.stream()
.skip(2)//跳过前两个
.distinct()//去重 其中对象得重写hashcode和equls
.forEach(System.out::println);
}

说明:比如filter(Predicate<? super T> predicate)需要传递的是一个函数式接口,而上述代码使用lambda表达式去实现。

②、映射

  • flatMap(Function<? super T, ? extends Stream<? extends R>> mapper):【多个流合并为一个流】把流中的元素一个个加入到当前的流中。
  • map(Function<? super T, ? extends R> mapper):函数会对每一个元素进行映射得到一个新的元素。

下面为了测试这两种的区别,我们自定义一个返回Stream流的方法:

@Test
public void test04(){
List<String>str=Arrays.asList("aaa","bbb","ccc");
//直接使用map map本地得到的就是一个新的Stream流,而map执行多次得到多个流,最终Stream流存放的依然是 Stream流【Stream流中存放的是Stream流】
//map:不会合并流,仅仅是对元素输入函数进行映射,得到一个个Stream加入到当前的流中
Stream<Stream<Character>> sm = str.stream().map(TestStream::filterCharter);//直接调用此类的方法
sm.forEach((ssmm)->{
ssmm.forEach(System.out::println);
});
//flatMap:将返回的流进行合并,得到一个流
str.stream()
.flatMap(TestStream::filterCharter)//lambda表达式的类::静态方法调用 直接调用此类的方法
.forEach(System.out::println);
}
//将字符串切割得到字符
public static Stream<Character>filterCharter(String str){
List<Character> list=new ArrayList<>();
for (Character c:str.toCharArray()) {
list.add(c);
}
return list.stream();
}

映射

③、排序

 List<Employee> list= Arrays.asList(
new Employee("张三","15"),
new Employee("李四","45"),
new Employee("王五","35"),
new Employee("王五","35"),
new Employee("王六","35"),
new Employee("小刘","65")
);
/**
* 排序:
* sorted()-自然排序-(Comparable)
* sorted(Comparator com)-定制排序
*/
@Test
public void test05(){
List<String>str=Arrays.asList("ddd","aaaaa","bbb","ccccc");
str.stream()
.sorted()
.forEach(System.out::println);
list.stream()
.sorted((x,y)->{
if(x.getAge().equals(y.getAge())){
return -x.getName().compareTo(y.getName());
}else{
return -(Integer.parseInt(x.getAge())-Integer.parseInt(y.getAge()));
}
})
.forEach(System.out::println);
}

2.4、Stream的终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如: List、 Integer,甚至是 void 。

①、查找与匹配

  • allMatch-检查是否匹配所有元素
  • anyMatch-检查是否至少匹配一个元素
  • noneMatch-检查是否没有匹配所有元素
  • findFirst-返回第一个元素
  • findAny-返回当前流中的任意元素
  • count-返回流中元素的总个数
  • max-返回流中最大值
  • min-返回流中的最小值

是不是已经感觉到迷糊了,这么多!别急,一个小案例就清晰了。

 List<Student> list= Arrays.asList(
new Student("张三",18, Student.Status.FREE),
new Student("李四",13, Student.Status.BUSY),
new Student("李四",13, Student.Status.BUSY),
new Student("王五",11, Student.Status.BUSY),
new Student("刘六",55, Student.Status.VOCATION),
new Student("王麻子",77, Student.Status.FREE)
);
//注:Student.Status是一个enum
@Test
public void test01(){
//是否匹配所有的
boolean match = list.stream()
.allMatch((t) -> Student.Status.BUSY.equals(t.getStatus()));
System.out.println(match);//false
//至少有一个匹配
boolean match1 = list.stream()
.anyMatch((t) -> Student.Status.BUSY.equals(t.getStatus()));
System.out.println("至少有一个元素与之匹配"+match1);//至少有一个元素与之匹配true
//没有匹配的元素
boolean match2 = list.stream()
.noneMatch((t) -> Student.Status.BUSY.equals(t.getStatus()));
System.out.println("没有与之匹配的元素:"+match2);//没有与之匹配的元素:false
//避免空指针异常,将结果封装到Optional,一旦有可能为空就封装到Optional中
Optional<Student> op = list.stream()
.sorted((x, y) ->Integer.compare(x.getAge(),y.getAge()))
.findFirst();
System.out.println("排序后拿到第一个元素:"+op.get());//Student{name='王五', age=11, Status=BUSY}
//随便找到一个空闲状态的人,先过滤出来,再随便找一个
Optional<Student> any = list.stream()
.filter((e) -> e.getStatus().equals(Student.Status.FREE))
.findAny();
System.out.println("随便-找到一个status为空闲的人"+any.get());//Student{name='张三', age=18, Status=FREE}
System.out.println("人员总数"+list.stream().count());//人员总数6
Optional<Student> max = list.stream()
.max((x, y) -> Integer.compare(x.getAge(), y.getAge()));
System.out.println("获取年龄最大的:"+max.get());//Student{name='王麻子', age=77, Status=FREE}
}

下面对上述方法的具体描述:

②、归约

  • reduce(T identity,BinaryOperator)

    T identity 初始值

    BinaryOperator->继承BiFunction<T,U, R>

    将identity作为起始值,做为x,再从流中取出一个元素作为y;
  • reduce(BinaryOperator)--可以将流中元素反复结合起来,得到一个值
  @Test
public void test02(){
List<Integer>list=Arrays.asList(1,2,3,4,5,6,7,8);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);//初始值,一开始x=初始值,再从流中拿到第一个值作为y
System.out.println(sum);//36
/**
* 下列包含Lambda表达式中的方法方法引用的 类::实例方法
* 此种情况较为特殊:
* 若lambda参数列表中的第一个参数是实例方法的调用者,
* 而第二个参数是实例方法的参数时或者没有第二个参数,
* 可以使用ClassName::method
*/
Optional<Integer> op = this.list.stream()
.map((x) -> x.getAge())
.reduce(Integer::sum);
System.out.println(op.get());//187
Optional<Integer> op2 = this.list.stream()
.map(Student::getAge)
.reduce(Integer::sum);
System.out.println(op2.get());//187
}

③、收集

Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、 Set、 Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例, 具体方法与实例如下表:



  • collect-将流转换为其他形式。接受一个Collector接口的实现,用于Stream中元素做汇总的方法
 List<Student> list= Arrays.asList(
new Student("张三",18, Student.Status.FREE),
new Student("李四",13, Student.Status.BUSY),
new Student("李四",13, Student.Status.BUSY),
new Student("王五",11, Student.Status.BUSY),
new Student("刘六",55, Student.Status.VOCATION),
new Student("王麻子",77, Student.Status.FREE)
);
//需求:将当前学生的名字提取出后放在一个集合里面
@Test
public void test03(){
System.out.println("-----------将结果收集到list集合并返回--------------------");
List<String> list1 = list.stream()
.map(Student::getName)
.collect(Collectors.toList());
list1.forEach(System.out::println);
System.out.println("-----------将结果收集到set集合并返回-----------------------");
Set<String> set1 = list.stream()
.map(Student::getName)
.collect(Collectors.toSet());
set1.stream().forEach(System.out::println);
System.out.println("-----------自定义返回的集合类型---------------------------");
HashSet<String> set2 = list.stream()
.map(Student::getName)
.collect(Collectors.toCollection(HashSet::new));
set2.forEach(System.out::println);
System.out.println("-----------将结果收集为总数数量---------------------------");
Long number = list.stream()
.collect(Collectors.counting());
System.out.println(number);
System.out.println("-----------得到结果的平均值---------------------------");
Double av = list.stream()
.collect(Collectors.averagingInt(value -> value.getAge()));
System.out.println(av);
System.out.println("-----------得到结果的总和---------------------------");
Integer sum = list.stream()
.collect(Collectors.summingInt(Student::getAge));
System.out.println(sum);
System.out.println("-----------得到结果的最大值---------------------------");
Optional<Student> student = list.stream()
.collect(Collectors.maxBy((o1, o2) -> Double.compare(o1.getAge(), o2.getAge())));
System.out.println(student.get());
System.out.println("-----------连接字符串---------------------------");
String s = list.stream()
.map(Student::getName)
.collect(Collectors.joining("中间连接符,","首","尾"));
System.out.println(s); }

需求一:按照状态进行分组 对应sql的分组

@Test
public void test04(){
Map<Student.Status, List<Student>> map = list.stream()
.collect(Collectors.groupingBy(Student::getStatus));
System.out.println(map);
}
//结果:
{VOCATION=[Student{name='刘六', age=55, Status=VOCATION}],
BUSY=[Student{name='李四', age=13, Status=BUSY}, Student{name='李四', age=13, Status=BUSY}, Student{name='王五', age=11, Status=BUSY}],
FREE=[Student{name='张三', age=18, Status=FREE}, Student{name='王麻子', age=77, Status=FREE}]}

需求二:多级分组

注意:分组后一般返回的是一个map,其中key作为分组的依据,value就是分组后的值

 /**
*
* Collectors.groupingBy(Function,Collectors)
* 分组后,对已分组的数据进行二次分组
*/
@Test
public void test05(){
Map<Student.Status, Map<String, List<Student>>> map = list.stream()
.collect(Collectors.groupingBy(Student::getStatus, Collectors.groupingBy(o -> {
if (((Student) o).getAge() <= 18) {
return "青年";
} else if (((Student) o).getAge() <= 50) {
return "中年";
} else {
return "老年";
}
})));
System.out.println(map);
}
//结果:
{BUSY={青年=[Student{name='李四', age=13, Status=BUSY}, Student{name='李四', age=13, Status=BUSY}, Student{name='王五', age=11, Status=BUSY}]},
VOCATION={老年=[Student{name='刘六', age=55, Status=VOCATION}]},
FREE={青年=[Student{name='张三', age=18, Status=FREE}], 老年=[Student{name='王麻子', age=77, Status=FREE}]}}

分片和分区:根据处理的结果ture/false进行分区

 @Test
public void test06(){
Map<Boolean, List<Student>> map = list.stream()
.collect(Collectors.partitioningBy(o -> o.getAge() > 50));
System.out.println(map);
}
//结果:
{false=[Student{name='张三', age=18, Status=FREE}, Student{name='李四', age=13, Status=BUSY}, Student{name='李四', age=13, Status=BUSY}, Student{name='王五', age=11, Status=BUSY}],
true=[Student{name='刘六', age=55, Status=VOCATION}, Student{name='王麻子', age=77, Status=FREE}]}

3、新的时间日期API

3.1、LocalDate、 LocalTime、 LocalDateTime

LocalDate、 LocalTime、 LocalDateTime 类的实例是不可变的对象【线程安全】,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。 新的日期类将统一放在java.time包下。

注意:三者使用的方式完全相同,只是一个表示日期,一个表示时间,一个表示时间和日期

下面让我们快速入门吧!

 @Test
public void test01(){
System.out.println("---------------------获取当前系统时间----------------------------");
LocalDateTime dateTime1 = LocalDateTime.now();
System.out.println(dateTime1);
System.out.println("---------------------指定时间,年月日时分秒----------------------------");
LocalDateTime dateTime2 = LocalDateTime.of(2015, 10, 16, 13, 22, 33);
System.out.println(dateTime2);
//时间运算,添加一日
System.out.println("---------------------日期运算,当前系统时间往后添加一日-----------------");
LocalDateTime dateTime3 = dateTime1.plusDays(1);
System.out.println(dateTime3);
System.out.println("---------------------日期运算,当前系统时间往后减少一个月---------------");
LocalDateTime dateTime4 = dateTime1.minusMonths(1);
System.out.println(dateTime4);
//获取单独的年月日
//可以直接获取值,也可以获取年月日对象
System.out.println("年:"+dateTime1.getYear());
System.out.println("月:"+dateTime1.getMonthValue());
System.out.println("日:"+dateTime1.getDayOfMonth());
System.out.println("时:"+dateTime1.getHour());
System.out.println("分:"+dateTime1.getMinute());
System.out.println("秒:"+dateTime1.getSecond());
}
输出:
---------------------获取当前系统时间---------------------------------
2020-08-13T22:36:07.123
---------------------指定时间,年月日时分秒----------------------------
2015-10-16T13:22:33
---------------------日期运算,当前系统时间往后添加一日-----------------
2020-08-14T22:36:07.123
---------------------日期运算,当前系统时间往后减少一个月---------------
2020-07-13T22:36:07.123
年:2020
月:8
日:13
时:22
分:36
秒:7

①、Instant时间戳

Instant 时间戳 给机器读的用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算。

 @Test
public void test02(){
Instant instant = Instant.now();//默认获取的是UTC时区为基础的
System.out.println("默认获取的是UTC时区为基础的时间:"+instant);
//对时区做一个偏移量运算
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println("带偏移量的时区时间:"+offsetDateTime); //输出时间格式为毫秒格式
System.out.println("输出时间格式为毫秒格式"+instant.toEpochMilli());
//相较1970年的时间
System.out.println("相较1970年的时间 向后添加60秒"+Instant.ofEpochSecond(60));//1970-01-01T00:01:00Z
}
输出:
默认获取的是UTC时区为基础的时间:2020-08-15T02:49:41.828Z
带偏移量的时区时间:2020-08-15T10:49:41.828+08:00
输出时间格式为毫秒格式1597459781828
相较1970年的时间 向后添加60秒1970-01-01T00:01:00Z

②、Duration 和 Period

  • Duration:计算两个时间之间的间隔。
  • Period:计算两个日期之间的间隔。
@Test
public void test03() throws InterruptedException {
Instant instant1 = Instant.now();
Thread.sleep(100);
Instant instant2 = Instant.now();
//计算两个时间戳之间的间隔
Duration duration = Duration.between(instant1, instant2);
//获取毫秒
System.out.println(duration.toMillis());//119
//获取纳秒
System.out.println(duration.getNano());//119000000
//获取秒
System.out.println(duration.getSeconds());//0
//获取纳秒第二种方式
System.out.println(duration.toNanos());//119000000 LocalDateTime dateTime1 = LocalDateTime.now();
Thread.sleep(1);
LocalDateTime dateTime2 = LocalDateTime.now();
System.out.println(duration.between(dateTime1,dateTime2).toMillis());//1
}
  @Test
public void test04(){
LocalDate date1 = LocalDate.of(2018,1,1);
LocalDate date2 = LocalDate.now();
Period period = Period.between(date1, date2);
System.out.println(period);//P2Y5M13D
//直接输出格式不是很明显
System.out.println("相差几年:"+period.getYears());//
System.out.println("相差多少月:"+period.getMonths());//
System.out.println("相差多少天:"+period.getDays());//
}
输出:
P2Y7M14D
相差几年:2
相差多少月:7
相差多少天:14

③、日期的操纵

  • TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
  • TemporalAdjusters : 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。

方法public LocalDate with(TemporalAdjuster adjuster) ,传入接口TemporalAdjuster 对日期进行调整,我们查看TemporalAdjuster 接口的源码:

@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}

可以发现其是一个函数式接口

  @Test
public void test05(){
//下一个周五是啥时候
LocalDate date = LocalDate.now();
//下一个周五是啥时候
LocalDate date1 = date.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println("下一个周五是啥时候"+date1); //自定义指定时间,获取当前时间距离周六的天数,
LocalDate date2 = date.with(t -> {
//LocalDate实现了接口Temporal,所以可以实现强转
LocalDate lt = (LocalDate) t;
//获取今天是周几
//lt.plusDays():向当前日期添加几天
DayOfWeek dayOfWeek = lt.getDayOfWeek();
if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
return lt.plusDays(0);//其实这一句是多余的
} else {
return lt.plusDays(DayOfWeek.SATURDAY.getValue() - dayOfWeek.getValue());
}
});
System.out.println("前时间距离周六的时间"+date2);
}
输出:
下一个周五是啥时候2020-08-21
前时间距离周六的时间2020-08-15

说明:DayOfWeek是java.time包下的一个enum类型,其中定义好了周一~周日的枚举值。

④、解析与格式化

java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:

  • 预定义的标准格式
  • 语言环境相关的格式
  • 自定义的格式
 @Test
public void test07(){
System.out.println("-------------------------使用自带的各种格式---------------");
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME; LocalDateTime dateTime = LocalDateTime.now();
String strtime = formatter.format(dateTime);
System.out.println(strtime);
System.out.println("-------------------------自定义日期格式---------------");
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String strtime2 = formatter2.format(dateTime);
System.out.println(strtime2);
System.out.println("--------------------把字符串解析成为时间LocalDateTime---");
LocalDateTime dateTime2 = dateTime.parse(strtime2, formatter2);
System.out.println(dateTime2);
}
输出:
-------------------------使用自带的各种格式---------------
2020-08-15T11:14:51.74
-------------------------自定义日期格式---------------
2020年08月15日 11:14:51
--------------------把字符串解析成为时间LocalDateTime---
2020-08-15T11:14:51

注意:根据字符串解析LocalDateTime时,其formatter需保持一致,否则会报错java.time.format.DateTimeParseException

⑤、时区的处理

Java8 中加入了对时区的支持,带时区的时间为分别为: ZonedDate、 ZonedTime、 ZonedDateTime

其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式例如 : Asia/Shanghai 等。

ZoneId:该类中包含了所有的时区信息

  • getAvailableZoneIds() : 可以获取所有时区时区信息
  • of(id) : 用指定的时区信息获取 ZoneId 对象
@Test
public void test08(){
System.out.println("---------------获取支持的所有时区----------------------");
Set<String> ids = ZoneId.getAvailableZoneIds();
ids.forEach(System.out::println);
System.out.println("---------------获取指定时区地区的时间信息---------------");
LocalDateTime now = LocalDateTime.now(ZoneId.of("Europe/London"));
System.out.println(now);//2020-06-15T08:18:27.396
System.out.println("---------------获取带时区信息的时间,包含了与UTC标准的时差---");
LocalDateTime now1 = LocalDateTime.now();
ZonedDateTime zone = now1.atZone(ZoneId.of("Europe/London"));
System.out.println(zone);//2020-06-15T15:18:27.515+01:00[Europe/London]
}
输出:
---------------获取支持的所有时区----------------------
Asia/Aden
America/Cuiaba
Etc/GMT+9
Etc/GMT+8
Asia/Shanghai
等等.....
---------------获取指定时区地区的时间信息---------------
2020-08-15T04:22:09.009
---------------获取带时区信息的时间,包含了与UTC标准的时差---
2020-08-15T11:22:09.029+01:00[Europe/London]

⑥、与传统时器处理与转换

4、接口中的默认方法和静态方法

我们都知道,当实体类(非抽象类)A继承了某个接口B,那么类A必须重写接口中的方法。此模式在springboot 1.X的版本中均使用此模式,其解决方式是在接口和实体类中添加了一层:抽象类,使之抽象类继承接口,实体类再去继承抽象类。比如:

在springboot 1.X版本时,当需要对springmvc进行扩展时,相关配置需要继承抽象类WebMvcConfigureAdapter:

@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
}
...WebMvcConfigurer接口的其他方法
}

进入源码我们可以发现其对WebMvcConfigurer实现,但是均没有具体实现。

当springboot 2.X版本时,此时我们进入接口WebMvcConfigurer中查看:

public interface WebMvcConfigurer {

	/**
* Helps with configuring HandlerMappings path matching options such as trailing slash match,
* suffix registration, path matcher and path helper.
* Configured path matcher and path helper instances are shared for:
* <ul>
* <li>RequestMappings</li>
* <li>ViewControllerMappings</li>
* <li>ResourcesMappings</li>
* </ul>
* @since 4.0.3
*/
default void configurePathMatch(PathMatchConfigurer configurer) {
}
@Nullable
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
...等等...
}

对比一下:此接口中定义方法与我们平时定义的有何不同?

  • 接口中的方法前多了一个关键字default,而且对其进行了实现。

最后插入一下springboot中对于springmvc扩展时的部分配置:

@Configuration
//@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {
//需要什么方法,从写什么方法 /**
* 配置嵌入式servlet容器
* @return
*/
// @Bean //一定要将这个定制器加入到容器中
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
//定制嵌入式的Servlet容器相关的规则
/* return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
@Override
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(8081);
}
};*/
return (factory)->factory.setPort(8081);
}
/**
* 自定义一个controller
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//返回的路径也是经过thymeleaf模板引擎的
//这个相当于在controller中映射了一个@RequestMapping()
registry.addViewController("/page03").setViewName("thymeleaf01");
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
/**
* 自定义一个视图解析器
* 将被ContentNegotiatingViewResolver自动加载
*/
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
private static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
} /**
*注册拦截器
*/
/* @Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(Arrays.asList("/index.html","/","/user/login","/assert/**","/css/**","/js/**","/img/**","/webjars/**"))
.addPathPatterns("/**");
}*/
}

好了,上述听着可能比较繁琐,而我们比较直观的看见的是:当接口中的方法使用default修饰后,其实现类不强制去实现,也只是为了引入default关键字,那么接口中的默认方法和静态方法有啥注意事项呢?

接口默认方法的” 类优先” 原则 :

若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时 :

  • 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
  • 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法), 那么必须覆盖该方法来解决冲突 。

好了,下面使用一个例子来说明上述两条规则:

定义一个接口Myfun:

/**
* 在以前接口中只能有:全局静态常量和抽象方法
* 在java8中添加默认方法、静态方法
*/
public interface Myfun {
default String getName(){
return "这是接口Myfun!";
}
public static void shwo(){
System.out.println("这是Myfun接口中的静态方法");
}
}

定义一个实体类TestDefault,其下有一个与接口Myfun同名方法:

public class TestDefault  {
public String getName(){
return "这是类testDefault!";
}
}

定义一个实现类MyClass去继承第接口和实现类:

public class MyClass extends TestDefault implements Myfun{
}

此时若调用MyClass的getName()方法,那么调用的是接口中的方法,或者是实体类终的方法呢???

public class TestDefaultInterface {
/**
* 类MyClass同时继承了TestDefault、Myfun
* 在类TestDefault和接口Myfun均存在方法getName()
* 问题:当子孙类同时继承时,调用getName()方法时,调用的是谁的方法?
*/ //注意:当继承的多个接口中存在同名的默认方法时,那么实现类中必须对此方法进行重写 public static void main(String[] args) {
//调用的是类中的方法
System.out.println(new MyClass().getName());
//接口直接调用静态方法执行
Myfun.shwo();
}
}
结果:
这是类testDefault!
这是Myfun接口中的静态方法

5、Optional类

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

常用方法:

  • Optional.of(T t) : 创建一个 Optional 实例
  • Optional.empty() : 创建一个空的 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

最新文章

  1. CSS 兼容解决之hack
  2. 在嵌入式开发板中运行程序提示-/bin/sh: ./xx: not found的解决办法
  3. Cpu Gpu 内存 显存 数据流
  4. 单链表在不知头结点的情况下对第i个元素的删除
  5. redis基础使用
  6. nginx常用变量
  7. 关于Windows Azure 地缘组(Affinity Groups)
  8. html5上传本地图片,在线预览及裁剪(filereader,canvas)
  9. Android Animations 视图动画使用详解!!!
  10. Composite Design Pattern 设计模式组合
  11. Linux下安装Tomcat启动报错
  12. windows安装elasticsearch
  13. Python_Mix*异常处理
  14. hive最全学习线路和实践练习
  15. Linux查看与挂载新磁盘
  16. AngularJS 关于ng-model和ng-bind还有{{}}
  17. mysql列反转Pivoting
  18. 【BZOJ5321】[JXOI2017]加法(贪心)
  19. 搭建前端监控系统(三)NodeJs服务器部署篇
  20. ATS 自定义日志格式

热门文章

  1. hash table
  2. value-key
  3. 23 种设计模式 APP &amp; 23 Design Patterns App
  4. linux &amp; node &amp; cli &amp; exit(0) &amp; exit(1)
  5. no code form generator
  6. 前端 &amp; 技术团队 TL &amp; 如何面试 &amp; 如何带人
  7. Github &amp; DMCA Takedown &amp; git remove history
  8. taro weapp
  9. ts 在Function上创建静态属性和方法
  10. MySQL修改表中字段的字符集