注解的概述及作用
JDK1.5之后引进的新技术。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
注解其实就是一种标记,在编译时和运行时可以检测到这些标记从而执行一些特殊的操作。
注解分类
JDK普通注解
@Override
标记在成员方法上,用于标识当前方法是重写父类(父接口)方法,简单来说就是告诉编译器下面的方法是重写父类的方法。编译器在对该方法进行编译时会检查是否符合重写规则,如果不符合,编译报错。
如果不写@override注解去直接重写方法,编译器是不会判断你是不是正确重写了父类中的方法的。如重写方法时参数与父类不同,程序是不会提示报错的。当你写了@override注解时,程序会判断你是否正确的重写了父类的对应方法。而且加上此注解后,程序会自动屏蔽父类的方法。
@Deprecated
表示某些类,对象,变量等等不推荐程序员使用。告知程序员“这是旧版本时候的功能了,我们不建议再继续使用旧版本的功能”。
@SuppressWarnings
抑制编译器产生警告信息。
本来可能产生的异常就不会显示给程序员看了。例如:
一个名为arrayList的变量从来未使用过那么他就会报下面这个警告
但是当我们在变量上写上@SuppressWarning(“unused”)就不会有警告出现
@Repeatable
@Repeatable是jdk8中新增的注解。在没有@Repeatable注解的的注解中,在同一个地方使用相同的注解会报错,有了@Repeatable的注解,就可以在同一个地方使用相同的注解。
@Repeatable(Values.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Value {
String value() default "default String";
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Values {
Value[] value();
}
@Value("123")
@Value("456")
public class test {
public static void main(String[] args) throws NoSuchMethodException {
Value[] annotationsByType = test.class.getAnnotationsByType(Value.class);
for (Value value : annotationsByType) {
System.out.println(value.value());
}
}
}
//123
//456
元注解
@Target
指定被修饰的注解的作用范围。一共10个,例如:type(类),field(属性),method(方法)等等。
说白了就是这个注解能放在哪里。@Target(ElementType.TYPE)说明这个注解能放在类的开头。
@Retention
指定了被修饰的注解的生命周期。一共3个:source,class,runtime。
source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略
class:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
这3个生命周期分别对应于:Java源文件(.java文件) —> .class文件 —> 内存中的字节码。
一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
@Documented
指定了被修饰的注解是可以Javadoc等工具文档化
如果一个注解@B,被@Documented标注,那么被@B修饰的类,生成文档时,会显示@B。如果@B没有被@Documented标准,最终生成的文档中就不会显示@B。
@Inherited
指定了被修饰的注解修饰程序元素的时候是可以被子类继承的
我们在Person类中标记了注解,如果该注解被@Inherited注解修饰,我们就可以得出继承于Person类的Student类也同样被注解标记了,如果你要获取该注解的某些的话,肯定获取的也是父类上注解值的那个值
自定义注解
格式:public @Interface 注解名 {属性列表/无属性}
public @interface MyAnno2 {
int num() default 0;//基本数据类型,默认为0
String value();//字符串
Color color();//枚举类型
MyAnno myAnno();//注解
int[] nums();
String[] values();
Color[] colors();
MyAnno[] myAnnos();
}
属性的返回值类型必须是以下几种:
- 基本数据类型
- String类型
- 枚举类型
- 注解
- 以上类型的数组
如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时可以不为属性赋值,它取的是默认值。如果为它再次传入值,那么就发生了对原值的覆盖。
如果只有一个属性需要赋值,并且属性的名称为value,则赋值时value可以省略,可以直接定义值
数组赋值时,值使用{}存储值。如果数组中只有一个值,则可以省略{}。
其实我们对注解进行反编译:
public interface java_annotation.InitDemo extends java.lang.annotation.Annotation {
}
所以,我们可以得知注解的本质就是一个接口,该接口默认继承了Annotation接口。
注解传参:名称 = 返回值类型参数
@MyAnno2(num = 1, value = "value1", color = Color.Blue, myAnno = @MyAnno, nums = {1, 2, 3}, values = {"1", "@", "z"}, colors = {Color.Blue, Color.Red}, myAnnos = {@MyAnno})
利用反射机制解析注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sign {
String methodName();
String className();
}
public class Cat {
@Deprecated
public void eat() {
System.out.println("cat eat fish");
}
@Override
public String toString() {
return "Cat{}";
}
}
通过反射来获取注解中的methodName和className参数
@Sign(className = "java_annotation.Cat", methodName = "eat")
public class testSign {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
Class<testSign> testSignClass = testSign.class;
Sign annotation = testSignClass.getAnnotation(Sign.class);
String className = annotation.className();
String methodName = annotation.methodName();
System.out.println(className);//java_annotation.Cat
System.out.println(methodName);//eat
Class<?> aClass = Class.forName(className);
Object o = aClass.newInstance();
Method method = aClass.getMethod(methodName);
method.invoke(o);//cat eat fish
}
}
获取类对象中的注解对象时,其原理实际上是在内存中生成了一个注解接口的子类实现对象并返回的字符串内容
public class SignImpl implements Sign {
public String methodName() {
return "eat";
}
public String className() {
return "cat eat fish";
}
}