注解
概念
说明程序的。给计算机看的。与注释的不同之处是注释是用文字描述程序的,给程序员看的。
概念描述:
- JDK1.5之后的新特性
- 说明程序的
- 使用注解:@注解名称
定义
注解(Annotation
),也叫元数据。一种代码级别的说明。它是JDK1.5
及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
注解的作用分类:
- 编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
javadoc 类名
使用这个命令就可以为一个类生成doc文档 - 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【
Override
】
JDK中预定义的一些注解
-
@Override
:检测被该注解标注的方法是否是继承自父类(接口)的 -
@Deprecated
:该注解标注的内容,表示已过时 -
@SuppressWarnings
:压制警告- 一般传递参数
all
@SuppressWarnings("all")
表示压制所有警告 - 该注解如果是使用在方法上,则有关该方法的警告都会被压制
- 该注解如果是使用在类上,则类中的所有的警告都会被压制
- 一般传递参数
自定义注解
格式
元注解
public @interface 注解名称{
属性列表;
}
本质
注解本质上就是一个接口,该接口默认继承Annotation
接口
- 比如自己编写一个注解
public @interface MyAnno{
}
然后将上面的代码编译 `javac MyAnno.java`,会生成`MyAnno.class`文件,然后使用命令 `javap MyAnno.class`可以将编译后的文件反编译重新得到一个java文件,内容如下:
`public interface MyAnno extends java.lang.annotation.Annotation {}`
经过上面的操作可以知道,注解本质上是一个接口,所以接口中可以定义的内容就是注解中的可以定义的内容,不过需要注意的是注解中的所有内容都是称为注解的属性。
属性
上面已经提到过接口中想内容或者说接口中的抽象方法就是注解中的属性,但是这些属性(抽象方法有一些规范需要遵守)
规范
- 属性的返回值类型有下列取值
- 基本数据类型
- String
- 枚举
- 注解
- 以上类型的数组
- 注意注意的是方法的返回值不能是
void
- 定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用
default
关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。 - 如果只有一个属性需要赋值,并且属性的名称是
value
,则value
可以省略,直接定义值即可。 - 数组赋值时,值使用
{}
包裹。如果数组中只有一个值,则{}
可以省略
- 如果定义属性时,使用
定义第一个注解
package top.twolovelypig.anno;
import top.twolovelypig.allEnum.Person;
public @interface MyAnno {
String name() default "张三";
int age();
Person per();
MyAnno2 anno2();
String[] strs();
}
定义第二个注解
package top.twolovelypig.anno;
public @interface MyAnno2 {
}
使用注解
package top.twolovelypig.test;
import top.twolovelypig.allEnum.Person;
import top.twolovelypig.anno.MyAnno;
import top.twolovelypig.anno.MyAnno2;
// 这里的Person.P1是自己建立的一个枚举类而已
@MyAnno(age = 12, anno2 = @MyAnno2, per = Person.P1, strs = "hehe", strsMany = {"11", "22"})
public class TestAnno {
}
从这里使用注解就可以看到为什么说定义的方法作为属性了,在注解中定义的方法名在使用注解时都是作为属性名来使用的。同时可以看到使用时的几个特点
- 注解中
name()
方法给了默认值所以在使用时就可以不用赋值 - 对于字符串如果是有多个值需要使用
{}
赋值
元注解
用于描述注解的注解
@Target
描述注解能够作用的位置
@Target
的源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
可以看到@Target
注解中只有一个方法,并且该方法的名称是value
,意味着在使用该注解时属性名可以省略,该方法的返回值是一个ElementType
数组,可以看下ElementType
的源码:
package java.lang.annotation;
/**
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.4.1 @Target
* @jls 4.1 The Kinds of Types and Values
*/
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
* @since 1.8
*/
TYPE_USE
}
其中有几个常用的值。
ElementType
常用取值:
-
TYPE
:可以作用于类上 -
METHOD
:可以作用于方法上 -
FIELD
:可以作用于成员变量上
@Retention
描述注解被保留的阶段
@Retention
的源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
同样的也是只有一个方法,并且方法名也是value
,该方法的返回值是一个RetentionPolicy
RetentionPolicy
源码
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
-
@Retention(RetentionPolicy.RUNTIME)
:当前被描述的注解,会保留到class
字节码文件中,并被JVM
读取到 -
@Retention(RetentionPolicy.SOURCE)
:当前被描述的注解,会保留到源码阶段 -
@Retention(RetentionPolicy.CLASS)
:当前被描述的注解,会保留到编译阶段
注意:对于我们自定义的注解该属性一般都是定义为 RetentionPolicy.RUNTIME
,也就是运行时阶段。
@Documented
描述注解是否被抽取到api
文档中,比如上面我们自己定义了一个@MyAnno
注解,如果在我们自定义的注解中使用了@Documented
注解,那么我们在使用到@MyAnno
的类中进行api
文档生成的时候就会加上我们自定义的这个注解。
@Inherited
描述注解是否被子类继承,比如我们上面自定义了一个@MyAnno
注解,如果在@MyAnno
注解中我们使用了@Inherited
注解,那么当我们将@MyAnno
注解使用在一个类上面的时候,其子类也会自动加上这个注解。
获取注解中定义的属性值
注解的主要作用是为了替代配置文件,我们在注解中配置一些内容,然后程序在运行的时候获取我们配置的内容,这样就不需要在配置文件中去配置内容了。比如下面的案例是通过注解来获取我们配置的具体的类所有位置以及类中的某一个方法,然后通过注解来拿到这些纸再通过反射来调用这个方法。
创建注解
package top.twolovelypig.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExeMethod {
String className();
String methodName();
}
使用注解(测试类)
package top.twolovelypig.test;
import top.twolovelypig.anno.ExeMethod;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@ExeMethod(className = "top.twolovelypig.test.TestShowMethod", methodName = "show")
public class TestAnno {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 解析注解,获取当前类的字节码文件
Class<TestAnno> testAnnoClass = TestAnno.class;
// 获取该类上面的注解对象(其实就是在内存中生成了一个该注解接口的子类实现对象)
ExeMethod exeMethodImpl = testAnnoClass.getAnnotation(ExeMethod.class);
// 调用注解对象中定义的抽象方法,获取返回值
String className = exeMethodImpl.className(); // 这里获取的结果是我们在注解上配置的“top.twolovelypig.test.TestShowMethod”
String methodName = exeMethodImpl.methodName(); // 这里获取的结果是我们在注解上配置的 “show”
// 加载该类进内存
Class aClass = Class.forName(className);
// 创建该类的对象
Object instance = aClass.newInstance();
// 获取方法对象
Method method = aClass.getMethod(methodName);
// 执行方法
method.invoke(instance);
}
}
TestShowMethod类
package top.twolovelypig.test;
public class TestShowMethod {
public void show(){
System.out.println("hehe");
}
}
执行上面main
方法最终会打印出“hehe”。
网友评论