JDK 8 新特性

1. 了解Open JDK 和 Oracle JDK

2. JDK 8新特性

  Lambda 表达式
  集合之 Stream流式操作
  接口的增强
  并行数组排序
  Optional 中避免Null检查
  新的时间和日期 API
  可重复注解

1.Lambda 表达式介绍

1.1使用匿名内部类存在的问题

  当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。
  传统写法,代码如下:

public class Demo01LambdaIntro {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程任务执行!");
}
}).start();
}
}

由于面向对象的语法要求,首先创建一个 Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,再将其交给一个线程来启动。
代码分析:对于 Runnable 的匿名内部类用法,可以分析出几点内容:Thread 类需要 Runnable 接口作为参数,
其中的抽象 run 方法是用来指定线程任务内容的核心为了指定 run 的方法体,

不得不需要 Runnable 接口的实现类为了省去定义一个 Runnable 实现类的麻烦,
不得不使用匿名内部类必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错而实际上,
似乎只有方法体才是关键所在

1.2Lambda体验

Lambda是一个匿名函数,可以理解为一段可以传递的代码。
Lambda表达式写法,代码如下:
借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到相同的效果
public class Demo01LambdaIntro {
public static void main(String[] args) {
new Thread(() -> System.out.println("新线程任务执行!")).start(); // 启动线程
}
}
这段代码和刚才的执行效果是完全一样的,可以在JDK 8或更高的编译级别下通过。从代码的语义中可以看出:我们
启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象。
1.3Lambda的优点

简化匿名内部类的使用,语法更加简单。
小结:了解了匿名内部类语法冗余,体验了Lambda表达式的使用,发现Lmabda是简化匿名内部类的简写

1.4Lambda 的标准格式

  Lambda省去面向对象的条条框框,Lambda的标准格式格式由3个部分组成:

  ( 参数类型 参数名称) -> {
    代码体;
  }

  格式说明
    ( 参数类型 参数名称):参数列表
    { 代码体;}:方法体
    - > :箭头,分隔参数列表和方法体
  Lambda与方法的对比

    匿名内部类

      public void run() {
        System.out.println("aa");
      }

    Lambda

      () -> System.out.println("bb!")

  1.4.1练习无参数无返回值的Lambda

  interface Swimmable {   public abstract void swimming(); }
package com.itheima.demo01lambda;
public class Demo02LambdaUse {    
public static void main(String[] args) {        
goSwimming(new Swimmable() {

   @Override            

  public void swimming() {                

    System.out.println("匿名内部类游泳");          

  }      

});
  goSwimming(() -> {            

    System.out.println("Lambda游泳");      

  });  

 }
   public static void goSwimming(Swimmable swimmable) {        

    swimmable.swimming();  

  }

}

1.4.2练习有参数有返回值的Lambda 

下面举例演示 java.util.Comparator<T> 接口的使用场景代码,其中的抽象方法定义为:
public abstract int compare(T o1, T o2);
当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator 接口实例来指定排序的规则。
传统写法
如果使用传统的代码对 ArrayList 集合进行排序,写法如下:

public class Person {   
 private String name;   
 private int age;    
private int height;    
// 省略其他 
}
package com.itheima.demo01lambda; 
 
import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; 
 
public class Demo03LambdaUse {     
public static void main(String[] args) {       
  ArrayList<Person> persons = new ArrayList<>();        
 persons.add(new Person("刘德华", 58, 174));         
persons.add(new Person("张学友", 58, 176));        
 persons.add(new Person("刘德华", 54, 171));        
 persons.add(new Person("黎明", 53, 178)); 
 Collections.sort(persons, new Comparator<Person>() {            
 @Override           
 public int compare(Person o1, Person o2) {                 
return o1.getAge() - o2.getAge();           
  }        
 }); 
 
        for (Person person : persons) {           
  System.out.println(person);        
 }    
 } }

这种做法在面向对象的思想中,似乎也是“理所当然”的。其中 Comparator 接口的实例(使用了匿名内部类)代表 了“按照年龄从小到大”的排序规则。

Lambda写法

package com.itheima.demo01lambda; 
 
import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; 
 
public class Demo03LambdaUse {     

public static void main(String[] args) {
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("刘德华", 58, 174));
persons.add(new Person("张学友", 58, 176));
persons.add(new Person("刘德华", 54, 171));
persons.add(new Person("黎明", 53, 178)); Collections.sort(persons, (o1, o2) -> {

      return o1.getAge() - o2.getAge();
     });
for (Person person : persons) {
      System.out.println(person);
     }
     System.out.println("-----------------"); List<Integer> list = Arrays.asList(11, 22, 33, 44); list.forEach(new Consumer<Integer>() {
      @Override

      public void accept(Integer integer) {
         System.out.println(integer);
       }
   });

System.out.println("-----------------");

list.forEach((s) -> { System.out.println(s); });

} }


1.4.3Lambda表达式的标准格式

(参数列表) -> {  方法体; }

1.5Lambda的实现原理

我们现在已经会使用Lambda表达式了。现在同学们肯定很好奇Lambda是如何实现的,现在我们就来探究Lambda 表达式的底层实现原理。

@FunctionalInterface 
interface Swimmable {
  public abstract void swimming();
}
public class Demo04LambdaImpl {
   public static void main(String[] args) {
     goSwimming(new Swimmable() {
      @Override
      public void swimming() {
        System.out.println("使用匿名内部类实现游泳");
      }
    });
   }
public static void goSwimming(Swimmable swimmable) {
    swimmable.swimming();
  }
}

我们可以看到匿名内部类会在编译后产生一个类: Demo04LambdaImpl$1.class 

 使用 XJad反编译这个类,得到如下代码:

package com.itheima.demo01lambda;
import java.io.PrintStream;
// Referenced classes of package com.itheima.demo01lambda:
// Swimmable, Demo04LambdaImpl
static class Demo04LambdaImpl$1 implements Swimmable {
public void swimming()
{
System.out.println("使用匿名内部类实现游泳");
}
Demo04LambdaImpl$1() {
}
}

我们再来看看Lambda的效果,修改代码如下

public class Demo04LambdaImpl {
public static void main(String[] args) {
goSwimming(() -> {
System.out.println("Lambda游泳");
});
}
public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}

运行程序,控制台可以得到预期的结果,但是并没有出现一个新的类,也就是说Lambda并没有在编译的时候产生一
个新的类。使用XJad对这个类进行反编译,发现XJad报错。使用了Lambda后XJad反编译工具无法反编译。我们使用
JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。

在DOS命令行输入:

          javap -c -p 文件名.class
          -c:表示对代码进行反汇编
          -p:显示所有类和成员

反汇编后效果如下:

C:Users>javap -c -p Demo04LambdaImpl.class
Compiled from "Demo04LambdaImpl.java"
public class com.itheima.demo01lambda.Demo04LambdaImpl {
public com.itheima.demo01lambda.Demo04LambdaImpl();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokedynamic #2, 0 // InvokeDynamic #0:swimming:()Lcom/itheima/demo01lambda/Swimmable;
5: invokestatic #3 // Method goSwimming:(Lcom/itheima/demo01lambda/Swimmable;)V
8: return
public static void goSwimming(com.itheima.demo01lambda.Swimmable);
Code:
0: aload_0
1: invokeinterface #4, 1 // InterfaceMethodcom/itheima/demo01lambda/Swimmable.swimming:()V
6: return
private static void lambda$main$0();
Code:
0: getstatic #5 // Field
java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String Lambda游泳
5: invokevirtual #7 // Method java/io/PrintStream.println:
(Ljava/lang/String;)V
8: return
}

可以看到在类中多出了一个私有的静态方法 lambda$main$0 。这个方法里面放的是什么内容呢?我们通过断点调试
可以确认 lambda$main$0 里面放的就是Lambda中的内容,我们可以这么理解 lambda$main$0 方法:

public class Demo04LambdaImpl {
public static void main(String[] args) {
...
}
private static void lambda$main$0() {
System.out.println("Lambda游泳");
}
}

关于这个方法 lambda$main$0 的命名:以lambda开头,因为是在main()函数里使用了lambda表达式,所以带有
$main表示,因为是第一个,所以$0。
如何调用这个方法呢?其实Lambda在运行的时候会生成一个内部类,为了验证是否生成内部类,可以在运行时加
上 - Djdk.internal.lambda.dumpProxyClasses ,加上这个参数后,运行时会将生成的内部类class码输出到一个文
件中。使用java命令如下:

java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名

eg:C:Users>java -Djdk.internal.lambda.dumpProxyClasses  com.itheima.demo01lambda.Demo04LambdaImpl

 

匿名内部类在编译的时候会一个 class文件
Lambda在程序运行的时候形成一个类
1. 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
2. 还会形成一个匿名内部类,实现接口,重写抽象方法
3. 在接口的重写方法中会调用新生成的方法.

1.6Lambda 省略格式

在Lambda标准格式的基础上,使用省略写法的规则为:
1. 小括号内参数的类型可以省略
2. 如果小括号内有且仅有一个参数,则小括号可以省略
3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号

(int a) -> {
return new Person();
}

省略后

a -> new Person()

1.7Lambda 的前提条件

Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
1. 方法的参数或局部变量类型必须为接口才能使用Lambda
2. 接口中有且仅有一个抽象方法

函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以
适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
FunctionalInterface注解
与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注
解可用于一个接口的定义上

@FunctionalInterface
public interface Operator {
void myMethod();
}

一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即
使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

1.8Lambda 和匿名内部类对比

1. 所需的类型不一样
匿名内部类,需要的类型可以是类,抽象类,接口
Lambda表达式,需要的类型必须是接口
2. 抽象方法的数量不一样
匿名内部类所需的接口中抽象方法的数量随意
Lambda表达式所需的接口只能有一个抽象方法
3. 实现原理不同
匿名内部类是在编译后会形成class
Lambda表达式是在程序运行的时候动态生成class
小结
当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类

2.JDK 8 接口新增的两个方法

JDK 8以前的接口

interface 接口名 {
静态常量;
抽象方法;
}

JDK 8以前的接口

interface 接口名 {
静态常量;
抽象方法;
默认方法;                        //接口中的默认方法实现类不必重写,可以直接使用,实现类也可以根据需要重写。这样就方便接口的扩展。
静态方法;
}

2.1接口默认方法的定义格式

interface 接口名 {
修饰符 default 返回值类型 方法名() {
代码;
}
}

  接口默认方法的使用
    方式一:实现类直接调用接口默认方法
    方式二:实现类重写接口默认方法

2.2接口静态方法的定义格式

interface 接口名 {
修饰符 static 返回值类型 方法名() {
代码;
}
}

  接口静态方法的使用
    直接使用接口名调用即可:接口名.静态方法名();

  接口默认方法和静态方法的区别
    1. 默认方法通过实例调用,静态方法通过接口名调用。
    2. 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
    3. 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用

3.常用内置函数式接口

内置函数式接口来由来
  我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽
象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。

常用内置函数式接口介绍
它们主要在 java.util.function 包中。下面是最常用的几个接口

1. Supplier接口    

@FunctionalInterface
public interface Supplier<T> {
public abstract T get();
}

2. Consumer接口

@FunctionalInterface
public interface Consumer<T> {
public abstract void accept(T t);
}

3. Function接口

@FunctionalInterface
public interface Function<T, R> {
public abstract R apply(T t);
}

4. Predicate接口

@FunctionalInterface
public interface Predicate<T> {
public abstract boolean test(T t);
}
Predicate接口用于做判断,返回boolean类型的值

4.Stream 流介绍

集合处理数据的弊端
当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。我们来体验
集合操作数据的弊端,需求如下:

一个 ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据

代码如下

public static void main(String[] args) {
// 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
// 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰");
// 1.拿到所有姓张的
ArrayList<String> zhangList = new ArrayList<>(); // {"张无忌", "张强", "张三丰"}
for (String name : list) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
// 2.拿到名字长度为3个字的
ArrayList<String> threeList = new ArrayList<>(); // {"张无忌", "张三丰"}
for (String name : zhangList) {
if (name.length() == 3) {
threeList.add(name);
}
}
// 3.打印这些数据
for (String name : threeList) {
System.out.println(name);
}
}

循环遍历的弊端
这段代码中含有三个循环,每一个作用不同:
1. 首先筛选所有姓张的人;
2. 然后筛选名字有三个字的人;
3. 最后进行对结果进行打印输出。
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环
是做事情的方式,而不是目的。每个需求都要循环一次,还要搞一个新集合来装数据,如果希望再次遍历,只能再使
用另一个循环从头开始。
那Stream能给我们带来怎样更加优雅的写法呢?
Stream的更优写法

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、逐一打印。我们真
正要做的事情内容被更好地体现在代码中。

4.1获取 Stream流的两种方式

获取一个流非常简单,有以下几种常用的方式:
所有的 Collection 集合都可以通过 stream 默认方法获取流;
Stream 接口的静态方法 of 可以获取数组对应的流。
方式1 : 根据Collection获取流

首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。

public interface Collection {
default Stream<E> stream()
}
import java.util.*;
import java.util.stream.Stream;
public class Demo04GetStream {
public static void main(String[] args) {
// 集合获取流
// Collection接口中的方法: default Stream<E> stream() 获取流
List<String> list = new ArrayList<>();
// ...
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
// ...
Stream<String> stream2 = set.stream();
Vector<String> vector = new Vector<>();
// ...
Stream<String> stream3 = vector.stream();
}
}

java.util.Map 接口不是 Collection 的子接口,所以获取对应的流需要分key、value或entry等情况:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class Demo05GetStream {
public static void main(String[] args) {
// Map获取流
Map<String, String> map = new HashMap<>();
// ...
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
}
}

方式2 : Stream中的静态方法of获取流
由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of ,使用很简单:

import java.util.stream.Stream;
public class Demo06GetStream {
public static void main(String[] args) {
// Stream中的静态方法: static Stream of(T... values)
Stream<String> stream6 = Stream.of("aa", "bb", "cc");
String[] arr = {"aa", "bb", "cc"};
Stream<String> stream7 = Stream.of(arr);
Integer[] arr2 = {11, 22, 33};
Stream<Integer> stream8 = Stream.of(arr2);
// 注意:基本数据类型的数组不行
int[] arr3 = {11, 22, 33};
Stream<int[]> stream9 = Stream.of(arr3);
}
}

备注: of 方法的参数其实是一个可变参数,所以支持数组。

4.2Stream常用方法

终结方法 :返回值类型不再是 Stream 类型的方法,不再支持链式调用。本小节中,终结方法包括 count 和
forEach 方法。
非终结方法 :返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结
方法。)

Stream注意事项(重要)
1. Stream只能操作一次
2. Stream方法返回的是新的流
3. Stream不调用终结方法,中间的操作不会执行

Stream 流的forEach方法

forEach 用来遍历流中的数据

void forEach(Consumer<? super T> action);

该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。例如:

@Test
public void testForEach() {
List<String> one = new ArrayList<>();
Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子");
/*one.stream().forEach((String s) -> {
System.out.println(s);
});*/
// 简写
// one.stream().forEach(s -> System.out.println(s));
one.stream().forEach(System.out::println);
}

5.学习 JDK 8新的日期和时间 API

旧版日期时间 API 存在的问题
  1. 设计很差: 在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包
    含日期。此外用于格式化和解析的类在java.text包中定义。
  2. 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  3. 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和
    java.util.TimeZone类,但他们同样存在上述所有的问题。

新日期时间 API介绍

  JDK 8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于 java.time 包中,下面是一些关键类。
    
LocalDate :表示日期,包含年月日,格式为 2019-10-16
    LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
    LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
    DateTimeFormatter :日期时间格式化类。
    Instant:时间戳,表示一个特定的时间瞬间。
    Duration:用于计算2个时间(LocalTime,时分秒)的距离
    Period:用于计算2个日期(LocalDate,年月日)的距离
    ZonedDateTime :包含时区的时间
    Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。此外Java 8还提供了4套其他历法,分别是:
      ThaiBuddhistDate :泰国佛教历
      MinguoDate :中华民国历
      JapaneseDate :日本历
      HijrahDate :伊斯兰历

5.1JDK 8 的日期和时间类

LocalDate 、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用 ISO -8601 日历系统的日期、时
间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息

// LocalDate: 获取日期时间的信息。格式为 2019-10-16
@Test
public void test01() {
// 创建指定日期
LocalDate fj = LocalDate.of(1985, 9, 23);
System.out.println("fj = " + fj); // 1985-09-23
// 得到当前日期
LocalDate nowDate = LocalDate.now();
System.out.println("nowDate = " + nowDate); // 2019-10-16
// 获取日期信息
System.out.println("年: " + nowDate.getYear());
System.out.println("月: " + nowDate.getMonthValue());
System.out.println("日: " + nowDate.getDayOfMonth());
System.out.println("星期: " + nowDate.getDayOfWeek());
}
// LocalTime类: 获取时间信息。格式为 16:38:54.158549300
@Test
public void test02() {
// 得到指定的时间
LocalTime time = LocalTime.of(12,15, 28, 129_900_000);
System.out.println("time = " + time);
// 得到当前时间
LocalTime nowTime = LocalTime.now();
System.out.println("nowTime = " + nowTime);
// 获取时间信息
System.out.println("小时: " + nowTime.getHour());
System.out.println("分钟: " + nowTime.getMinute());
System.out.println("秒: " + nowTime.getSecond());
System.out.println("纳秒: " + nowTime.getNano());
}
// LocalDateTime类: 获取日期时间信息。格式为 2018-09-06T15:33:56.750
@Test
public void test03() {
LocalDateTime fj = LocalDateTime.of(1985, 9, 23, 9, 10, 20);
System.out.println("fj = " + fj); // 1985-09-23T09:10:20
// 得到当前日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); // 2019-10-16T16:42:24.497896800
System.out.println(now.getYear());
System.out.println(now.getMonthValue());
System.out.println(now.getDayOfMonth());

System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
System.out.println(now.getNano());
}

 

对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。
withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对
象,他们不会影响原来的对象。

// LocalDateTime 类: 对日期时间的修改
@Test
public void test05() {
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);
// 修改日期时间
LocalDateTime setYear = now.withYear(2078);
System.out.println("修改年份: " + setYear);
System.out.println("now == setYear: " + (now == setYear));
System.out.println("修改月份: " + now.withMonth(6));
System.out.println("修改小时: " + now.withHour(9));
System.out.println("修改分钟: " + now.withMinute(11));
// 再当前对象的基础上加上或减去指定的时间
LocalDateTime localDateTime = now.plusDays(5);
System.out.println("5天后: " + localDateTime);
System.out.println("now == localDateTime: " + (now == localDateTime));
System.out.println("10年后: " + now.plusYears(10));
System.out.println("20月后: " + now.plusMonths(20));
System.out.println("20年前: " + now.minusYears(20));
System.out.println("5月前: " + now.minusMonths(5));
System.out.println("100天前: " + now.minusDays(100));
}

日期时间的比较

// 日期时间的比较
@Test
public void test06() {
// 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2018, 8, 8);
System.out.println(now.isBefore(date)); // false
System.out.println(now.isAfter(date)); // true
}

6.JDK 8重复注解与类型注解

重复注解的使用
自从Java 5中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限
制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注
解。在JDK 8中使用@Repeatable注解定义重复注解。
重复注解的使用步骤:
1. 定义重复的注解容器注解

@Retention(RetentionPolicy.RUNTIME)
@interface MyTests {
MyTest[] value();
}

2. 定义一个可以重复的注解

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest {
String value();
}

3. 配置多个重复的注解

@MyTest("tbc")
@MyTest("tba")
@MyTest("tba")
public class Demo01 {
@MyTest("mbc")
@MyTest("mba")
public void test() throws NoSuchMethodException {
}
}

4. 解析得到指定注解

// 3. 配置多个重复的注解
@MyTest("tbc")
@MyTest("tba")
@MyTest("tba")

public class Demo01 {
@Test
@MyTest("mbc")
@MyTest("mba")
public void test() throws NoSuchMethodException {
// 4.解析得到类上的指定注解
MyTest[] tests = Demo01.class.getAnnotationsByType(MyTest.class);
for (MyTest test : tests) {
System.out.println(test.value());
}
// 得到方法上的指定注解
Annotation[] tests1 =
Demo01.class.getMethod("test").getAnnotationsByType(MyTest.class);
for (Annotation annotation : tests1) {
System.out.println("annotation = " + annotation);
}
}
}

类型注解的使用
JDK 8为@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE 。
TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。 类型参数声明如: <T> 、
TYPE_USE :表示注解可以再任何用到类型的地方使用。
TYPE_PARAMETER的使用

@Target(ElementType.TYPE_PARAMETER)
@interface TyptParam {
}

public class Demo02<@TyptParam T> {
public static void main( String[] args) {
}
public <@TyptParam E> void test( String a) {
}
}

 

TYPE_USE的使用

@Target(ElementType.TYPE_USE)
@interface NotNull {
}

public class Demo02<@TyptParam T extends String> {
private @NotNull int a = 10;
public static void main(@NotNull String[] args) {
@NotNull int x = 1;
@NotNull String s = new @NotNull String();
}
public <@TyptParam E> void test( String a) {
}
}

 
原文地址:https://www.cnblogs.com/xp0813/p/12300912.html