Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
注解也是一种类的类型,他使用的修饰符为 @interface
例如:
public @interface MyTestAnnotation {
}
共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。
作用在代码的注解是
内置注解包括元注解
元注解:(元注解就是自定义注解时,对自定义注解的一个定义)
@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
//@Retention是需要入参的
//源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
//定义在注解内的都是使用注解时所需要传入的参数,如果为一个,就命名为value;
RetentionPolicy value();
}
//RetentionPolicy是声明好的枚举类型
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
//此注解类型的信息只会记录在源文件中,编译时将被编译器丢弃,也就是说
//不会保存在编译好的类信息中
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
//编译器将注解记录在类文件中,但不会加载到JVM中。如果一个注解声明没指定范围,则系统
//默认值就是Class
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
//注解信息会保留在源文件、类文件中,在执行的时也加载到Java的JVM中,因此可以反射性的读取。
RUNTIME
}
//也就是说SOURCE只存在于.java文件,.class文件中就不存在;大部分声明为RUNTIME在jvm中同样生效
@Documented - 标记这些注解是否包含在用户文档中。
@Target - 标记这个注解应该是哪种 Java 成员。
//@Target同样需要入参的;
//源码分析
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
//定义在注解内的都是注解所需要的参数,如果为一个,就命名为value;
ElementType[] value();
}
//可以看到是一个枚举类型的入参,这个枚举类型的入参规定了自定义注解的作用在什么地方;
//比如TYPE就说明,该注解只能在类、接口(包括注释类型)或枚举中使用
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
}
//举个例子
@My
public class Testzhujie {
@My//会报错(写上就会直接报:'@My' not applicable to method)
public int hashCode() {
return super.hashCode();
}
}
@Target(ElementType.TYPE)
@interface My{
}
@Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
从 Java 7 开始,额外添加了 3 个注解:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
也正是反射机制,java也可以称为**“准动态语言”**
//动态语言
/*
动态语言就是可以在运行时改变其结构的语言;
就是说在运行时代码可以根据某些条件改变自身结构
*/
//静态语言
/*
与动态语言相反
*/
//要想了解一个类,必须先要获取到该类的字节码文件对象.
//在Java中,每一个字节码文件,被加载到内存后,都存在一个对应的Class类型的对象
//白话,反射就是通过操作class类型的对象(从字节码文件)获得类的所有属性和方法
得到Class的几种方式
要想了解一个类,必须先要获取到该类的字节码文件对象.
在Java中,每一个字节码文件,被加载到内存后,都存在一个对应的Class类型的对象
1. 如果在编写代码时, 知道类的名称, 且类已经存在, 可以通过
包名.类名.class 得到一个类的 类对象
2. 如果拥有类的对象, 可以通过
Class 对象.getClass() 得到一个类的 类对象
3. 如果在编写代码时, 知道类的名称 , 可以通过
Class.forName(包名+类名): 得到一个类的 类对象
上述的三种方式, 在调用时, 如果类在内存中不存在, 则会加载到内存 ! 如果类已经在内存中存在, 不
会重复加载, 而是重复利用 !
通过class对象 获取一个类的构造方法(Constructor)
//1、将person类加载到内存(已存在的实体类)
Class<?> aClass = Class.forName("com.kfd.test002.Person");
//通过类对象获得无参构造方法
Constructor<?> constructor = aClass.getConstructor();
//通过无参构造方法创建对象
Object o = constructor.newInstance();
System.out.println(o);
//2、通过有参构造方法创建对象
Constructor<?> constructor1 = aClass.getConstructor(String.class, int.class);
//通过有参构造方法创建对象时,需要知道入参类型;
Object instance = constructor1.newInstance("张三", 19);
//打印该对象
System.out.println(instance);
//结果:
Person{name='null', age=0, phoneNumber='null'}
Person{name='张三', age=19, phoneNumber='null'}
//但是通过getConstructor()方法获得的构造方法是非私有的;
//3、通过反射使用私有权限的构造方法创建对象
//获取所有权限的单个构造方法getDeclaredConstructor
//获取所有权限的构造方法数组getDeclaredConstructors
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class);
//设置:构造方法(忽略权限检查)必须设置忽略权限检查,否则会因为权限不够而报错
declaredConstructor.setAccessible(true);
Object instance1 = declaredConstructor.newInstance("老夏");
System.out.println(instance1);
//实体类:
//私有构造方法
private Person(String name) {
this.name = name;
this.age = 99;
}
//结果:
Person{name='老夏', age=99, phoneNumber='null'}
通过Constructor创建对象
常用方法:
newInstance(Object… para)
调用这个构造方法, 把对应的对象创建出来
参数: 是一个Object类型可变参数, 传递的参数顺序 必须匹配构造方法中形式参数列表的顺
序!
setAccessible(boolean flag)
如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的方法)
上面只是通过反射Class类对象获得实体类的无参构造方法和有参构造方法
下面通过Class获得对象中的方法
1.getMethod(String methodName , class.. clss)
根据参数列表的类型和方法名, 得到一个方法(public修饰的)
2. getMethods();
得到一个类的所有方法 (public修饰的)
3. getDeclaredMethod(String methodName , class.. clss)
根据参数列表的类型和方法名, 得到一个方法(除继承以外所有的:包含私有, 共有, 保护, 默认)
4. getDeclaredMethods();
得到一个类的所有方法 (除继承以外所有的:包含私有, 共有, 保护, 默认)
//练习
public static void main(String[] args) throws Exception {
//1、将类加载到内存
Class<?> aClass = Class.forName("com.kfd.test002.Person");
//2、获取无参构造方法
Constructor<?> constructor = aClass.getConstructor();
//3、利用无参构造方法创建对象
Object instance = constructor.newInstance();
//4、获取类的单个方法(需要传递方法名,和入参类型)
Method method = aClass.getMethod("setName", String.class);
//5、执行这个方法(需要传递:执行方法的类对象,和参数)
method.invoke(instance, "李四");
//看结果
System.out.println(instance);
//6、调用私有的setAge方法(使用getDeclaredMethod方法)
Method setAge = aClass.getDeclaredMethod("setAge", int.class);
//7、别忘了设置忽略权限
setAge.setAccessible(true);
setAge.invoke(instance,10);
System.out.println(instance);
}
//setname是共有方法
//setage是私有方法
//结果:
Person{name='李四', age=0, phoneNumber='null'}
Person{name='李四', age=10, phoneNumber='null'}
下面通过Class获得对象的属性
1. getDeclaredField(String filedName)
根据属性的名称, 获取一个属性对象 (所有属性)
2. getDeclaredFields()
获取所有属性
3. getField(String filedName)
根据属性的名称, 获取一个属性对象 (public属性)
4. getFields()
获取所有属性 (public)
获得属性后的常用方法
常用方法:
1. get(Object o );
参数: 要获取属性的对象 获取指定对象的此属性值
2. set(Object o , Object value);
参数1. 要设置属性值的 对象
参数2. 要设置的值 设置指定对象的属性的值
3. getName()
获取属性的名称
4. setAccessible(boolean flag)
如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的属性)
//练习
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("com.kfd.test002.Person");
Constructor<?> constructor = aClass.getConstructor();
Object instance = constructor.newInstance();
//获得对象的属性
Field phoneNumber = aClass.getField("phoneNumber");
//可以通过属性对象对其赋值,需要传入,是为那个对象的属性赋值,第一参数是对象,第二个参数是属性值
phoneNumber.set(instance,"1526475892");
System.out.println(instance);
}
//结果:
Person{name='null', age=0, phoneNumber='1526475892'}
获取注解信息
// 获取类/属性/方法的全部注解对象.注意替换
Annotation[] annotations01 = Class/Field/Method.getAnnotations(); for (Annotation annotation : annotations01) { System.out.println(annotation); }
//练习
public static void main(String[] args) throws Exception {
Class aClass = Class.forName("com.kfd.test003.Book");
Constructor constructor = aClass.getConstructor();
//通过无参构造方法创建对象
Object instance = constructor.newInstance();
//获得类的table_name注解
table_name annotation = (table_name) aClass.getAnnotation(table_name.class);
//打印
String value = annotation.value();
System.out.println(value);
//获得类的所有属性
Field[] fields = aClass.getDeclaredFields();
//循环获得每个属性的注解
for (Field field: fields) {
column_name annotation1 = field.getAnnotation(column_name.class);
System.out.println(annotation1.name()+"类型是:"+annotation1.type()+"长度是:"+annotation1.length());
}
//实体类:
@table_name("book")
public class Book {
@column_name(name = "id",type = "int",length = "11")
private int id;
@column_name(name = "name",type = "varchar",length = "50")
private String name;
@column_name(name = "info",type = "varchar",length = "1000")
private String info;
}
//执行结果
book
id类型是:int长度是:11
name类型是:varchar长度是:50
info类型是:varchar长度是:1000