Java8新特性 - 个人总结

将之前做的java8新特性的笔记搬运到博客上来。

目录:

一、JDK8数据结构上的变化:

【关于HashMap、LinkedHashMap、TreeMap见自己写的博客:】

 

详谈:HashMap、HashTable、concurrentHashMap

Hashtable、HashMap、concurrentHashMap、LinkedHashMap、TreeMap

看【你听】,看最后与size相关的方法、总结,有size:ConcurrentHashmap简介

size辅助补充:ConcurrentHashMap size 方法原理分析

1、HashMap

   -----------------------------------------------------------------

    1.8之前:

    数组+链表,默认大小:16,0.75开始扩容(2倍);

    (链表元素是加在表头)

   -----------------------------------------------------------------

   1.8:    

    Node数组+链表+红黑树

    当链表:大于8、总大小大于64,链表变为红黑树;

    若链表大小小于6了,就会再次变回链表;

    Node数组满了,也是会满足16,0.75开始扩容(2倍)这个过程叫 resize+rehash

    (链表元素是加在表尾)

   

    如何减少碰撞

    使用不可变的、声明作final对象,并且采用合适的equals()和   hashCode() 方法,将会减少碰撞的发生;

   

    hash()函数时如何实现的

    key.hashCode()得到哈希值,

    哈希值的低16位和高16位做异或,

    再乘(n-1)得到位置;

   

   重新调整HashMap大小resize+rehash存在的问题:

    多线程环境下会存在条件竞争;

    在调整大小的过程中,存储在链表中的元素的次序会反过来。因为移动到新 的 bucket 位置的时候,HashMap 并不会将元素放在链表的尾部,而是放在   头部。这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了, 那么就死循环了。多线程的环境下不使用 HashMap。

   

    hashmap扩容过程

    满足条件:16*0.75后,

    先二倍扩容,

    然后再rehash原来的数据到新的数组;

   -----------------------------------------------------------------

    hashtable特辑 复习这一块就有必要提hashtable了;

    实现:数组+链表;

    默认大小:11;

    put操作:

       首先进行索引计算:

       (key.hashCode() & 0x7FFFFFFF)%   table.length;

       (注:0x7FFFFFFF代表int的最大值)

       若在链表中找到了,则替换旧值,若未找到则继续;

       当总元 素个数超过 容量 * 加载因子 时,扩容为原来 2 倍+1并重新     散列;

       将新元素加到链表头部

    对修改 Hashtable 内部共享数据的方法添加了 synchronized,保证线程安 全;

   

   HashMap 与 HashTable 区别:

    默认容量不同,扩容不同

    线程安全性:HashTable 安全

    效率不同:HashTable 要慢,因为加锁;

   

    Hashtable扩容的数组长度为什么时旧数组长度乘以2加1?

    Hashtable中数组的长度尽量为素数或者奇数,同时Hashtable采用取模的 方式来计算数组下标,这样减少Hash碰撞,计算出来的数组下标更加均匀。   但是这样效率会比HashMap利用位运算计算数组下标低。

   

   Hashtable为什么采用头插法的方式迁移数组?

    采用头插法的方式效率更高。如果采用尾插法需要遍历数组将元素放置到   链表的末尾,而采用头插法将结点放置到链表的头部,减少了遍历数组的时   间,效率更高。

   

    JDK1.8前HashMap也是采用头插法迁移数据,多线程情况下会造成死循环,   JDK1.8对HashMap做出了优化,为什么JDK1.8Hashtable还是采用头插法  的方式迁移数据?

    Hashtable是线程安全的,所以Hashtable不需要考虑并发冲突问题,可以 采用效率更高的头插法。

   

    为什么Hashtable渐渐被弃用?

    Hashtable使用synchronized来实现线程安全,在多并发的情况下效率低  下。

   

   -----------------------------------------------------------------

2、ConcurrentHashMap:

1.7:

    Segment数组(继承自ReetrantLock)+ HashEntry

    核心数据如 value,以及链表都是 volatile 修饰的,保证了获取时的可见 性

   

    默认大小:16、初始化时可以指定,指定后不可变,

    而数组大小又决定并发程度;

   

    虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保 证并发的原子性,所以 put 操作时仍然需要加锁处理

    加锁:自旋获取,若超次数,改为阻塞锁获取:

    首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争, 则利用scanAndLockForPut() 自旋获取锁;

    尝试自旋获取锁;

    如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁获取,保证能获取 成功。最后解除当前 Segment 的锁;

   

   

1.8

    Node数组,跑起了原来的segment分段锁;

    CAS方式扩容 + synchronized保证线程的整体安全,

    其中的 val next 都用了 volatile 修饰,保证了可见性

   

    CAS:无锁算法

    借助 Unsafe 来实现 native code。CAS有3个操作数,内存值 V旧的预 期值 A、要修改的新值 B

    当且仅当预期值 A 和内存值 V 相同时,将内存值V修改为 B,否则什么都 不做。

    Unsafe 借助 CPU 指令 cmpxchg 来实现。

    CAS 会出现的问题:ABA

   什么是ABA:时间差引起

   CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较 并替换,那么在这个时间差类会导致数据的变化。

    比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内 存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数    据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操  作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题   的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。

    解决:增加标识,具体:对变量增加一个版本号,每次修改,版本号加 1, 比较的时候比较版  本号。

 

 

二、JDK8内存结构的变化:

将原来的方法区改为MetaSpace元空间元空间是直接在物理(本地)内存上实现的;

 

三、Lambda表达式

四、函数式接口

接口中只有一个抽象方法的接口 @FunctionalIterface

定义一个函数式接口:

@FunctionalInterface

public interface MyFun {



    Integer count(Integer a, Integer b);

}

用一下:

@Test

public void test05(){

    MyFun myFun1 = (a, b) -> a + b;

    MyFun myFun2 = (a, b) -> a - b;

    MyFun myFun3 = (a, b) -> a * b;

    MyFun myFun4 = (a, b) -> a / b;

}

再用一下:

public Integer operation(Integer a, Integer b, MyFun myFun){

    return myFun.count(a, b);

}



@Test

public void test06(){

    Integer result = operation(1, 2, (x, y) -> x + y);

    System.out.println(result);

}

 

Java内置四大核心函数式接口:

Consumer消费型接口

Supplier提供型接口

Function<T, R>函数型接口

Predicate断言型接口

五、引用

5.1:方法引用

若 Lambda 表达式体中的内容已有方法实现,则我们可以使用“方法引用”;

语法格式

    对象 :: 实例方法

       对象::println;

    类 :: 静态方法

       Integer::compare;

    类 :: 实例方法

       String::equals;

 

5.2:构造器引用

格式

    ClassName :: new

       ArrayList::new;

 

5.3、数组引用:

语法:

    Type :: new;

创建流的几种方式:
/**
* 创建流
*/
@Test
public void test01(){
    /**
    * 集合流
    *  - Collection.stream() 穿行流
    *  - Collection.parallelStream() 并行流
    */
    List<String> list = new ArrayList<>();
    Stream<String> stream1 = list.stream();

    //数组流
    //Arrays.stream(array)
    String[] strings = new String[10];
    Stream<String> stream2 = Arrays.stream(strings);

    //Stream 静态方法
    //Stream.of(...)
    Stream<Integer> stream3 = Stream.of(1, 2, 3);

    //无限流
    //迭代
    Stream<Integer> stream4 = Stream.iterate(0, (i) -> ++i+i++);
    stream4.forEach(System.out::println);

    //生成
    Stream.generate(() -> Math.random())
        .limit(5)
        .forEach(System.out::println);
}

// map:
@Test
public void test02(){
    List<String> list = Arrays.asList("a", "b", "c");
    list.stream()
        .map((str) -> str.toUpperCase())
        .forEach(System.out::println);
}

// flatMap:
public Stream<Character> filterCharacter(String str){
    List<Character> list = new ArrayList<>();
    for (char c : str.toCharArray()) {
        list.add(c);
    }

    return list.stream();
}

@Test
public void test03(){
    List<String> list = Arrays.asList("a", "b", "c");
    Test02 test02 = new Test02();
    list.stream()
        .flatMap(test02::filterCharacter)
        .forEach(System.out::println);
}

6.4、排序:

sorted():自然排序
sorted(Comparator c):定制排序

// Comparable自然排序:
@Test
public void test04(){
    List<Integer> list = Arrays.asList(1,2,3,4,5);
    list.stream()
        .sorted() //comparaTo()
        .forEach(System.out::println);
}

// Comparable定制排序:
@Test
public void test05(){
    emps.stream()
        .sorted((e1, e2) -> { //compara()
            if (e1.getAge().equals(e2.getAge())){
                return e1.getName().compareTo(e2.getName());
            } else {
                return e1.getAge().compareTo(e2.getAge());
            }
        })
        .forEach(System.out::println);
}

6.5、查找/匹配:终止操作:

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

public enum Status {
    FREE, BUSY, VOCATION;
}

@Test
public void test01(){
    List<Status> list = Arrays.asList(Status.FREE, Status.BUSY, Status.VOCATION);

    boolean flag1 = list.stream()
        .allMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag1);

    boolean flag2 = list.stream()
        .anyMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag2);

    boolean flag3 = list.stream()
        .noneMatch((s) -> s.equals(Status.BUSY));
    System.out.println(flag3);

    // 避免空指针异常
    Optional<Status> op1 = list.stream()
        .findFirst();
    // 如果Optional为空 找一个替代的对象
    Status s1 = op1.orElse(Status.BUSY);
    System.out.println(s1);

    Optional<Status> op2 = list.stream()
        .findAny();
    System.out.println(op2);

    long count = list.stream()
        .count();
    System.out.println(count);
}

6.6、规约、收集:

归约:reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中的数据反复结合起来,得到一个值;
收集:collect 将流转换成其他形式;接收一个 Collector 接口的实现用于给流中元素做汇总的方法;

collect:
/**
* Java:
*  - reduce:需提供默认值(初始值)
* Kotlin:
*  - fold:不需要默认值(初始值)
*  - reduce:需提供默认值(初始值)
*/
@Test
public void test01(){
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    Integer integer = list.stream()
        .reduce(0, (x, y) -> x + y);
    System.out.println(integer);
}

collect:

在这里插入图片描述

List<Employee> emps = Arrays.asList(
    new Employee(101, "Z3", 19, 9999.99),
    new Employee(102, "L4", 20, 7777.77),
    new Employee(103, "W5", 35, 6666.66),
    new Employee(104, "Tom", 44, 1111.11),
    new Employee(105, "Jerry", 60, 4444.44)
);

@Test
public void test02(){
    //放入List
    List<String> list = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toList()); 
    list.forEach(System.out::println);
    
	//放入Set
    Set<String> set = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toSet());
    set.forEach(System.out::println);

    //放入LinkedHashSet
    LinkedHashSet<String> linkedHashSet = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.toCollection(LinkedHashSet::new));
    linkedHashSet.forEach(System.out::println);
}

@Test
public void test03(){
    //总数
    Long count = emps.stream()
        .collect(Collectors.counting());
    System.out.println(count);

    //平均值
    Double avg = emps.stream()
        .collect(Collectors.averagingDouble(Employee::getSalary));
    System.out.println(avg);

    //总和
    Double sum = emps.stream()
        .collect(Collectors.summingDouble(Employee::getSalary));
    System.out.println(sum);

    //最大值
    Optional<Employee> max = emps.stream()
        .collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
    System.out.println(max.get());

    //最小值
    Optional<Double> min = emps.stream()
        .map(Employee::getSalary)
        .collect(Collectors.minBy(Double::compare));
    System.out.println(min.get());
}

@Test
public void test04(){
    //分组
    Map<Integer, List<Employee>> map = emps.stream()
        .collect(Collectors.groupingBy(Employee::getId));
    System.out.println(map);

    //多级分组
    Map<Integer, Map<String, List<Employee>>> mapMap = emps.stream()
        .collect(Collectors.groupingBy(Employee::getId, Collectors.groupingBy((e) -> {
            if (e.getAge() > 35) {
                return "开除";
            } else {
                return "继续加班";
            }
        })));
    System.out.println(mapMap);
    
    //分区
    Map<Boolean, List<Employee>> listMap = emps.stream()
        .collect(Collectors.partitioningBy((e) -> e.getSalary() > 4321));
    System.out.println(listMap);
}

@Test
public void test05(){
    //总结
    DoubleSummaryStatistics dss = emps.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));
    System.out.println(dss.getMax());
    System.out.println(dss.getMin());
    System.out.println(dss.getSum());
    System.out.println(dss.getCount());
    System.out.println(dss.getAverage());
    
    //连接
    String str = emps.stream()
        .map(Employee::getName)
        .collect(Collectors.joining("-")); //可传入分隔符
    System.out.println(str);
}
案例一:**给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?(如:给定【1,2,3,4,5】,返回【1,4,9,16,25】)
@Test
public void test01(){
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    list.stream()
        .map((x) -> x * x)
        .forEach(System.out::println);
}
案例二:**怎样使用 map 和 reduce 数一数流中有多少个 Employee 呢?
List<Employee> emps = Arrays.asList(
    new Employee(101, "Z3", 19, 9999.99),
    new Employee(102, "L4", 20, 7777.77),
    new Employee(103, "W5", 35, 6666.66),
    new Employee(104, "Tom", 44, 1111.11),
    new Employee(105, "Jerry", 60, 4444.44)
);

@Test
public void test02(){
    Optional<Integer> result = emps.stream()
        .map((e) -> 1)
        .reduce(Integer::sum);
    System.out.println(result.get());
}

6.7、并行流:

并行流:就是把一个内容分成几个数据块,并用不同的线程分别处理每个数据块的流;
Java 8 中将并行进行了优化,我们可以很容易的对数据进行操作;Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与串行流之间切换;

Fork / Join 框架:

在这里插入图片描述

Fork / Join 框架与传统线程池的区别:采用了“工作窃取”模式

在这里插入图片描述

// Fork/Join实现:
public class ForkJoinCalculate extends RecursiveTask<Long> {

    private static final long serialVersionUID = 1234567890L;

    private long start;
    private long end;

    private static final long THRESHPLD = 10000;

    public ForkJoinCalculate(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long length = end - start;

        if (length <= THRESHPLD) {
            long sum = 0;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            long middle = (start + end) / 2;

            ForkJoinCalculate left = new ForkJoinCalculate(start, end);
            left.fork(); //拆分子任务 压入线程队列

            ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
            right.fork();

            return left.join() + right.join();
        }

        return null;
    }
}

public class TestForkJoin {

    /**
     * ForkJoin 框架
     */
    @Test
    public void test01(){
        Instant start = Instant.now();

        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinCalculate task = new ForkJoinCalculate(0, 100000000L);

        Long sum = pool.invoke(task);
        System.out.println(sum);

        Instant end = Instant.now();
        System.out.println(Duration.between(start, end).getNano());
    }

    /**
     * 普通 for循环
     */
    @Test
    public void test02(){
        Instant start = Instant.now();

        Long sum = 0L;
        for (long i = 0; i < 100000000L; i++) {
            sum += i;
        }

        Instant end = Instant.now();
        System.out.println(Duration.between(start, end).getNano());
    }
}

Java 8 并行流 / 串行流:

@Test
public void test03(){
    //串行流(单线程):切换为并行流 parallel()
    //并行流:切换为串行流 sequential()
    LongStream.rangeClosed(0, 100000000L)
        .parallel() //底层:ForkJoin
        .reduce(0, Long::sum);

}

7、Optional:

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

常用方法

  • Optional.of(T t):创建一个 Optional 实例
  • Optional.empty(T t):创建一个空的 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
// Optional.of(T t):
@Test
public void test01(){
    Optional<Employee> op = Optional.of(new Employee());
    Employee employee = op.get();
}

// Optional.empty(T t):
@Test
public void test02(){
    Optional<Employee> op = Optional.empty();
    Employee employee = op.get();
}

// Optional.ofNullable(T t):
@Test
public void test03(){
    Optional<Employee> op = Optional.ofNullable(new Employee());
    Employee employee = op.get();
}

// isPresent():
@Test
public void test03(){
    Optional<Employee> op = Optional.ofNullable(new Employee());
    if (op.isPresent()) {
        Employee employee = op.get();
    }
}

8、接口

8.1、默认方法:

public interface MyFun {

    default String getName(){
        return "libo";
    }

    default Integer getAge(){
        return 22;
    }
}

类优先原则:

在这里插入图片描述

8.2、静态方法:

public interface MyFun {

    static void getAddr(){
        System.out.println("addr");
    }

    static String Hello(){
        return "Hello World";
    }
}

9、新的时间日期API:Date/Time API

9.1、线程安全问题:

// 传统的时间日期:存在线程安全问题
@Test
public void test01(){
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    Callable<Date> task = () -> sdf.parse("20200517");

    ExecutorService pool = Executors.newFixedThreadPool(10);

    ArrayList<Future<Date>> result = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        result.add(pool.submit(task));
    }

    for (Future<Date> future : result) {
        try {
            System.out.println(future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
    
    pool.shutdown();
}

// 解决方式:使用ThreadLocal
public class DateFormatThreadLocal {
    private static final ThreadLocal<DateFormat> df = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static Date convert(String source) throws ParseException{
        return df.get().parse(source);
    }
}

@Test
public void test02(){
    Callable<Date> task = () -> DateFormatThreadLocal.convert("20200517");

    ExecutorService pool = Executors.newFixedThreadPool(10);

    ArrayList<Future<Date>> result = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        result.add(pool.submit(task));
    }

    for (Future<Date> future : result) {
        try {
            System.out.println(future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    pool.shutdown();
}

现在使用DateTimeFormatter:

@Test
public void test03(){
    DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;

    Callable<LocalDate> task = () -> LocalDate.parse("20200517",dtf);

    ExecutorService pool = Executors.newFixedThreadPool(10);

    ArrayList<Future<LocalDate>> result = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        result.add(pool.submit(task));
    }

    for (Future<LocalDate> future : result) {
        try {
            System.out.println(future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    pool.shutdown();
}

9.2、本地时间 / 日期

ISO 标准:

在这里插入图片描述

常用方法:

方法名 返回值类型 解释
now( ) static LocalDateTime 从默认时区的系统时钟获取当前日期
of(int year, int month, int dayOfMonth, int hour, int minute, int second) static LocalDateTime 从年,月,日,小时,分钟和秒获得 LocalDateTime的实例,将纳秒设置为零
plus(long amountToAdd, TemporalUnit unit) LocalDateTime 返回此日期时间的副本,并添加指定的数量
get(TemporalField field) int 从此日期时间获取指定字段的值为 int
@Test
public void test01(){
    //获取当前时间日期 now
    LocalDateTime ldt1 = LocalDateTime.now();
    System.out.println(ldt1);

    //指定时间日期 of
    LocalDateTime ldt2 = LocalDateTime.of(2020, 05, 17, 16, 24, 33);
    System.out.println(ldt2);

    //加 plus
    LocalDateTime ldt3 = ldt2.plusYears(2);
    System.out.println(ldt3);

    //减 minus
    LocalDateTime ldt4 = ldt2.minusMonths(3);
    System.out.println(ldt4);

    //获取指定的你年月日时分秒... get
    System.out.println(ldt2.getDayOfYear());
    System.out.println(ldt2.getHour());
    System.out.println(ldt2.getSecond());
}

9.3、时间戳:

/**
* Instant:以 Unix 元年 1970-01-01 00:00:00 到某个时间之间的毫秒值
*/
@Test
public void test02(){
    // 默认获取 UTC 时区 (UTC:世界协调时间)
    Instant ins1 = Instant.now();
    System.out.println(ins1);

    //带偏移量的时间日期 (如:UTC + 8)
    OffsetDateTime odt1 = ins1.atOffset(ZoneOffset.ofHours(8));
    System.out.println(odt1);

    //转换成对应的毫秒值
    long milli1 = ins1.toEpochMilli();
    System.out.println(milli1);

    //构建时间戳
    Instant ins2 = Instant.ofEpochSecond(60);
    System.out.println(ins2);
}

9.4、时间 / 日期 差
Duration
:计算两个时间之间的间隔
Period:计算两个日期之间的间隔

@Test
public void test03(){
    //计算两个时间之间的间隔 between
    Instant ins1 = Instant.now();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Instant ins2 = Instant.now();
    Duration dura1 = Duration.between(ins1, ins2);
    System.out.println(dura1.getSeconds());
    System.out.println(dura1.toMillis());
}

@Test
public void test04(){
    LocalDate ld1 = LocalDate.of(2016, 9, 1);
    LocalDate ld2 = LocalDate.now();
    Period period = Period.between(ld1, ld2);  // ISO 标准
    System.out.println(period.getYears());
    System.out.println(period.toTotalMonths());
}

9.5、时间校正器
日期操纵:

在这里插入图片描述

9.6、时区:

ZonedDate
ZonedTime
ZonedDateTime

10、注解:

10.1、可重复的注解:

// 定义注解:
@Repeatable(MyAnnotations.class) //指定容器类
@Target({ElementType.TYPE, ElementType.METHOD,  ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String value() default "Java 8";
}

// 定义容器:
@Target({ElementType.TYPE, ElementType.METHOD,  ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {

    MyAnnotation[] value();
}

// 测试使用可重复注解:
public class Test01 {

    //重复注解
    @Test
    @MyAnnotation("Hello")
    @MyAnnotation("World")
    public void test01() throws NoSuchMethodException {
        Class<Test01> clazz = Test01.class;
        Method test01 = clazz.getMethod("test01");
        MyAnnotation[] mas = test01.getAnnotationsByType(MyAnnotation.class);
        for (MyAnnotation ma : mas) {
            System.out.println(ma.value());
        }
    }
}

10.1、可用于类型的注解:

Java 8 新增注解:新增ElementType.TYPE_USE 和ElementType.TYPE_PARAMETER(在Target上);

参考:

官网:What's New in JDK 8

https://www.runoob.com/java/java8-new-features.html

https://blog.csdn.net/weixin_45225595/article/details/106203264

猜你喜欢

转载自blog.csdn.net/ScorpC/article/details/113795298