Java注解和反射总结
- 1. 注解(Annotation)
- 2. 反射(Reflection)
1. 注解(Annotation)
java注解又称为java标注,是 JDK5.0 引入的一种注释机制。作用主要是对程序作出解释,同时可以被其他程序读取。java一共定义了7个注解,其中三个在java.lang中,剩下四个在java.lang.annotation中。
内置注解:
- @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。如下,表示toString()是继承的方法。
- @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。如下,由于标注了test1,此时会提示test1是已经过时的方法。
- @SuppressWarnings - 指示编译器去忽略注解中声明的警告。如下禁止所有的警告。
元注解:
- @Target - 标记这个注解应该是哪种 Java 成员。如下图,由于是定义作用在方法上,在test3上直接引用即可。而如果作用在类上就会报错。
- @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。如下,表示在运行时可以通过反射进行访问。
- @Documented - 标记这些注解是否包含在用户文档中。如下再加上此注解。
- @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)。如下注解所示。
从 Java 7 开始,额外添加了 3 个注解:
- @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
- @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
结论:Annotation 的每一个实现类,都 “和 1 个 RetentionPolicy 关联” 并且 " 和 1~n 个 ElementType 关联"。
自定义注解:
我们也可以使用@interface来自定义注解,自动继承了java.lang.annotation.Annotation接口。如下定义了一个自定义接口,可以作用在方法和类上,并在运行时可以通过反射进行访问。同时带有参数,参数还有默认值。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface MyAnnotation{
String name() default "cb";
int age() default 21;
String[] schools() default ("CUG");
};
2. 反射(Reflection)
反射是java语言被视为准动态语言(指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化)的关键。反射可以允许程序在执行期间借助Reflection API取得任何类的内部信息并且能够直接操作对象的内部属性及方法。
在加载完类后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个对象),这个对象就包含了完整的类的结构信息。因此获取Class对象后,我们就可以根据这个对象去获取其他的信息。
得到Class类的几种方法:
首先定义一个person类:
package com.reflection;
import java.util.Date;
public class Person {
private String name;
private int age;
private Date birth;
public Person() {
}
public Person(String name, int age, Date birth) {
this.name = name;
this.age = age;
this.birth = birth;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", birth=" + birth +
'}';
}
}
具体的获取方法如下所示:
package com.reflection;
public class test {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Person();
// 第一种方法:通过对象获得
Class c1 = person.getClass();
System.out.println(c1.hashCode());
// 第二种方法:通过forname获得
Class c2 = Class.forName("com.reflection.Person");
System.out.println(c2.hashCode());
// 第三种方法:通过类名.class获得
Class c3 = Person.class;
System.out.println(c3.hashCode());
// 基本内置类型的包装类都有一个Type属性,可以直接调用获取
Class c4 = Integer.TYPE;
System.out.println(c4);
// 获取父类类型
Class c5 = c1.getSuperclass();
System.out.println(c5);
}
}
可以观察结果知道,三种获取方式得到的结果是一样的。
类加载的内存分析:
一个java文件经过javac编译成为.class文件,此时首先是加载过程,将.class文件字节码加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。然后就是链接的过程,这是一个将java类合并到JVM的运行状态之中的过程。具体过程就是首先验证类信息是否符合JVM规范,确保没有安全方面的问题。然后就是准备过程,这个过程就是正式为类变量(static)分配内存并设置初始值的阶段,这些内存将会在方法区中进行分配。最后是解析阶段,这就是将虚拟机常量池中的符号引用(常量名)替换为直接引用的过程。完成链接过程后就是初始化过程,主要就是执行类构造器(clinit)方法的过程,主要是由编译器自动收集类中所有的类变量的赋值动作和静态代码块中的语句合并产生的。
关于类的初始化:
类的主动初始化:
- 虚拟机启动时,一定会先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final变量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类时,如果其父类没有初始化,则会初始化其父类
类的被动初始化:
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化,例如子类调用父类的静态变量,子类不会初始化
- 通过数组定义类引用时,不会触发此类的初始化
- 引用常量不会触发此类的初始化,因为常量在链接过程中就已经存入调用类的常量池中了
如下一个简单的演示:
public class test1 {
static {
System.out.println("main被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
// 1.主动引用
Son son = new Son();
// 2.反射也会产生主动引用
Class c1 = Class.forName("com.reflection.Son");
// 3.不会产生类的引用的方法
System.out.println(Son.b);
// 4.数组定义类引用,也不会产生类引用
Son[] arr = new Son[]{};
}
}
class Father{
static int b = 2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
}
}
如下是获取类相关的方法大全:
方法 | 用途 |
asSubclass(Class clazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
获得类中属性相关的方法
方法 | 用途 |
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
获得类中注解相关的方法
方法 | 用途 |
getAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() | 返回该类所有的公有注解对象 |
getDeclaredAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类所有的注解对象 |
获得类中构造器相关的方法
方法 | 用途 |
getConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
获得类中方法相关的方法
方法 | 用途 |
getMethod(String name, Class…<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class…<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
类中其他重要的方法
方法 | 用途 |
isAnnotation() | 如果是注解类型则返回true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果是指定类型注解类型则返回true |
isAnonymousClass() | 如果是匿名类则返回true |
isArray() | 如果是一个数组类则返回true |
isEnum() | 如果是枚举类则返回true |
isInstance(Object obj) | 如果obj是该类的实例则返回true |
isInterface() | 如果是接口类则返回true |
isLocalClass() | 如果是局部类则返回true |
isMemberClass() | 如果是内部类则返回true |
通过反射来创建对象并获得相关属性和方法:
下面用一个例子演示如何动态的创建对象以及通过对象来获取方法和相关属性:
public class test2 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//获得Class对象
Class c1 = Class.forName("com.reflection.Person");
//构造一个对象(本质是调用了无参构造器)
Person person = (Person) c1.newInstance();
System.out.println(person);
//通过构造器创建对象
Date date = Calendar.getInstance().getTime();
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, Date.class);
Person person1 = (Person) constructor.newInstance("cb", 18, date);
System.out.println(person1);
//通过反射调用普通方法
Person person2 = (Person) c1.newInstance();
//通过反射获取一个方法
Method setName = c1.getDeclaredMethod("setName", String.class);
setName.invoke(person2, "cb");
System.out.println(person2.getName());
//通过反射操作属性
Person person3 = (Person) c1.newInstance();
Field name = c1.getDeclaredField("name");
//name是私有属性不能直接操作,必须使用setAccessible方法关闭程序的安全检测
name.setAccessible(true);
name.set(person3, "jack");
System.out.println(person3.getName());
}
}
首先是两种构造对象的方法,分别对应于无参构造和有参构造。然后演示了如何通过构造对象来调用相关的方法和相关的属性。调用的是getDeclaredMethod和getDeclaredField方法,这个函数可以调用所有的方法和属性,无视是否公有,但要注意的是如果要操作私有属性,则还需要调用setAccessible方法关闭程序的安全检测。输出的结果如下图所示:
通过反射来获取泛型信息:
下面演示的是通过反射来获取泛型信息:
public class test3 {
public Map<String, Person> test(){
System.out.println("test");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = test3.class.getMethod("test", null);
Type genericReturnType = method.getGenericReturnType();
if(genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for(Type actualTypeArgument : actualTypeArguments){
System.out.println(actualTypeArgument);
}
}
}
}
输出结果如下:
通过反射来获取注解:
首先创建一个类以及相应的两个注解,一个是作用在类名的注解,另一个是作用在属性的注解。这里用到了之前的知识:
@TableTest("db_student")
class Student{
@FieldTest(columnName = "db_id", type = "int", length = 10)
private int id;
@FieldTest(columnName = "db_age", type = "int", length = 10)
private int age;
@FieldTest(columnName = "db_name", type = "varchar", length = 5)
private String name;
public Student() {
}
public Student(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableTest{
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldTest{
String columnName();
String type();
int length();
}
下面就是通过反射来获取注解:
public class test4 {
public static void main(String[] args) throws NoSuchFieldException {
Class c = Student.class;
//通过反射获取注解
Annotation[] annotations = c.getAnnotations();
for(Annotation annotation : annotations){
System.out.println(annotation);
}
//通过反射获取注解value的值
TableTest tableTest = (TableTest) c.getAnnotation(TableTest.class);
String value = tableTest.value();
System.out.println(value);
//获得类指定的注解
Field field = c.getDeclaredField("name");
FieldTest fieldTest = field.getAnnotation(FieldTest.class);
System.out.println(fieldTest.columnName());
System.out.println(fieldTest.type());
System.out.println(fieldTest.length());
}
}
得到的结果如下:
至此注解与反射的总结告一段落,在后续框架中正是使用了这种方式通过反射获取注解,能够很简便的实现功能。