什么是注解?
百度百科:
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
①编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】
② 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
③编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
通俗一点就是说:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。
再通俗一点来讲,就是给类、接口、方法、属性等。。。贴上一个标签,来新添他们的作用。
注解的定义
注解通过 @interface 关键字进行定义。
public @interface Annotation {
}
使用注解则是在类或者方法等定义的上方添加@注解名。
@Annotation
public class Test {
}
Java注解分类:分为 1 JDK基本注解、2 JDK元注解、3 自定义注解,基本注解和自定义注解都是在元注解的基础上进行的。
元注解
元标签有 @Retention、@Target、@Inherited、@Documented、@Repeatable 5 种。
作用:元注解用于修饰其他的注解。
1、@Retention:定义注解的保留策略
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) //默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME) //注解会在class字节码文件中存在,在运行时可以通过反射获取到
如果不是特殊情况,大多情况都是使用RUNTIME
2,@Target:指定被修饰的Annotation可以放置的位置(被修饰的目标)
@Target(ElementType.TYPE) //接口、类、枚举
@Target(ElementType.FIELD) //属性
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE) //局部变量
@Target(ElementType.ANNOTATION_TYPE) //注解
@Target(ElementType.PACKAGE) //包
也可以指定多个位置,例如:
@Target({ElementType.METHOD, ElementType.TYPE}),也就是此注解可以在方法和类上面使用,用逗号隔开
3,@Inherited:指定被修饰的Annotation将具有继承性 ,
也就比如A有@Inherited注解,那么继承他的子类也会有他的所有注解。
4,@Documented:它的作用是能够将注解中的元素包含到 Javadoc 中去。
5,@Repeatable:Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。
什么是容器注解呢?就是用来存放其它注解的地方。它本身也是一个注解。
我们再看看代码中的相关容器注解。
@interface Persons {
Person[] value();
}
按照规定,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组,注意它是数组。
如果不好理解的话,可以这样理解。Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。到了这里接下来给大家讲讲注解的属性.
注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Documented
public @interface MyAnnotation1 {
String name();
int id();
}
上面代码定义了 MyAnnotation1 这个注解中拥有 id 和 name两个属性。在使用的时候,我们应该给它们进行赋值。
@MyAnnotation1(id=3,name="zhangsan")
public class Test {
}
注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Documented
public @interface MyAnnotation1 {
String name() default "zhangsan";
int id() default 1;
}
当你设置了默认值后,你调用的时候就可以不用再赋值了。
@MyAnnotation1()
public class Test {
}
另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Documented
public @interface MyAnnotation1 {
String value() ;
}
调用:
方法1:
@MyAnnotation1("zhangsan")
public class Test {
}
方法2:
@MyAnnotation1(value="zhangsan")
public class Test {
}
调用的这两个方式效果是一样的。
最后,还需要注意的一种情况是一个注解没有任何属性。比如:
public @interface Test3 {}
那么在应用这个注解的时候,括号都可以省略。
@Test3
public class Test {
}
Java 预置的注解
1,@Deprecated
这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。
public class Hero {
@Deprecated
public void say(){
System.out.println("Noting has to say!");
}
public void speak(){
System.out.println("I have a dream!");
}
}
2,@Override
这个大家应该很熟悉了,提示子类要复写父类中被 @Override 修饰的方法也就是重写。
3,@SuppressWarnings
阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。
4,@SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
@SafeVarargs // Not actually safe!
static void m(List<String>... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; // Semantically invalid, but compiles without warnings
String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}
上面的代码中,编译阶段不会报错,但是运行时会抛出 ClassCastException 这个异常,所以它虽然告诉开发者要妥善处理,但是开发者自己还是搞砸了。
Java 官方文档说,未来的版本会授权编译器对这种不安全的操作产生错误警告。
@FunctionalInterface
函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。
函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。
比如
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
我们进行线程开发中常用的 Runnable 就是一个典型的函数式接口,上面源码可以看到它就被 @FunctionalInterface 注解。
可能有人会疑惑,函数式接口标记有什么用,这个原因是函数式接口可以很容易转换为 Lambda 表达式。这是另外的主题了,有兴趣的同学请自己搜索相关知识点学习。
注解与反射
注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
然后通过 getAnnotation() 方法来获取 Annotation 对象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
或者是 getAnnotations() 方法:
public Annotation[] getAnnotations() {}
前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。
如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了。比如:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Documented
public @interface MyAnnotation1 {
String name() default "zhangsan";
int id() default 1;
}
@TestAnnotation()
public class Test {
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(MyAnnotation1.class);
if ( hasAnnotation ) {
TestAnnotation testAnnotation = Test.class.getAnnotation(MyAnnotation1.class);
System.out.println("id:"+testAnnotation.id());
System.out.println("name:"+testAnnotation.name());
}
}
}
结果:
id:1
name:zhangsan
上面的例子中,只是检阅出了注解在类上的注解,其实属性、方法上的注解照样是可以的。同样还是要假手于反射。
@TestAnnotation(msg="hello")
public class Test {
@Check(value="hi")
int a;
@Perform
public void testMethod(){}
@SuppressWarnings("deprecation")
public void test1(){
Hero hero = new Hero();
hero.say();
hero.speak();
}
public static void main(String[] args) {
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if ( hasAnnotation ) {
TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
//获取类的注解
System.out.println("id:"+testAnnotation.id());
System.out.println("msg:"+testAnnotation.msg());
}
try {
Field a = Test.class.getDeclaredField("a");
a.setAccessible(true);
//获取一个成员变量上的注解
Check check = a.getAnnotation(Check.class);
if ( check != null ) {
System.out.println("check value:"+check.value());
}
Method testMethod = Test.class.getDeclaredMethod("testMethod");
if ( testMethod != null ) {
// 获取方法中的注解
Annotation[] ans = testMethod.getAnnotations();
for( int i = 0;i < ans.length;i++) {
System.out.println("method testMethod annotation:"+ans[i].annotationType().getSimpleName());
}
}
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
结果:
id:-1
msg:hello
check value:hi
method testMethod annotation:Perform
总结:
注解的使用场景
我相信博文讲到这里大家都很熟悉了注解,但是有不少同学肯定会问,注解到底有什么用呢?
对啊注解到底有什么用?
我们不妨将目光放到 Java 官方文档上来。
文章开始的时候,我用标签来类比注解。但标签比喻只是我的手段,而不是目的。为的是让大家在初次学习注解时能够不被那些抽象的新概念搞懵。既然现在,我们已经对注解有所了解,我们不妨再仔细阅读官方最严谨的文档。
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多用处,主要如下:
提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
值得注意的是,注解不是代码本身的一部分。