注解和反射
注解(Annotation)
什么是注解
注解是放在Java源码的类、方法、参数前的一种特殊的“注释”。
所有的注解都继承自
java.lang.annotation.Annotation
Java注解分为三类:
1.编译器使用的注解,SOURCE
类型,在编译期就被丢掉了,如:
@Override
:让编译器检查该方法是否正确地实现了覆写@SuppressWarnings
:告诉编译器忽略此处代码产生的警告
2.工具处理.class
文件使用的注解,CLASS
类型的注解仅保存在class文件中,它们不会被加载进JVM
3.程序运行期间能够读取的注解,RUNTIME
类型的注解会被加载进JVM,并且在运行期可以被程序读取
注解定义:
使用@interface
定义一个注解:
public @interface Annotation1{
}
定义注解时还可以定义配置参数。配置参数可以包括:
- 所有基本类型;
- String;
- 枚举类型;
- 基本类型、String、Class以及枚举的数组。
public @interface TestAnnotation{
int id() default 0; // 可设置默认值
String name();
String value(); //最常用的参数应当命名为value
}
元注解
修饰其他注解的注解称为元注解。Java标准库定义了一些常用的元注解:
@Target
使用@Target
定义注解可以应用在什么位置(类,字段,方法构造方法,方法参数)
@Target({ElementType.METHOD, ElementType.TYPE}) //定义了该注解可以放在类(TYPE)和方法(METHOD)上
@interface TestAnnotation{
int id() default 0;
String name();
}
ElementType
是一个枚举类型,里面的值包括:
- 类或接口:
ElementType.TYPE
;- 字段:
ElementType.FIELD
;- 方法:
ElementType.METHOD
;- 构造方法:
ElementType.CONSTRUCTOR
;- 方法参数:
ElementType.PARAMETER
。
@Retention
@Retention
定义了注解的生命周期
@Retention(RetentionPolicy.RUNTIME) // 运行期,最常使用
@Target({ElementType.METHOD, ElementType.TYPE})
@interface TestAnnotation{
int id() default 0;
String name();
}
RetentionPolicy是一个枚举类型,里面的值包括:
- 仅编译期:
RetentionPolicy.SOURCE
;- 仅class文件:
RetentionPolicy.CLASS
;- 运行期:
RetentionPolicy.RUNTIME
。
@Inherited
@Inherited
定义子类是否继承父类定义的注解,@Inherited
仅针对@Target(ElementType.TYPE)
类型的annotation
有效,并且仅针对class
的继承,对interface
的继承无效
@Inherited // 子类可继承父类的注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@interface TestAnnotation{
int id() default 0;
String name();
}
注解的使用
如何使用注解完全由工具决定。SOURCE
类型的注解主要由编译器使用,因此我们一般只使用,不编写。CLASS
类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。只有RUNTIME
类型的注解不但要使用,还经常需要编写。
此处注解的使用针对于RUNTIME
类型的注解,一般结合反射机制使用注解。
import java.lang.annotation.*;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws NoSuchFieldException {
Student student = new Student();
Class cls = student.getClass();
ClsAnnotation clsAnnotation = (ClsAnnotation)cls.getAnnotation(ClsAnnotation.class); // 获取类注解
System.out.println(clsAnnotation.value()); // 输出"类注解"
Field name = cls.getDeclaredField("name");
FieldAnnotation fieldAnnotation = name.getAnnotation(FieldAnnotation.class); // 获取字段注解
System.out.println(fieldAnnotation.columnName()); // 输出"db_name"
System.out.println(fieldAnnotation.type()); // 输出"varchar"
System.out.println(fieldAnnotation.length()); // 输出"50"
}
}
@ClsAnnotation("类注解")
class Student{
@FieldAnnotation(columnName = "db_id", type = "int", length = 10)
private int id;
@FieldAnnotation(columnName = "db_name", type = "varchar", length = 50)
private String name;
@FieldAnnotation(columnName = "db_age", type = "int", length = 3)
private int age;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ClsAnnotation{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldAnnotation{
String columnName();
String type();
int length();
}
反射(Reflection)
反射机制允许程序在执行期借助于Refelection API获取一个类的所有信息,并能够直接操作任意对象的内部属性及方法。
Class类
JVM每加载一种class
(或interface
),如果是第一次加载,就为其创建一个Class
实例,这个实例对象包含了类class
完整的结构信息。这个对象就如同一面镜子,透过这个对象可以看到完整的类的结构信息。
通过一个class
获取对应的Class
实例,有三种方法:
// 1.通过类名获得
Class cls1 = String.class;
// 2.通过实例对象获得
String s = "Hello";
Class cls2 = s.getClass();
// 3.通过完整的包名
Class cls3 = Class.forName("java.lang.String");
Class
实例是唯一的,一个类只有一个Class
实例。所以上面三种方法获得的实例实际上是同一个。
反射的优点和缺点:
优点:实现动态创建对象和编译,体现出很大的灵活性
缺点:对性能有影响,性能比较——不使用反射 > 检测关闭的反射 > 正常的反射
类的加载过程
1.类的加载(Load):类加载器将类的class文件读入内存,并将这些静态数据转换成方法区运行数据结构,然后为之创建一个java.lang.Class对象;
2.类的链接(Link):将类的二进制数据合并到JVM运行状态之中的过程;
3.类的初始化(Initialize):JVM负责对类进行初始化。
Java类加载是动态加载的,只有该类被用到时才加载
访问字段
Class
类提供了以下几个方法来获取Field
:
Field getField(name)
:根据字段名获取某个public
的field
(包括父类)Field getDeclaredField(name)
:根据字段名获取当前类的某个field
(不包括父类)Field[] getFields()
:获取所有public
的field
(包括父类)Field[] getDeclaredFields()
:获取当前类的所有field
(不包括父类)
一个Field
对象包含一个字段的所有信息:
getName()
:返回字段名称,例如,"name"
;getType()
:返回字段类型,也是一个Class
实例,例如,String.class
;getModifiers()
:返回字段的修饰符,它是一个int
,不同的bit表示不同的含义。
获取/设置字段值:
1.首先获取一个类的Class
实例
2.通过这个Class
实例去获取对应的字段,private
字段要使用getDeclaredField(String fieldname)
方法
3.通过字段对象的get(Object obj)
方法获取对应的字段值,private
字段要setAccessible(true)
才能访问
4.通过字段对象的set(Object obj, Object value)
方法设置字段值
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Object person = new Person("sen");
Class cls = person.getClass();
Field field = cls.getDeclaredField("name");
field.setAccessible(true); // 由于name字段是private的,必须将setAccessible设置true才能访问
Object value = field.get(person);
System.out.println(value); // 输出"sen"
field.set(person, "chen");
value = field.get(person);
System.out.println(value); // 输出"chen"
}
}
class Person{
private String name;
public Person(String name){
this.name = name;
}
}
调用方法
Class
类提供了以下几个方法来获取Method
:
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method
(不包括父类)Method[] getMethods()
:获取所有public
的Method
(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有Method
(不包括父类)
获取特定方法时,除了要传入方法名外,还要传入对应参数的Class实例,因为Java存在方法重载,可能会存在同名的方法,必须通过参数签名进行区分。如果为无参方法,第二个参数传入null
一个Method
对象包含一个方法的所有信息:
getName()
:返回方法名称,例如:"getName"
;getReturnType()
:返回方法返回值类型,也是一个Class实例,例如:String.class
;getParameterTypes()
:返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
;getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit表示不同的含义。
调用方法:
1.首先获取一个类的Class
实例
2.通过这个Class
实例去获取对应的方法对象,private
方法要使用getDeclaredMethod(name, Class...)
方法
3.通过方法对象的invoke(Object obj, Class...)
方法调用对象的方法,private
方法要setAccessible(true)
才能访问
4.如果获得的Method
是一个静态方法,调用invoke
方法时传入的第一个参数永远为null
5.如果是无参方法,第二个参数为null
6.调用方法时遵循多态,根据传入的对象调用其对应的方法
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Student student = new Student();
student.setName("sen");
Class cls = student.getClass();
Method getName = cls.getMethod("getName"); // 获得父类public方法
Method setScore = cls.getDeclaredMethod("setScore", int.class); // 获得子类private且带参的方法
Method getScore = cls.getMethod("getScore"); // 获得子类public方法
setScore.setAccessible(true); // 调用私有方法必须设置setAccessible(true)
// 调用方法
System.out.println(getName.invoke(student));
setScore.invoke(student, 99);
int score = (int)getScore.invoke(student);
System.out.println(score);
}
}
class Student extends Person{
private int score;
private void setScore(int score) {
this.score = score;
}
public int getScore() {
return score;
}
}
class Person{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
调用构造方法
通过Class
实例获取Constructor
的方法如下:
getConstructor(Class...)
:获取某个public
的Constructor
;getDeclaredConstructor(Class...)
:获取某个Constructor
;getConstructors()
:获取所有public
的Constructor
;getDeclaredConstructors()
:获取所有Constructor
。
调用构造方法:
首先获取一个类的Class
实例
2.通过这个Class
实例去获取对应的构造方法对象Constructor
,private
构造方法要使用getDeclaredMethod(name, Class...)
方法
3.通过构造方法对象的newInstance(Class...)
方法调用构造方法创建对象,private
构造方法要setAccessible(true)
才能调用
4.默认newInstance()
是调用无参构造器
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor constructor = Student.class.getConstructor(String.class, int.class);
Student student = (Student) constructor.newInstance("chen", 24);
System.out.println(student);
}
}
class Student extends Person{
private int age;
public Student(String name, int age){
super(name);
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
'}';
}
}
class Person{
private String name;
public Person(String name){
this.name = name;
}
}
获取注解
读取Annotation:
Class.getAnnotation(Class)
Field.getAnnotation(Class)
Method.getAnnotation(Class)
Constructor.getAnnotation(Class)
参数中的
Class
为注解的Class实例
判断某个注解是否存在于Class
、Field
、Method
或Constructor
:
Class.isAnnotationPresent(Class)
Field.isAnnotationPresent(Class)
Method.isAnnotationPresent(Class)
Constructor.isAnnotationPresent(Class)