java5特性

拆箱和装箱

装箱(autoboxing)和拆箱(unboxing)

autoboxing: 将基本数据类型用他们响应的引用类包装起来,使其具有对象的性质

unboxing:和装箱相反,将引用数据类型的对象简化为值类型数据

  1. 在表达式中,基本类型的数据值可以和基本数据对象进行运算

  2. 基本数据类型的数组不能实现自动装箱和拆箱,即int[]不能当成Integer[]使用

示例

// jdk5.0之前
Integer i = new Integer(200);
// jdk5.0-自动装箱
Integer j = 200;

// jdk5.0之前
int ii = i.intValue();
// jdk5.0-自动拆箱
int jj = j;

//进行计算时隐含的有自动拆箱
Integer num = 1;
System.out.print(num--);

其它类型类似,就不演示了。

Object o = 1;

上述语句是正确的,1 会被自动装箱为Integer类型,赋值给Object对象。

String 也有自动装箱和拆箱

String str = "sl";
等价于
String str = String.valueOf("sl");

//代替下面的声明方式
String str = new String("sl");

原理

自动装箱时编译器调用 valueOf() 将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似 intValue(), doubleValue() 这类的方法将对象转换成原始类型值。

即:

Integer i = 200;
等价于
Integer i = Integer.valueOf(200);

int j = i;
等价于
int j = i.intValue();

证明

public class Test {
    public static void main(String[] args){
       Integer  i  =  100; // 装箱
       int  j  = i; // 拆箱
    }
}

使用 javap -c Test 对上述代码的字节码进行反编译。

file

自动装箱的设计

//在-128~127 之外的数
Integer num1 = 127; Integer num2 = 127;
System.out.println("num1==num2: "+(num1==num2)); // true

// 在-128~127 之内的数
Integer num3 = 128; Integer num4 = 128;
System.out.println("num3==num4: "+(num3==num4)); // false

这是因为Java自动装箱/拆箱使用 享元模式

享元模式为了加大对简单数字的重用,所以定义:在自动装箱对于值-128到127内的值,他们被装箱为Integer(也包括Long、Character)对象后,会在内存中重用(指向同一个对象),始终指存在一个对象。

如果超过了这个范围(-128~127),被装箱后的Integer对象则不会被重用,所以每次装箱都会创建一个对象。

注意:只有 Long,Integer, Character有享元模式。Double,Float,Byte都没有享元模式

查看Integer源码:

public static Integer valueOf(int i) {
      // 我们发现如果传入的数在这个数组范围内, 则直接返回告诉缓存中的对象,否则创建一个新的对象
      // 这就是【享元模式】,为了性能而有的设计
      if (i >= IntegerCache.low && i <= IntegerCache.high)
          return IntegerCache.cache[i + (-IntegerCache.low)];
          return new Integer(i);
      }

CacheInteger:

    // Integer内部类
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        // 内部是一个静态的不可变缓存数组
        static final Integer cache[];

    // 这里有一个静态代码块用于进行初始化
        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
            // 初始化了一个Integer数组,大小为 127 - -128 +1
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
     }

foreach循环

JDK1.5提供了foreach循环,冒号左边为当前遍历到的元素,右边为遍历的集合/数组。

public static void foreach() {
    String[] strs = new String[]{"a", "b", "c"};
    for (String a : strs) {
        System.out.println(a);
    }
}

foreach遍历不能修改数组/集合

public static void foreach() {
    String[] strs = new String[]{"a", "b", "c"};
    for (String a : strs) {
        a = "1";
    }
    System.out.println(Arrays.toString(strs));
}
// 运行结果 仍然为 [a, b, c]

foreach每次loop的时候,获取的都是对象的值,而不是对象的句柄,所以数组永远不会改变。

ConcurrentModificationException 并发修改异常

遍历集合时,修改集合同样会出现ConcurrentModificationException异常:

for(String language: list){
    list.remove(language);
}

foreach的原理

foreach遍历的原理是基于java.lang.Iterable接口,只要继承该接口,就可以使用foreach对对象进行遍历。

import java.util.*;
public class Test {
    static List<Integer> integers;
    public static void main(String[] args) {
        for(Integer i : integers){
        }
    }
}

下面是反编译后: a80370d3 可以清楚的看到整个步骤:

  1. 加载this对象到栈

  2. 加载字段包含integers

  3. 调用integers接口iteratable方法iterator,获取Iterator对象

  4. 使用Iterator.hasNext()判断是否有下一个值,如果有调用Iterator.next()获取值,并赋值给变量i

注意,数组并没有实现Iterale接口,数组使用foreach遍历的字节码如下:

public class Test {
    static Integer[] integers = new Integer[]{1,2,3,4,5,6,7};
    public static void main(String[] args) {
        for(Integer i : integers){
        }
    }
}
64e8793b

变长参数 vararg

private String print(Object... values) {
    StringBuilder sb = new StringBuilder();
    for (Object o : values) {
      sb.append(o.toString())
        .append(" ");
    }
    return sb.toString();
}

变长参数 values 实际还是一个数组类型

静态导入 static iomport

静态导入可以导入Class中的所有static变量以及方法:

import static java.lang.System.err;
import static java.lang.System.out;
import java.io.IOException;
import java.io.PrintStream;
public class StaticImporter {
  public static void writeError(PrintStream err, String msg) 
    throws IOException {
   
    // Note that err in the parameter list overshadows the imported err
    err.println(msg); 
  }
  public static void main(String[] args) {
    if (args.length < 2) {
      err.println(
        "Incorrect usage: java com.oreilly.tiger.ch08 [arg1] [arg2]");
      return;
    }
    out.println("Good morning, " + args[0]);
    out.println("Have a " + args[1] + " day!");
    try {
      writeError(System.out, "Error occurred.");
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

泛型

为什么需要泛型?

List list = new ArrayList();
list.add("A");
list.add(100);
for (int i = 0; i < list.size(); i++) {
  String name = (String) list.get(i); //取出Integer时,运行时出现异常
System.out.println("name:" + name);
}

使用泛型

List<String> list = new ArrayList<String>();

通过List,直接限定了list集合中只能含有String类型的元素,集合记住了元素的类型,无需进行强制转换

Heap Pollution

什么是堆污染:

堆污染是一种技术用语,它指的是对象的引用类型不是指向对象类型或者超类类型。

List<A> listOfAs = new ArrayList<>();
List<B> listOfBs = (List<B>)(Object)listOfAs; // 指向as列表,造成堆污染
// 这会导致ClassCastExceptions

所以,当将一个不带泛型的对象赋值给带泛型的变量是,通常会引发堆污染。

什么时候会发生堆污染

把一个不带泛型的对象赋给一个带泛型的变量是,就会发生堆污染. 通常编译期间都会检测出这种情况,并且显示警告: unchecked warning.

List ns = new ArrayList<Number>();
List<String> ls = ns;

为什么会发生堆污染

在Java中,参数化类型(泛型),都是非具体化类型(non-reifiable types)。

non-reifiable types 是指,在runtime时期是不完整的类型,在编译期间,非具体化类型会经过 “类型擦除”(编译器会删除域类型参数相关的信息),所以准确的说,泛型只存在于编译时期。

非具体化类型保证了那些使用JDK1.5(没有泛型)之前的程序的二进制兼容性。

因此,不同泛型的同一类型变量,在运行时具有相同的类或接口实现。所以,当参数化类型的变量( List<String>)指的不是该参数化类型的对象( List<Integer>)时,就发生了堆污染。

Set s = new TreeSet<Integer>();
Set<String> ss = s;            // unchecked warning
s.add(new Integer(42));        // another unchecked warning
Iterator<String> iter = ss.iterator();
while (iter.hasNext())
{
    String str = iter.next();   // ClassCastException thrown
    System.out.println(str);
}

parameterized vararg type 为什么会发生堆污染警告

有如下参数化可变参数类型的方法会发生堆污染警告:

public static <T> void foo(List<T>... bar) {
    for (List<T> ts : bar) {
        System.out.println(ts);
    }
}
// warning: Possible heap pollution from parameterized vararg type

参数化可变参数类型可能发生堆污染,这是因为,可变参数在编译时期,会做出如下转换:

  • public static <T> void foo(List<T>... bar) 函数,被转换为

  • public static <T> void foo(List<T>[] bar),然后再转换为

  • public static <T> void foo(List[] bar)

这样,我们如下调用:

Object[] objectArray = new String[]{};
List<Integer>[] lists = (List<Integer>[]) objectArray;
foo(lists);

就会发生 ClassCastException 异常,(java.lang.String; cannot be cast tojava.util.List)。所以,会出现堆污染警告。

枚举

JDK1.5引入枚举类型,他是一种特殊的数据类型,即使一种类(class)类型但是又比普通的类类型多了些特殊的约束,这些约束造就了枚举类型的简洁性,安全性以及便捷性。

定义枚举

enum Day {
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

引用枚举:

Day day =Day.MONDAY;

枚举的实现原理

将上述day类编译并使用反编译得到如下:

//反编译Day.class
// enum 就是继承Enum类型的类
final class Day extends Enum
{
    //编译器为我们添加的静态的values()方法
    public static Day[] values()
    {
        return (Day[])$VALUES.clone();
    }
    //编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法
    public static Day valueOf(String s)
    {
        return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);
    }
    //私有构造函数
    private Day(String s, int i)
    {
        super(s, i);
    }
     //前面定义的7种枚举实例
    public static final Day MONDAY;
    public static final Day TUESDAY;
    public static final Day WEDNESDAY;
    public static final Day THURSDAY;
    public static final Day FRIDAY;
    public static final Day SATURDAY;
    public static final Day SUNDAY;
    private static final Day $VALUES[];

    static
    {
        //实例化枚举实例
        MONDAY = new Day("MONDAY", 0);
        TUESDAY = new Day("TUESDAY", 1);
        WEDNESDAY = new Day("WEDNESDAY", 2);
        THURSDAY = new Day("THURSDAY", 3);
        FRIDAY = new Day("FRIDAY", 4);
        SATURDAY = new Day("SATURDAY", 5);
        SUNDAY = new Day("SUNDAY", 6);
        $VALUES = (new Day[] {
            MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
        });
    }
}
  1. 编译器在编译enum时,会生成一个final的 Day 类,并且此类继承 java.lang.Enum

  2. 根据枚举元素,生成了七个Day类型的实例对象,并且都是 public static final 修饰,所以,使用enum定义的枚举元素都是一个个Day实例

  3. 此外,编译器还生成了两个静态方法,分别为 values()valueOf()

java.lang.Enum

上面说到,使用enum修饰的枚举类型,在编译期间会自动继承Enum类,下面是Enum类的使用和源码:

首先,Enum是一个抽象类,实现了序列化接口Serializable和比较接口 Comparable,这意味着Enum可以被序列化以及被比较。

构造:

  • Enum只有一个包私有构造器: protected Enum(String name, int ordinal)

从Object继承的方法:

  • protected Object clone() CloneNotSupportedException,enum类型不支持对象克隆

  • protected void finalize() 枚举类不能有 finalize 方法

  • String toString() 返回枚举常量的名称,它包含在声明中

  • int hashCode()

  • boolean equals(Object other) 当指定对象等于此枚举常量时,返回 true

比较方法Compareable:

  • int compareTo(E o)

特有方法:

  • Class<E> getDeclaringClass() 返回枚举常量对应的Class对象

  • int ordinal() 返回枚举常量的序数,它在枚举声明中的位置,其中初始常量序数为零,比如 Day.MONDAY.ordinal() == 0

  • String name() 返回此枚举常量的名称,和toString功能类似

  • static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) 返回带指定名称的指定枚举类型的枚举常量

编译器生成的 values 方法和 valueOf 方法:

  • values()方法和valueOf(String name)方法是编译器生成的static方法

    Day[] days2 = Day.values();
    System.out.println("day2:"+Arrays.toString(days2));
    Day day = Day.valueOf("MONDAY");
    System.out.println("day:"+day);

源码解析:

package java.lang;

import java.io.Serializable;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

    // 枚举常量名称
    private final String name;

    // 返回枚举常量的名称
    public final String name() {
        return name;
    }

    // 枚举常量在枚举中的位置
    private final int ordinal;

    // 获取枚举常量的序数
    public final int ordinal() {
        return ordinal;
    }

    // 指定枚举常量名称和序数,构建一个枚举常量
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    // toString和name()别无二致
    public String toString() {
        return name;
    }
    // 使用equals比较的是内存地址
    public final boolean equals(Object other) {
        return this==other;
    }

    // 禁止子类重写hashCode
    public final int hashCode() {
        return super.hashCode();
    }

    // 不支持对象克隆
    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
    // 比较方法
    // 1. 比较对象class类型
    // 2. 比较对象的DeclaringClass类型
    // 3. 比较序数
    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }

    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

    //根据类型和名称获取泛型
    public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

    // 使finalize方法无效
    protected final void finalize() { }

    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }

    private void readObjectNoData() throws ObjectStreamException {
        throw new InvalidObjectException("can't deserialize enum");
    }
}

枚举的几种用法

1. 用作常量

在JDK1.5之前,定义常量需要使用 public static final

在JDK1.5,我们可以这样定义常量:

public enum Color {
    GREEN, RED, ORANGE
}

2. switch语句

JDK1.6之前switch语句只支持 int, char, enum 类型,使用枚举可以让代码可读性更强

enum Signal {
    GREEN, YELLOW, RED
}
public class TrafficLight {
    Signal color = Signal.RED;
    public void change() {
        switch (color) {
        case RED:
            color = Signal.GREEN;
            break;
        case YELLOW:
            color = Signal.RED;
            break;
        case GREEN:
            color = Signal.YELLOW;
            break;
        }
    }
}

3. 添加构造/方法/字段

因为enum本身是一个类,而元素又是enum的对象,所以我们可以添加字段记录一些值,使用构造初始化这些值:

public enum Day2 {
    MONDAY("星期一"),
    TUESDAY("星期二"),
    WEDNESDAY("星期三"),
    THURSDAY("星期四"),
    FRIDAY("星期五"),
    SATURDAY("星期六"),
    SUNDAY("星期日");//记住要用分号结束

    private String desc;//中文描述

    /**
     * 私有构造,防止被外部调用
     * @param desc
     */
    private Day2(String desc){
        this.desc=desc;
    }

    /**
     * 定义方法,返回描述,跟常规类的定义没区别
     * @return
     */
    public String getDesc(){
        return desc;
    }

    public static void main(String[] args){
        for (Day2 day:Day2.values()) {
            System.out.println("name:"+day.name()+
                    ",desc:"+day.getDesc());
        }
    }

    /**
     输出结果:
     name:MONDAY,desc:星期一
     name:TUESDAY,desc:星期二
     name:WEDNESDAY,desc:星期三
     name:THURSDAY,desc:星期四
     name:FRIDAY,desc:星期五
     name:SATURDAY,desc:星期六
     name:SUNDAY,desc:星期日
     */
}

也可以添加一些方法,作为工具类调用。

4. 覆盖toString

public enum Day2 {
    MONDAY("星期一"),
    TUESDAY("星期二"),
    WEDNESDAY("星期三"),
    THURSDAY("星期四"),
    FRIDAY("星期五"),
    SATURDAY("星期六"),
    SUNDAY("星期日");//记住要用分号结束

    private String desc;//中文描述

    /**
     * 私有构造,防止被外部调用
     * @param desc
     */
    private Day2(String desc){
        this.desc=desc;
    }

    /**
     * 覆盖
     * @return
     */
    @Override
    public String toString() {
        return desc;
    }

    public static void main(String[] args){
        for (Day2 day:Day2.values()) {
            System.out.println("name:"+day.name()+
                    ",desc:"+day.toString());
        }
    }
}

5. 定义抽象

package com.zejian.enumdemo;

/**
 * Created by zejian on 2017/5/9.
 * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
 */
public enum EnumDemo3 {

    FIRST{
        @Override
        public String getInfo() {
            return "FIRST TIME";
        }
    },
    SECOND{
        @Override
        public String getInfo() {
            return "SECOND TIME";
        }
    }

    ;

    /**
     * 定义抽象方法
     * @return
     */
    public abstract String getInfo();

    //测试
    public static void main(String[] args){
        System.out.println("F:"+EnumDemo3.FIRST.getInfo());
        System.out.println("S:"+EnumDemo3.SECOND.getInfo());
        /**
         输出结果:
         F:FIRST TIME
         S:SECOND TIME
         */
    }
}

6. 实现接口

interface food{
    void eat();
}

interface sport{
    void run();
}

public enum EnumDemo2 implements food ,sport{
    FOOD,
    SPORT,
    ; //分号分隔

    @Override
    public void eat() {
        System.out.println("eat.....");
    }

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

我们可能对一组数据进行分类:

public interface Food {
  enum Appetizer implements Food {
    SALAD, SOUP, SPRING_ROLLS;
  }
  enum MainCourse implements Food {
    LASAGNE, BURRITO, PAD_THAI,
    LENTILS, HUMMOUS, VINDALOO;
  }
  enum Dessert implements Food {
    TIRAMISU, GELATO, BLACK_FOREST_CAKE,
    FRUIT, CREME_CARAMEL;
  }
  enum Coffee implements Food {
    BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
    LATTE, CAPPUCCINO, TEA, HERB_TEA;
  }
}

public class TypeOfFood {
  public static void main(String[] args) {
    Food food = Appetizer.SALAD;
    food = MainCourse.LASAGNE;
    food = Dessert.GELATO;
    food = Coffee.CAPPUCCINO;
  }
}

Java的注解

参考

https://blog.csdn.net/sun_promise/article/details/51315032

https://docs.oracle.com/javase/tutorial/java/annotations/type_annotations.html

https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html

概念

Java提供了一种原程序中的元素关联任何信息和任何元数据的途径和方法。(注解就是元数据)

JDK1.5 提供注解特性。

元注解

修饰注解的注解,称为元注解,在JDK中,共有四种元注解:

  • @Target规定注解可以使用的位置,类,方法还是域

  • @Retention表示需要在什么级别保存该注解信息,由RetentionPolicy枚举定义

  • @Documented表示注解会被包含在javaapi文档中

  • @Inherited允许子类继承父类的注解

@Target:

使用ElementType枚举定义,包含以下值:

  • CONSTRUCTOR:构造器的声明

  • FIELD:域声明(包括enum实例)

  • LOCAL_VARIABLE:局部变量声明

  • METHOD:方法声明

  • PACKAGE:包声明

  • PARAMETER:参数声明

  • TYPE:类、接口(包括注解类型)或enum声明

  • ANNOTATION_TYPE:注解声明(应用于另一个注解上)

  • TYPE_PARAMETER:类型参数声明(1.8新加入)

  • TYPE_USE:类型使用声明(1.8新加入)

@Retention:

使用RetentionPolicy枚举定义,包含以下值:

  • SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)

  • CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机(JVM)中)

  • RUNTIME:VM将在运行期也保留注解信息,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息) PS:当注解未定义Retention值时,默认值是CLASS

常用注解

  • java.lang.SuppressWarnings 抑制警告

  • java.lang.Override 方法重写

  • java.lang.Deprecated 已经过时

jdk7新增:

  • @SafeVarargs 堆污染

定义注解

// 定义的注解默认会继承java.lang.annotation.Annotation接口
public @interface 注解名{

}

底层实现

注解的本质实际上是一个Interface,所有注解在反编译后都是继承 java.lang.annotation.Annotation 接口的。

@MyAnnotation // 声明类
public class Test {
}

类型注解

JDK8后,类型注解可以在任何有类型的地方加入注解(包含泛型):

@MyAnnotation
public class Test<@MyAnnotation T> extends @MyAnnotation Fathers implements @MyAnnotation Callable {

    private @MyAnnotation String name = "lisi";

    public @MyAnnotation String test(@MyAnnotation String name) throws @MyAnnotation Exception {
        @MyAnnotation String s = "test";
        @MyAnnotation List<String> list = new @MyAnnotation ArrayList<>();
        Number a = 1;
        Integer i = (@MyAnnotation Integer) a;
        if (list.get(0) == null) {
            throw new @MyAnnotation Exception();
        }
        list.add(s);
        return list.get(0);
    }

    @Override
    public Object call() throws Exception {
        return null;
    }
}

作用:

在8种新增类型注释主要是为了改进Java的程序分析,配合类型检查框架做强类型检查,从而在编译期间确认运行时异常,比如 NullpointException,从而提高代码质量。

比如:

@NonNull Object my = null;

第三方工具会在编译期间自动检测my是否为null,如果为null,抛出异常或者警告

目前具有此项功能的检测框架有 The Check Framework

创建类型注解

JDK8 新增 ElementType.TYPE_USE 用来创建类型注解:

@Target({TYPE_USE})
@Retention(RUNTIME)
public @interface MyAnnotation {
}

注意:JDK8还提供了 TYPE_PARAMETER 类型的注解,表示可以修饰类型参数(泛型)的注解。

注解的继承

  1. 首先想让注解可继承,必须增加@Inherited元注解,如果需要反射支持,就需要@Rentention(RetentionPolicy.RUNTIME)标识

  2. JDK文档中,对于注解继承的描述为:只有类上的注解才能被继承

  3. 实际上,如果类中的成员成员变量以及成员方法没有被重写,他们就仍然是父类的那个方法,即使在子类中。故继承自父类的没有被重写的成员,也具有原注解的信息。

  4. 注解的继承不能用在接口上,任何接口上的注解都不能被继承

可重复注解(Repeating Annotations)

在8之前,一个目标只可以打一个注解:

@Test
private String name;

现在JDK8提供了Repeating Annotations,可以在一个目标上打上多个可重复注解:

@Test("a")
@Test("b")
private String name;

创建可重复注解

JDK8中提供了一个新的源注解,用于表示可重复注解:

// 创建可重复注解:指定容器类型为 Sechedules.class
@Repeatable(Schedules.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedule {
    String dayOfMonth() default "first";
    String dayOfWeek() default "Mon";
    int hour() default 12;
}
// 可重复注解的容器注解,用于存放多个可重复注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedules {
    Schedule[] value();
}

获取可重复注解(1.8)

注意:如果想要在运行时获取注解,注解 Retention 必须为 RetentionPolicy.RUNTIME,并且,Schedules与Schedule 注解的保留时期必须都为运行时!

获取可重复注解的方式同获取普通注解没有什么太大的区别:

@Schedule(hour = 9)
@Schedule(hour = 10)
public class Task {

    public static void main(String[] args) throws Exception {
        Schedule[] ats = Task.class.getAnnotationsByType(Schedule.class);
        System.out.println(Arrays.toString(ats));
        // [@test.s.Schedule(hour=9, dayOfMonth=first, dayOfWeek=Mon), @test.s.Schedule(hour=10, dayOfMonth=first, dayOfWeek=Mon)]

        System.out.println(Arrays.toString(Task.class.getAnnotations()));
        // [@test.s.Schedules(value=[@test.s.Schedule(hour=9, dayOfMonth=first, dayOfWeek=Mon), @test.s.Schedule(hour=10, dayOfMonth=first, dayOfWeek=Mon)])]
        //注意:这里获取到的是 Schedules 注解,而不是多个 Schedule 注解
    }
}

可重复注解的原理

使用一个注解来存储重复的注解,编译后的class为:

@Schedules({@Schedule(hour = 9), @Schedule(hour = 10)})
public class Task {
    public Task() {
    }
    public static void main(String[] args) throws Exception {
        Schedule[] ats = (Schedule[])Task.class.getAnnotationsByType(Schedule.class);
        System.out.println(Arrays.toString(ats));
        System.out.println(Arrays.toString(Task.class.getAnnotations()));
    }
}

所谓的可重复注解, 只是编译层面的改动

最后更新于

这有帮助吗?