zoukankan      html  css  js  c++  java
  • 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) {
    }
    }

     
  • 相关阅读:
    IOS-- UIView中的坐标转换
    iphone练习之手势识别(双击、捏、旋转、拖动、划动、长按)UITapGestureRecognizer
    Storm与Spark Streaming比较
    Python程序的常见错误(收集篇)
    Python画图笔记
    如何在论文中画出漂亮的插图?
    别老扯什么Hadoop了,你的数据根本不够大
    保险与互联网结合拉开序幕
    关于数学
    R--基本统计分析方法(包及函数)
  • 原文地址:https://www.cnblogs.com/xp0813/p/12300912.html
Copyright © 2011-2022 走看看