终极CURD-4-java8新特性

2019-06-13

1 概述

本篇博客主要介绍java8新特性,因为自己平常也使用到了一些java8的新特性,但是从来没有从头到尾,真真正正的把几乎所有常用的java8新特性研究一遍,这次借助端午节3三天,好好把常用java8新特性梳理一下,毕竟java12马上就要出来了。

本篇博客不会重点介绍java8新特性的基础内容,因为网上有很多优秀的文章已经介绍,比如像lambda表达式的用法,stream的特性,Optional的基础使用等等。在下就不重复造轮子了,我会给出几个我认为非常优秀的文章,对java8还不熟悉的读者,可以先看这些基础内容。本篇博客主要聚焦于java8新特性难点和易错点。

2 lambda表达式

如果对lambda还不清楚的读者,可以先按顺序阅读下面两篇文章

https://www.runoob.com/java/java8-lambda-expressions.html

http://blog.oneapm.com/apm-tech/226.html

2.1 lambda重要知识点总结

①lambda仅仅是一个语法糖,编译成class文件后,就可以发现,lambda变成了内部类

②一个lambda表达式,看成是一个匿名对象

③lambda表达式 所代表的的接口必须是一个函数式接口

④ 函数式接口:接口中只有一个抽象方法,就称为函数式接口。可以使用@FunctionalInterface确保该接口为函数式接口,如果不小心写错,java编译器会报错。

注意:函数式接口可以包含多个default方法和Object方法,以下程序编译器不会报错

@FunctionalInterface
public interface MyFunction {

    void hello();

    @Override
    boolean equals(Object o);

    default String sayHi() {
        System.out.println("hi ... everyone i am super ...");
        return "hi";
    }
}

⑤函数式接口的用处,某些方法的参数,可以使用这些函数式接口,那么我们在调用这些方法的时候,就可以使用lambda表达式。函数式接口的唯一作用,就是为了lambda的使用

lambda重点在于参数和body,所以参数类型和body逻辑一定要正确,java编译期会自动将lambda转换成对象。

    @Test // 使用匿名对象
    public void test1(){
        Comparable<Integer>  comparable = new Comparable<Integer>() {
            @Override
            public int compareTo(Integer o) {
                return 1;
            }
        };
    }

    @Test  // 使用lambda表达式
    public void test2(){
        Comparable<Integer> comparable = (o) -> 1;
    }

     /*
       想一想 为什么 下面4个会报错
                                            (o) -> "1"
                                            (o) -> System.out.print("abc")
                                            () -> 1;
                                            (String o) -> 1;
       原因:java编译器现在已经非常强大,可以根据上下文推到lambda表达式参数和body,因为lambda表达式
       实际上就是一个匿名对象,因此lambda的参数类型和body代码逻辑必须要符合匿名对象的格式
     */

⑦lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义,编辑器会自动帮我们加上final)

2.2 java内置函数接口

java内置函数接口,不需要我们自己写函数接口。最常用的四大函数接口是,Consumer、Supplier、Function、Predicate所有内置函数接口均在 java.util.function

为什么java会内置如此多的函数式接口,我们明明可以自己写函数式接口啊?

​ 原因:因为函数式接口非常简单,一个接口声明,加上一个抽象方法。既然如此,java的内置函数式接口就是业界规定,是一种规范。比如我想写一个Myfunction函数式接口

public interface MyFunction<T> {
     void consume(T t);
}

而别人又写宁一个MyFunction2、MyFunction3,既然如此,那大家就都用java内置的函数式接口,约定很重要!!!

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

2.3 方法引用

核心:方法引用只是lambda表现的另一种形式,它依旧还是一个匿名内部对象

1 对象::实例方法 该方法的参数类型和返回类型和函数式接口中的抽象方法必须一致

        // 1 匿名对象
        Consumer<String> consumer1 = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };

        // 2 lambda表达式
        Consumer<String> consumer2 = s -> System.out.println(s);

        // 3 方法引用 1
        PrintStream printStream = System.out;
        Consumer<String> consumer3 = printStream::println;

        // 4 方法引用 2
        Consumer<String> consumer4 = System.out::println;

2 类::静态方法 该方法的参数类型和返回类型和函数式接口中的抽象方法必须一致

    @Test
    public void test4(){
        // 1
        Comparator<Integer> comparator1 = (x,y) -> Integer.compare(x,y);
        // 2
        Comparator<Integer> comparator2 = Integer::compare;
    }

3 类::实例方法 特殊 该方法的参数类型和返回类型和函数式接口中的抽象方法肯定不一致,不一致,不一致

    @Test
    public void test5(){
        // 1
        BiPredicate<String, String> bp = (x, y) -> x.equals(y);
        // 2
        BiPredicate<String, String> bp2 = String::equals;
    }
    /*
    若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格     式: ClassName::MethodName  比如 x就是body中的调用者,而y正是该方法的参数
    */

比如下,下面的 类::静态方法 类::实例方法 是等价的

public class TestUtil {
    // 实例方法  没有参数
    private TestUtil println2() {
        return new TestUtil();
    }
    
    // 静态方法 有参数
    private static TestUtil println3(TestUtil testUtil) {
        return testUtil;
    }

    public static void main(String[] args) {
        TestUtil testUtil = new TestUtil();
        // 实例方法引用 函数式接口和实际方法参数肯定不一致
        Function<TestUtil, TestUtil> function2222 = TestUtil::println2;
        // 静态方法引用 函数式接口和实际方法参数必须一致
        Function<TestUtil, TestUtil> function33333 = TestUtil::println3;
    }
}

实际上TestUtil::println2,TestUtil::println3都可以看成是一个匿名对象,java编译过后,class文件几乎是一样的。

平常我们还是写 () -> {} 这种形式的,如果存在方法引用或者构造器引用,idea会自动提示我们,我们再修改即可。但是最好还是要给出详细注释,因为有的方法引用,一时半会看不明白。

2.4 构造器引用

构造器参数类型必须和函数式接口中的抽象方法一致,返回类型默认就是该类

    @Test
    public void test6(){
        // 1
        Supplier<Employee> supplier1 = () -> new Employee();
        // 2 默认使用Employee无参构造器,因为Supplier的get方法没有任何参数
        Supplier<Employee> supplier2 = Employee::new;
        // 3 Employee必须要有一个 Integer构造器   public Employee(Integer age)
        Function<Integer,Employee> function = Employee::new;
    }

2.5 数组引用

构造器参数类型必须和函数式接口中的抽象方法一致,返回类型默认就是该数组

    @Test
    public void test7(){
        // 1
        Function<Integer,String[]> function1 = x -> new String[x];
        // 2
        Function<Integer,String[]> function2 = String[]::new;
    }

2.6 lambda表达式的陷阱

有一篇博客非常棒,推荐给大家

https://segmentfault.com/a/1190000018857239

3 Stream

集合讲的是数据,流讲的是计算!

注意:

①Stream 自己不会存储元素。

②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

3.1 stream 三个核心步骤

一:创建 Stream

二:中间操作

三:终止操作(终端操作)

3.2 stream 创建的5个方法

        //1. Collection 提供了两个方法  stream() 与 parallelStream()
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream(); //获取一个顺序流
        Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
        
        //2. 通过 Arrays 中的 stream() 获取一个数组流
        Integer[] nums = new Integer[10];
        Stream<Integer> stream1 = Arrays.stream(nums);
        
        //3. 通过 Stream 类中静态方法 of()
        Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
        
        //4. 创建无限流
        //迭代
        Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
        stream3.forEach(System.out::println);
        
        //5. 创建无限流
        //生成
        Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
        stream4.forEach(TestUtil::println2);

3.3 stream 中间操作

多个中间操作可以连接起来形成一个流水线,除非流水 线上触发终止操作,否则中间操作不会执行任何的处理!

而在终止操作时一次性全部处理,称为“惰性求值”。

一:筛选与切片

方法 描述
filter(Predicate p) 接收 Lambda , 从流中排除某些元素。
distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量。
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素 不足 n 个,则返回一个空流。与 limit(n) 互补

二:映射

方法 描述
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流

注意:map和flatMap的区别

区别就是 list方法中的 add 和 addAll

add(Object o) 如果添加一个集合,会直接把该集合添加进去。

addAll(Object o)如果添加一个集合,会把集合中的元素一个一个加入进去,而不会加入一整个集合。

三:排序

方法 描述
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator comp) 产生一个新流,其中按比较器顺序排序

在stream中排序方法就只有sorted,如果不带参数,就是自然排序

        List<String> list = Arrays.asList("ccc","aaa","ddd","bbb");
        list.stream().sorted().forEach(System.out::println);

因为集合中的元素 String 实现了 Comparable接口,所以会自动调用public int compareTo(T o)进行排序

如果sorted方法带了参数,就是定制排序

emps.stream().sorted((e1,e2) -> e1.getAge()-e2.getAge()).forEach(System.out::println);

3.4 stream 终止操作

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

一:查找与匹配:

方法 描述
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)

二:归约:

方法 描述
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。 返回 T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。 返回 Optional

备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

因为reduce(T iden, BinaryOperator b)中,起始值就是T iden所以肯定会有一个值,不会返回null值,所以返回值不是Optional

三:收集:

方法 描述
collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

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

方法 返回类型 作用
toList List 把流中元素收集到List
toSet Set 把流中元素收集到Set
toCollection Collection 把流中元素收集到创建的集合
counting Long 计算流中元素的个数
summingInt Integer 对流中元素的整数属性求和
averagingInt Double 计算流中元素Integer属性的平均值
summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值。比如:数量、总和、最小值、平均值、最大值
maxBy Optional 根据比较器选择最大值
minBy Optional 根据比较器选择最小值
groupingBy Map<K,List > 根据某属性值对流分组,属性为K,值为List
partitioningBy Map<Boolean,List > 根据true或false进行分区
joining String 连接流中每个字符串

注意:groupingBy可以多级分组

3.5 并行流和串行流

并行流就是把一个内容分成多个数据块,并用不同的线程分 别处理每个数据块的流。

Java 8 中将并行进行了优化,我们可以很容易的对数据进行行操作。Stream API 可以声明性地通过 parallel() 与
sequential() 在并行流与顺序流之间进行切换。

3.6 fork/join

Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。

Fork/Join 框架与传统线程池的区别

采用 “工作窃取”模式(work-stealing):
当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。

相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的 处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因 无法继续运行,那么该线程会处于等待状态.而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行.那么处理该子 问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程 的等待时间,提高了性能.

在jdk1.8以前,fork/join比较复杂,代码编写比较难,而在jdk1.8中,利用stream中的parallel()可以非常轻松的实现fork/join的效果。(parallel底层就是利用fork/join)

4 java8中默认的接口和方法

因为要兼容java以前和接口特性,所以java1.8中才有接口默认方法。

不过有以下点注意点:

①接口默认方法 “类优先” 原则

若一个接口中定义了一个默认方法,而另外一个父类或接口又定义了一个同名的方法时 ,子类如果直接调用,会选择父类的方法

②接口冲突

如果一个父接口提供一个默认方法,而另一个接 口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突

③如果存在接口冲突,如何选择指定的接口方法呢?

public class SubClass implements Animal,Animal2{}
    @Override
    public String hello() {
        return Animal.super.hello();
    }

    @Override
    public String hello() {
        return Animal2.super.hello();
    }

通过 Interface.super.method()

④接口中可以存在静态方法,和普通类的静态方法调用方式一模一样。

5 Optional

有两篇非常不错的文章,大家可以参考一下,本人就不重复造轮子了。

https://www.cnblogs.com/chenpi/p/5923829.html

http://www.importnew.com/22060.html

Optional的底层源码其实非常简单,建议大家多去看看源码。

5.1 Optional 精华所在

请仔细思考以下代码:

    @Test
    public void test1() {
        Animal animal = getAnimal();
        if (animal != null) {
            Person person = animal.getPerson();
            if (person != null) {
                Student student = person.getStudent();
                if (student != null) {
                    System.out.println(student.getUsername());
                }
            }
        }
    }

  @Test
    public void test2() {
        Optional<Animal> animal = Optional.ofNullable(getAnimal());
        String name = animal.map(Animal::getPerson)
                .map(Person::getStudent)
                .map(Student::getUsername)
                .orElse("animal name");
        System.out.println(name);
    }

test1 是典型的非空判断,test2是使用Optional进行的非空判断,很明显后者的代码更加优雅,并且如果对象依赖的层级关系越多,那么if也就会越多,这时候使用Optional能省去很多麻烦。

5.2 Optional 误区

一:可以参考博客,文章内容非常不错,值得一看!

https://segmentfault.com/a/1190000018936877?utm_source=tag-newest

二:optional中使用map和flatMap

map源码:

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

flatMap源码:

   public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

我们可以看到,在flatMap中,如果mapper.apply(value)返回null,那么将会直接抛出异常。

而在map中mapper.apply(value)为null,不会抛出异常,仅仅返回一个empty对象。

6 java8 新的时间api

LocalDate、LocalTime、LocalDateTime:

/**
     * LocalDate、LocalTime、LocalDateTime API用法几乎一样
     * LocalDate  只有 年-月-日
     * LocalTime  只有 时-分-秒-纳秒
     * LocalDateTime 年-月-日-时-分-秒-纳秒
     */
    @Test
    public void test1() {
        LocalDateTime ldt = LocalDateTime.now(); //2019-06-12T11:40:55.132
        LocalDateTime ld2 = LocalDateTime.of(2016, 11, 21, 10, 10, 10);//2016-11-                                                                               21T10:10:10
        LocalDateTime ldt3 = ld2.plusYears(20);//2036-11-21T10:10:10
        LocalDateTime ldt4 = ld2.minusMonths(2);//2016-09-21T10:10:10
        System.out.println(ldt.getYear());//2019
        System.out.println(ldt.getMonthValue());//6
        System.out.println(ldt.getDayOfMonth());//12
        System.out.println(ldt.getHour());//11
        System.out.println(ldt.getMinute());//40
        System.out.println(ldt.getSecond());//55
    }

Instant:

    /**
     * Instant : 时间戳。 (使用 Unix 元年  1970年1月1日 00:00:00 所经历的毫秒值)
     */
    @Test
    public void test2() {
        Instant ins = Instant.now();  //默认使用 UTC 时区
        System.out.println(ins); //2019-06-12T03:45:38.923Z
        OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
        System.out.println(odt); //2019-06-12T11:46:08.147+08:00
        System.out.println(odt.toEpochSecond()); //1560311168  不论那个时区,输出的毫秒和秒                                                                           都是一样的
        System.out.println(ins.getEpochSecond()); //1560311168
        System.out.println(ins.toEpochMilli()); // 和System.currentTimeMillis() 一样
    }

Duration、Period:

    /**
     * Duration : 用于计算两个“时间”间隔
     * Period : 用于计算两个“日期”间隔
     */ 
    @Test
    public void test3() throws InterruptedException {
        Instant ins1 = Instant.now();
        Thread.sleep(1000);
        Instant ins2 = Instant.now();
        System.out.println("所耗费时间为:" + Duration.between(ins1, ins2)); //所耗费时间                                                                           为:PT1.001S
        System.out.println("----------------------------------");
        LocalDate ld1 = LocalDate.now();
        LocalDate ld2 = LocalDate.of(2011, 1, 1);
        Period pe = Period.between(ld2, ld1);
        System.out.println(pe.getYears()); //8
        System.out.println(pe.getMonths()); //5
        System.out.println(pe.getDays()); //11
    }

TemporalAdjuster:

    /**
     * TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”、“这个月的第一天”、"这一年的最后一天"等操作。
     * TemporalAdjusters : 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。
     * 主要涉及到LocalDateTime、LocalDate、LocalTime的with方法
     * public LocalDateTime with(TemporalAdjuster adjuster) { ... }
     */
    @Test
    public void test4() {
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt); // 2019-06-12T13:53:06.814

        LocalDateTime ldt2 = ldt.withDayOfMonth(10);
        System.out.println(ldt2); // 2019-06-10T13:53:06.814

        LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
        System.out.println(ldt3); // 2019-06-16T13:53:06.814

        //自定义:下一个工作日
        LocalDateTime ldt5 = ldt.with((l) -> {
            LocalDateTime ldt4 = (LocalDateTime) l;

            DayOfWeek dow = ldt4.getDayOfWeek();

            if (dow.equals(DayOfWeek.FRIDAY)) {
                return ldt4.plusDays(3);
            } else if (dow.equals(DayOfWeek.SATURDAY)) {
                return ldt4.plusDays(2);
            } else {
                return ldt4.plusDays(1);
            }
        });
        System.out.println(ldt5); // 2019-06-13T13:53:06.814
    }

DateTimeFormatter

    /**
     * DateTimeFormatter : 解析和格式化日期或时间
     */
    @Test
    public void test5() {
        // DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE; DateTimeFormatter内部定义了很多时间格式                
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss:SSS E");  // 自定义格式,E表示周几
        LocalDateTime ldt = LocalDateTime.now();
        String strDate = ldt.format(dtf); //LocalDateTime转成String
        System.out.println(strDate); // 2019年06月12日 14:14:22:611 星期三
        LocalDateTime newLdt = LocalDateTime.parse(strDate, dtf); // 将String转换成LocalDateTime
        System.out.println(newLdt); // 2019-06-12T14:14:22.611
    }

ZonedDate、ZonedTime、ZonedDateTime

/**
 * ZonedDate、ZonedTime、ZonedDateTime : 带时区的时间或日期
 */
@Test
public void test7() {
    Set<String> set = ZoneId.getAvailableZoneIds();
    set.forEach(System.out::println); // 获取所有的时区
    /*
        America/Los_Angeles
        SystemV/EST5EDT
        Pacific/Majuro
        America/Argentina/Buenos_Aires
        Europe/Nicosia
        Pacific/Guadalcanal
        Europe/Athens
        US/Pacific
        Europe/Monaco
        ... ...
        ... ...
    */
    ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
    System.out.println(zdt); //2019-06-12T14:33:16.543+08:00[Asia/Shanghai]
}

下面是一篇非常不错的博客,建议读者仔细阅读

https://www.cnblogs.com/comeboo/p/5378922.html

这篇博客总共有20个知识点:

1 如何在java8中获取当天的日期

2 如何在java8中获取当前的年月日

3 在java8中如何获取某个特定的日期

4 在java8中检查两个日期是否相等

5 在java8中如何检查重复事件,比如生日

6 如何在java8中获取当前时间

7 如何增加时间里面的小时数

8 如何获取1周后的日期

9 一年前后的日期

10 在java8中使用时钟

11 在java中如何判断某个日期在另一个日期的前面还是后面

12 在java8中处理不同的时区

13 如何表示固定的日期,比如信用卡过期时间

14 如何在java8中检查闰年

15 两个日期之间包含多少天,多少月

16 带时区的日期与时间

17 在java8中获取当前时间戳

18 如何在java8中使用预定义的格式器来对日期进行解析/格式化

19 如何在java中使用自定义的格式器来解析日期

20 如何在java8中对日期进行格式化,转换成字符串

这是一篇英语博客,内容非常不错,感兴趣的朋友可以阅读。

https://www.baeldung.com/java-8-date-time-intro

这篇博客的 ZonedDateTime时区相关讲解,感觉不错。

7 重复注解和类型注解

如果对java的注解完全不熟悉的朋友,可以参考下面的博客。

https://www.cnblogs.com/xdp-gacl/p/3622275.html

这篇博客主要讲解的是java1.8注解新特性

https://www.cnblogs.com/lxyit/p/9442586.html

注解有几个容易被遗忘的地方:

①如果注解上面没有写@Target,那么默认@Target包含所有的属性,下面两个MyAnnotation是完全一样的

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "hello world";
}

@Target( { ElementType.METHOD,ElementType.TYPE,ElementType.FIELD,ElementType.TYPE_USE
,ElementType.CONSTRUCTOR,ElementType.ANNOTATION_TYPE,ElementType.LOCAL_VARIABLE,
ElementType.PACKAGE,ElementType.PARAMETER,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "hello world";
}

②使用重复注解的时候,用getAnnotations只能获取 容器注解的类型(也是就@Repeatable中的类)

可重复注解:

@Target( { ElementType.METHOD,ElementType.TYPE,ElementType.FIELD,ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
    String value() default "hello world";
}

容器注解类

@Target( { ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
    MyAnnotation[] value();
}

使用注解的类

@MyAnnotation("value ... ...")
@MyAnnotation2
@MyAnnotation("personality design ... ...")
class AnnotationUse { }

主程序:

    @Test
    public void test1() {
        Annotation[] annotations = AnnotationUse.class.getAnnotations();
        Arrays.stream(annotations).forEach(System.out::println);
    }

输出:

@com.atguigu.anonotation.MyAnnotations(value=[@com.atguigu.anonotation.MyAnnotation(value=hello world), @com.atguigu.anonotation.MyAnnotation(value=personality design ... ...)])
@com.atguigu.anonotation.MyAnnotation2(value=MyAnnotation2 ... ...)

我们可以看到,用getAnnotations并没有拿到@MyAnnotation而是@MyAnnotations

参考资料

https://www.runoob.com/java/java8-lambda-expressions.html

http://blog.oneapm.com/apm-tech/226.html

https://segmentfault.com/a/1190000018857239

https://www.cnblogs.com/chenpi/p/5923829.html

http://www.importnew.com/22060.html

https://segmentfault.com/a/1190000018936877?utm_source=tag-newest

https://www.cnblogs.com/comeboo/p/5378922.html

https://www.baeldung.com/java-8-date-time-intro

请尊重作者劳动成果,转载请注明出处。以上内容若有侵权,请联系作者。