注解,可以看作是对 一个 类/方法 的一个扩展的模版,每个 类/方法 按照注解类中的规则,来为 类/方法 注解不同的参数,在用到的地方可以得到不同的 类/方法 中注解的各种参数与值
什么是注解
-
注解是JDK5开始引用的技术
-
注解说白了就是代码里的特殊标志,这些标志可以在编译,类加载,运行时被读取,并执行相应的处理,以便于其他工具补充信息或者进行部署
-
注解的格式:
注解是以@+注解名在代码中存在的,比如:@Value,@Data…,同时注解还可以添加参数,如@suppressWarnings(“unchecked”)
-
注解的使用范围
取值 注解使用范围 METHOD 可用于方法上 TYPE 可用于类或者接口上 ANNOTATION_TYPE 可用于注解类型上(被@interface修饰的类型) CONSTRUCTOR 可用于构造方法上 FIELD 可用于域上 LOCAL_VARIABLE 可用于局部变量上 PACKAGE 用于记录java文件的package信息 PARAMETER 可用于参数上
基本注解使用
以下三个注解是JDK提供的内置注解,当然不止这些,这里用三个注解来讲解如何使用他们的
@Override
作用:检查该方法是否是重写方法。
就好比这里有个User类 继承了Object类,这里重写父类的toString()方法
public class User extends Object{
@Override
public String toString() {
return super.toString();
}
}
上面这样完全没有问题,但是我们一个不小心就把toString的写成了tostring那么就会出现以下情况:
- IDEA会报错,提示:方法未从其超类重写方法
- 运行该方法的时候会报错,提示:方法不会覆盖或实现超类型的方法

所以这里就印证了上面关于@Override的说明,避免了出现低级错误,没错,就是这么个功能。
@Deprecated
作用: 标记过时方法。如果使用该方法,会报编译警告。(删除线,这个见了不少了吧)。

@SuppressWarnings
作用:指示编译器去忽略注解中声明的警告。

上面的代码,通常在一个方法中我们定义了一个变量但是没有使用它,在IDEA中就会显示成灰色,而且会有相应提示,下面我们使用注解来看下是什么效果

我们发现使用该注解以后,IDEA给提示的警告没有了,字体也变成了黑色。
元注解
元注解是负责对其它注解进行说明的注解,自定义注解时可以使用元注解。Java 5 定义了 4 个注解,分别是 @Documented、@Target、@Retention 和 @Inherited。Java 8 又增加了 @Repeatable 和 @Native 两个注解。这些注解都可以在 java.lang.annotation 包中找到。下面主要介绍每个元注解的作用及使用。
@Target
作用:该注解用来指定一个注解的使用范围,即被@Target修饰的注解可以用在什么地方。
@Target只能被用来标注“Annotation类型”,而且它被用来指定Annotation的ElementType属性。

这里我们自定义了一个@MeiShaYong注解,通过给@Target注解的value属性传了俩参数,指定了咱的注解可以用在方法和类上面,为了测试,我们在成员变量上也加了该注解,发现IDE高亮警告,并提示:‘@MeiShaYong’ 不适用于字段。
我们可以查看@Target注解的value属性的枚举类ElementType,看看里面具体有哪些范围可供我们使用:
| 名称 | 说明 |
|---|---|
| TYPE | 类、接口(包括注释类型)或枚举声明 |
| FIELD | 字段声明(包括枚举常量) |
| METHOD | 方法声明 |
| PARAMETER | 形参声明 |
| CONSTRUCTOR | 构造方法声明 |
| LOCAL_VARIABLE | 局部变量声明 |
| ANNOTATION_TYPE | 注释类型声明 |
| PACKAGE | 包声明 |
| TYPE_PARAMETER | 用来标注类型参数 // 如下是该注解的使用例子 @Target(ElementType.TYPE_PARAMETER) public @interface TypeParameterAnnotation {} public class TypeParameterClass<@TypeParameterAnnotation T> { public <@TypeParameterAnnotation U> T test(T t) { return null; } } |
| TYPE_USE | 能标注任何类型名称,包括上面的类型参数 @Target(ElementType.TYPE_USE) public @interface TypeUseAnnotation {} public static @TypeUseAnnotation class TypeUseClass<@TypeUseAnnotation T> extends @TypeUseAnnotation Object { public void test(@TypeUseAnnotation T t) throws @TypeUseAnnotation Exception { } } // 如下注解的使用都是合法的 public static void main(String[] args) throws Exception { TypeUseClass<@TypeUseAnnotation String> typeUseClass = new @TypeUseAnnotation TypeUseClass<>(); typeUseClass.test(“”); List<@TypeUseAnnotation Comparable> list1 = new ArrayList<>(); List<? extends Comparable> list2 = new ArrayList<@TypeUseAnnotation Comparable>(); @TypeUseAnnotation String text = (@TypeUseAnnotation String)new Object(); java.util. @TypeUseAnnotation Scanner console = new java.util.@TypeUseAnnotation Scanner(System.in); } |
@Retention
作用:标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Retention只能被用来标注“Annotation类型”,而且它被用来指定Annotation的RetentionPolicy属性。
我们可以查看@Target注解的value属性的枚举类RetentionPolicy,查看有哪些方式保存
| 名称 | 说明 |
|---|---|
| SOURCE | 若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该 Annotation 就没用了。 例如," @Override" 标志就是一个 Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,“@Override” 就没有任何作用了。 |
| CLASS | 编译器将Annotation存储于类对应的.class文件中,即在 class 文件中有效(即 class 保留)。默认行为 |
| RUNTIME | 编译器将Annotation存储于class文件中,并且可由JVM读入,即在运行时有效(即运行时保留) |
生命周期大小排序为 SOURCE < CLASS < RUNTIME,前者能使用的地方后者一定也能使用。如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 代码自动生成),就用 CLASS 注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
//@Retention(RetentionPolicy.SOURCE) 如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解
//@Retention(RetentionPolicy.CLASS) 如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 代码自动生成),就用 CLASS 注解
@Retention(RetentionPolicy.RUNTIME)// 如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解
@interface RetentionAno{}
@Documented
作用:用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档。默认情况下,JavaDoc 是不包括注解的,但如果声明注解时指定了 @Documented,就会被 JavaDoc 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。
定义 注解时,@Documented 可有可无;若没有定义,则 注解不会出现在 javadoc 中。
/**
* 测试文档注解1
*/
@DocumentedAno
public class DocumentedAnnotation {
/**
* 测试文档注解2
*/
@DocumentedAno
public void test(){}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@interface DocumentedAno{}
以下使用javadoc工具来生成java文档
执行以下命令:javadoc -d doc .\DocumentedAnnotation.java -charset utf-8 -encoding utf-8
PS D:\LEO\个人项目\study\annotation\src\main\java> javadoc -d doc .\DocumentedAnnotation.java -charset utf-8 -encoding utf-8
正在加载源文件.\DocumentedAnnotation.java...
正在构造 Javadoc 信息...
正在创建目标目录: "doc\"
标准 Doclet 版本 1.8.0_301
正在构建所有程序包和类的树...
正在生成doc\DocumentedAnnotation.html...
正在生成doc\package-frame.html...
正在生成doc\package-summary.html...
正在生成doc\package-tree.html...
正在生成doc\constant-values.html...
正在构建所有程序包和类的索引...
正在生成doc\overview-tree.html...
正在生成doc\index-all.html...
正在生成doc\deprecated-list.html...
正在构建所有类的索引...
正在生成doc\allclasses-frame.html...
正在生成doc\allclasses-noframe.html...
正在生成doc\index.html...
正在生成doc\help-doc.html...
点开我们生成好的文档,如下:

@Inherited
作用:@Inherited 是一个标记注解,用来指定该注解可以被继承。使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。就是说如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。
@Inherited只能被用来标注“Annotation类型”,它所标注的Annotation具有继承性。
下面我们来测试一下该注解:
@InheritedAnno
public class A {
public static void main(String[] args) {
System.out.println(A.class.getAnnotation(InheritedAnno.class));
System.out.println(B.class.getAnnotation(InheritedAnno.class));
System.out.println(C.class.getAnnotation(InheritedAnno.class));
System.out.println(D.class.getAnnotation(InheritedAnno.class));
}
}
class B extends A{}
class C extends B{}
class D {}
@Inherited
@Target(value = ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface InheritedAnno {}
输出如下:
@InheritedAnno()
@InheritedAnno()
@InheritedAnno()
null
@Repeatable
@Repeatable 注解是 Java 8 新增加的,它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解 “容器”。
比如有个需求,要求给一个方法通过注解的方式设置多角色,其代码实现如下:
Java 8 之前的做法
public @interface Role {
String roleName();
}
public @interface Roles {
Role[] roles();
}
public class RolesTest {
@Roles(roles = {@Role(roleName = "role1"), @Role(roleName = "role2")})
public String doString(){
return "具备有多个角色权限";
}
}
Java 8 之后增加了重复注解,使用方式如下
public @interface Roles {
Role[] value();
}
@Repeatable(Roles.class)
public @interface Role {
String roleName();
}
public class RolesTest {
@Role(roleName = "role2")
@Role(roleName = "role1")
public String doString(){
return "具备有多个角色权限";
}
}
@Native
使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。
一个很好的例子是serialVersionUIDjava 8 类的 @native。
这样,我们确保 java 程序的扩展能够轻松引用串行版本的 uid,从而确保我们引用正确的类。
自定义注解
定义方式:
- 使用@interface 注解名称 {} 的格式定义注解
- 要使定义注解生效,最少需要定义这俩元注解,@Target和@Retention
- 定义的注解可以有多个入参,同时每个参数都可以设定默认值
- 不能使用public关键字修饰注解
- 跟类不同的是,在注解的作用域中的定义的age(),name()…我们称之为参数,而不是类中的方法
public class CusAnnotation {
@User(age = 28,name = "lyq",favorite = {"play","run"})
public void testAno(){}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface User {
int age() default 18;
String name() default "leo";
String[] favorite() default {"read","write"};
}
上面就定义了一个完整的注解
小技巧:如果定义的注解只有一个参数,那么建议参数名称是value,这样在使用注解的时候就可以直接在参数中输入值了,若定义的参数名不是value,那么在注解中就必须使用定义的参数名来传参了。如下:
@interface User { String name(); } public class CusAnnotation { @User(name = "lyq") public void foo(){} }如果使用value 效果如下:
@interface User { String value(); } public class CusAnnotation { @User("lyq") public void foo(){} }
反射机制
Java Reflection(Java 反射)
以下ORACLE官方对反射机制的描述:
Reflection is a feature in the Java programming language. It allows an executing Java program to examine or “introspect” upon itself, and manipulate internal properties of the program. For example, it’s possible for a Java class to obtain the names of all its members and display them.
The ability to examine and manipulate a Java class from within itself may not sound like very much, but in other programming languages this feature simply doesn’t exist. For example, there is no way in a Pascal, C, or C++ program to obtain information about the functions defined within that program.
反射是Java编程语言中的一个特性。它允许正在执行的Java程序能够检查自身运行状态信息并能够操纵程序的内部属性。比如反射机制可以使一个类在运行时获得自身所有成员的名字,并且展示出来。这种在程序中检查和操控自身的能力不多见,而且在C/C++中没有这种特性。
反射可以实现功能:
- 在运行时获取类自身的相关信息,比如类名,类的修饰符,类的成员信息
- 在运行时通过反射,获取类的构造器,创建新的对象
- 在运行时通过反射调用执行类的方法。(包括private 修饰的方法)
- 在运行时通过反射修改字段值(private 修饰的亦可)
- 在运行时通过反射处理注解
- 生成动态代理
获取反射对象
获取反射对象的方式有如下几种:
-
Class<User> userClass = User.class; -
Class userClass = new User().getClass(); -
Class userClass = Class.forName("cc.leeleo.demo01.User");
下面我们来获取反射对象。
public class Reflection {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Class.forName("cc.leeleo.demo01.User");
Class c2 = User.class;
Class c3 = new User().getClass();
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
}
}
class User{
private String name;
public int age;
protected String sex;
}
输出:
460141958
460141958
460141958
通过上面的代码我们可以发现,当一个类被实例化以后,那么它只会有一个Class对象存在内存中,而这个Class中就包含了这个类的所有信息。
获取类的名字
获取包名+类名:
System.out.println(clazz.getName());
获取类名:
System.out.println(c1.getSimpleName());
输出:
cc.leeleo.demo01.User
User
获取类的属性
获取指定名称的属性(public权限)
如果获取private和protected权限的属性,则会报错。
Field field = c1.getField("age");
获取指定名称的属性(所有权限)
可以获取private、protected、public的属性
Field declaredField = c1.getDeclaredField("sex");
获取所有属性(public权限)
Field[] fields = c1.getFields();
获取所有属性(private、protected、public)
虽说可以获得所有权限的属性,但是它获取不了父类的属性
c1.getDeclaredFields()
获取类的方法
获取当前类指定名称的方法(public权限)
getMethod第一个参数往后都是可变参数,可以传多个
Method method = c1.getMethod("setName",String.class);
获取当前类指定名称的方法(所有权限)
可以获取private、protected、public的方法
Method declaredMethod = c1.getDeclaredMethod("setName",String.class);
获取当前类以及所有父类的方法(public权限)
Method[] methods = c1.getMethods();
获取当前类的所有方法(所有权限)
可以获取当前类的private、protected、public的方法
Method[] declaredMethods = c1.getDeclaredMethods();
获取类的构造器
获取指定参数的构造器(public权限)
getConstructor方法接受可变参数,可传多个
Constructor constructor = c1.getConstructor(String.class);
获取指定参数的构造器(所有权限)
可以获取当前类的private、protected、public的构造方法
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class);
获取当前类的所有构造器(public权限)
Constructor[] constructors = c1.getConstructors();
获取当前类的所有构造器(所有权限)
可以获取当前类的private、protected、public的构造方法
Constructor[] declaredConstructors = c1.getDeclaredConstructors();
获取方法参数和返回参数类型
获取方法的参数类型
public class Test {
public static void main(String[] args) {
Class clazz = Class.forName("cc.leeleo.demo01.User");
//获取反射对象方法
Method paramTypeMethod = clazz.getDeclaredMethod("testParamFun", Map.class, List.class,User.class,String.class);
//获取方法中的参数类型
Class[] parameterTypes = paramTypeMethod.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println(parameterType);
}
}
}
class User{
public void testParamFun(Map<String,User> userMap, List<User> userList,User user,String name){}
}
输出:
interface java.util.Map
interface java.util.List
class cc.leeleo.demo01.User
class java.lang.String
我们发现 通过反射方法的getParameterTypes()获取到的参数打印出来并不是我们想象中的参数类型,泛型的参数没有很好的展示出来,如果单是获取非泛型的参数类型还是很方便的,可以使用该方法。
获取方法的泛型参数类型
public class Test {
public static void main(String[] args) {
Class clazz = Class.forName("cc.leeleo.demo01.User");
//获取反射对象方法
Method paramTypeMethod = clazz.getDeclaredMethod("testParamFun", Map.class, List.class,User.class,String.class);
//获取反射方法的泛型参数类型
Type[] genericParameterTypes = paramTypeMethod.getGenericParameterTypes();
for (int i = 0; i < genericParameterTypes.length; i++) {
System.out.println("反射方法testParamFun的第"+(i+1)+"个参数为:"+genericParameterTypes[i]);
}
}
}
class User{
public void testParamFun(Map<String,User> userMap, List<User> userList,User user,String name){}
}
输出:
反射方法testParamFun的第1个参数为:java.util.Map<java.lang.String, cc.leeleo.demo01.User>
反射方法testParamFun的第2个参数为:java.util.List<cc.leeleo.demo01.User>
反射方法testParamFun的第3个参数为:class cc.leeleo.demo01.User
反射方法testParamFun的第4个参数为:class java.lang.String
通过对比发现,如果方法附带泛型参数类型,那么 通过getGenericParameterTypes()获取的类型更为直观
获取方法返回值的泛型类型
public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class clazz = Class.forName("cc.leeleo.demo01.User");
//获取反射对象方法
Method paramTypeMethod = clazz.getDeclaredMethod("testReturnFun");
//获取方法的泛型返回类型
Type genericReturnType = paramTypeMethod.getGenericReturnType();
System.out.println(genericReturnType);
}
}
class User{
public List<User> testReturnFun(){}
}
输出:
java.util.List<cc.leeleo.demo01.User>
动态创建对象
动态创建对象的两种方式:
-
通过反射对象创建
需要注意:使用反射对象的newInstance()方法来创建对象,需要被反射的对象拥有一个public的无参构造函数。
其本质是调用无参构造器,使用起来相对受限,因为该方法只能通过无参构造器来实例化对象
public class Test { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { Class clazz = Class.forName("cc.leeleo.demo01.User"); User user = (User) clazz.newInstance(); System.out.println(user); } } class User{ public User(){ } } -
通过获取构造器构造
通过构造器实例化对象相对方便,使用也不受限,即使没有public的无参构造器也不影响实例化对象。
public class Test { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Class clazz = Class.forName("cc.leeleo.demo01.User"); Constructor constructor = clazz.getConstructor(String.class,int.class,boolean.class); User user = (User) constructor.newInstance("张三",18,true); } } class User{ public User(String name,int age,boolean sex){ } }
动态调用对象方法
通过上面的方法我们已经可以获取到一个对象了,当然我们也可以调用其内部的public方法,如果我们想要调用其内部的私有方法呢?
下面我们通过反射来完成对象的私有方法调用:
public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//1.获取反射对象
Class clazz = Class.forName("cc.leeleo.demo01.User");
//2.获取构造方法
Constructor constructor = clazz.getConstructor(String.class,int.class,boolean.class);
//3.实例化对象
User user = (User) constructor.newInstance("张三",18,true);
//4.获取私有方法
Method write = clazz.getDeclaredMethod("write", String.class);
//5.忽略权限检查,使得我们可以调用private权限的方法,true是关闭,false是打开(默认)
write.setAccessible(true);
//6.执行私有方法
write.invoke(user,"去执行对象的私有方法...");
}
}
@Data
class User{
private String name;
private int age;
private boolean sex;
private void write(String txt){
System.out.println(txt);
}
public User(String name, int age, boolean sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
}
通过反射获取注解
下面自定义两个注解,给实体类User增加该注解,最后通过反射的方式来获取注解的值
package cc.leeleo.demo01;
import java.lang.annotation.*;
import java.lang.reflect.Field;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Column{
String name() default "";
String type() default "varchar";
int length() default 255;
}
@Table("db_user")
class User{
@Column(name = "id",type = "int",length = 19)
private int id;
@Column(name = "name",length = 50)
private String name;
}
public class Test {
public static void main(String[] args) throws ClassNotFoundException{
//1.获取反射对象
Class clazz = Class.forName("cc.leeleo.demo01.User");
//2.获取类的注解对象
Table table = (Table)clazz.getDeclaredAnnotation(Table.class);
System.out.println("表名:"+table.value());
//3.获取所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//4.获取字段注解对象
Column column = field.getAnnotation(Column.class);
System.out.println(field.getName()+"的字段名:"+column.name());
System.out.println(field.getName()+"的类型:"+column.type());
System.out.println(field.getName()+"的字段长度"+column.length());
}
}
}