Java反射

Java反射

概述

  1. Java反射机制可以让我们在编译期(Compile Time)之外的运行期(Runtime)检查类,接口,变量以及方法的信息。
  2. 反射还可以让我们在运行期实例化对象,调用方法,通过调用get/set方法获取变量的值, 即使方法或字段是私有的的也可以通过反射的形式调用

使用场景:

  1. 在某些情况下,我们要使用的类在运行时才能确定,但是这个类符合某种特定的规范,例如JDBC。因为我们无法在编译期就使用它,所以只能通过反射来使用运行时才存在的类。
  2. 我们对于类的内部信息不可知,必须要等到运行时才能获取类的具体信息。比如ORM框架,在运行时才能够获取类中的各个字段,然后通过反射的形式获取其字段名和值,存入数据库。
  3. 注解相关。例如JUnit,使用反射来判断类中的方法是否有@Test注解,来运行单元测试。

Class对象

当我们编写完一个Java项目之后,所有的Java文件都会被编译成一个.class文件,这些Class对象承载了这个类型的父类、接口、构造函数、方法、成员变量等原始信息,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已。

在你想检查一个类的信息之前,你首先需要获取类的Class对象。Java中的所有类型包括基本类型(int, long, float等等),即使是数组都有与之关联的Class类的对象。有三种方式来获取一个类的Class对象。

  1. 如果在编译期就知道类的名称,可以使用类字面量(class-literal)来获取Class对象,Foo.class
Class myObjectClass = MyObject.class;
  1. 如果已经得到一个对象,可以通过对象的object.getClass()方法获取Class对象。
TestClass testClass = new TestClass();
Class<?> clazz = testClass.getClass();
  1. 如果在编译期获取不到目标类型,但是在知道它的完整类路径(全类名),那么可以通过Class.forName()方法来获取Class对象。
Class<?> clazz = Class.forName("com.example.TestClass");

Class对象的一些方法

基本方法

  1. String name = clazz.getName() 获取类的全类名(包括包信息)
  2. String simpleName = clazz.getSimpleName() 获取类名
  3. Package package = clazz.getPackage() 获取包信息
  4. Class superClass = clazz.getSuperclass() 获取父类的Class对象
  5. Class[] interfaces = clazz.getInterfaces() 获取类所实现的接口集合
    getInterfaces() 返回的仅仅只是当前类所实现的接口,不包括父类实现的接口
  6. int modifiers = clazz.getModifiers() 返回类修饰符
    每个修饰符都是一个位标识(flag bit),将这些修饰符封装成一个int类型的值。可以通过java.lang.Modifier类中的方法来检查修饰符的类型。
    Modifier.isAbstract(int modifiers);
    Modifier.isFinal(int modifiers);
    Modifier.isInterface(int modifiers);
    Modifier.isNative(int modifiers);
    Modifier.isPrivate(int modifiers);
    Modifier.isProtected(int modifiers);
    Modifier.isPublic(int modifiers);
    Modifier.isStatic(int modifiers);
    Modifier.isStrict(int modifiers);
    Modifier.isSynchronized(int modifiers);
    Modifier.isTransient(int modifiers);
    Modifier.isVolatile(int modifiers);
    
    或者可以通过Modifier.toString(int mod)静态方法输出类修饰符。

构造器

  1. Constructor<?>[] getConstructors()
  2. Constructor<T> getConstructor(Class<?>... parameterTypes)
  3. Constructor<?>[] getDeclaredConstructors()
  4. Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

通过方法签名应该很容易看出来,有参数的方法是通过构造器的参数类型获取唯一的构造器,没有参数的是获取类所有的构造函数。带有Declared的是获取类自身所有的构造函数,public、default、protected和private的。而不带Declared则只返回public的构造函数。

方法

  1. Method[] getMethods()
  2. Method getMethod(String name, Class<?>... parameterTypes)
  3. Method[] getDeclaredMethods()
  4. Method getDeclaredMethod(String name, Class<?>... parameterTypes)

和获取构造函数的方法类似,不过带有Declared的是获取自身所有的方法,不包括从父类中继承的。而不带Declared的则返回该类所有public的方法,包括从父类中继承下来的。其中,参数name为方法的名称,parameterTypes为参数的类型。

成员变量

  1. Field[] getFields()
  2. Field getField(String name)
  3. Field[] getDeclaredFields()
  4. Field getDeclaredField(String name)

这个和获取方法的差不多,带有Declared的是获取自身所有的成员变量,不包括从父类中继承的。而不带Declared的则返回该类所有public的成员变量,包括从父类中继承下来的。其中,参数name为成员变量的名称。

注解

  1. Annotation[] getAnnotations()
  2. <A extends Annotation> A getAnnotation(Class<A> annotationClass)
  3. Annotation[] getDeclaredAnnotations()
  4. <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)

和获取方法的差不多。annotationClass为注解的Class对象。注意,getDeclaredAnnotation(Class<A> annotationClass)方法是Java8新添加的方法

Constructor对象

利用Java的反射机制你可以检查一个类的构造方法,并且可以在运行期创建一个对象。这些功能都是通过java.lang.reflect.Constructor这个类实现的。

首先展示我们测试用的类。

public class Student {
    @MyAnnotation(name = "annotation")
    private int age;
    private List<String> parent;

    public Student(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    @MyAnnotation(name = "annotation")
    public void setAge(@MyAnnotation(name = "annotation") int age) {
        this.age = age;
    }

    public List<String> getParent() {
        return parent;
    }

    public void setParent(List<String> parent) {
        this.parent = parent;
    }
}

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    String name();
}

利用Contructor对象实例化一个类

方法定义:T newInstance(Object ... initargs)

代码如下:

Class<Student> clazz = Student.class;
// 获得一个Contructor对象,参数类型为int
Constructor<Student> constructor = clazz.getConstructor(int.class);

int age = 5;
// 利用Contructor对象示例化一个实例
Student student = constructor.newInstance(age);
System.out.println("student's age is :" + student.getAge()); // 打印出 student's age is :5

Method对象

使用Java反射你可以在运行期检查一个方法的信息以及在运行期调用这个方法,通过使用java.lang.reflect.Method类就可以实现上述功能。

通过Method对象调用方法

方法定义:Object invoke(Object obj, Object... args)

参数obj为调用方法的实例对象,如果方法是静态方法,则objnull。参数args是原方法的参数。
如果方法的返回值为void,则invoke的返回值为null

代码如下:

Method setMethod = clazz.getMethod("setAge", int.class);
int anotherAge = 20;
// 通过invoke调用方法,参数1为对象实例,如果该方法为静态方法,则传null。接下来的参数是原方法的参数。
setMethod.invoke(student, anotherAge);

Method getMethod = clazz.getMethod("getAge");
Object result = getMethod.invoke(student);
System.out.println("student's age is :" + result); // 打印出 student's age is :20

通过Method对象获取方法参数以及返回类型

  1. Class<?>[] getParameterTypes() 获取方法所有的参数类型
  2. Class<?> getReturnType() 获取方法的返回类型

代码如下:

// 获取方法所有的参数类型
Class[] parameterTypes = setMethod.getParameterTypes();
for (Class parameterType : parameterTypes) {
    System.out.println(parameterType); // 打印出int
}
// 获取方法的返回类型
Class returnType = setMethod.getReturnType();
System.out.println(returnType);  // 打印出void

通过Method对象获取泛型参数类型和泛型返回类型

  1. Type getGenericReturnType() 获取泛型返回类型
  2. Type[] getGenericParameterTypes() 获取泛型参数类型

上文说的getReturnTypegetParameterTypes返回的值原始类型(raw type),无法获得泛型类型。而getGenericReturnTypegetGenericParameterTypes可以返回参数化类型。当然,如果方法本身返回的不是参数化类型,那么这两个方法和getReturnType等效果相同。

首先,我们先科普一下Type接口。

Type is the common superinterface for all types in the Java
programming language. These include raw types, parameterized types,
array types, type variables and primitive types.

  • raw types : 例如 List
  • parameterized types : 例如 List<String>
  • array types : 例如 String[]
  • type variables : 例如 interface List<E> 中的E
  • primitive types : 例如 int

然后我们来看一下代码:

Method getParentMethod = clazz.getMethod("getParent");
Type returnType = getParentMethod.getGenericReturnType();

if (returnType instanceof ParameterizedType) {
    // 将returnType转化为子类ParameterizedType。此时的parameterizedType为List<String>
    ParameterizedType parameterizedType = (ParameterizedType) returnType;

    // 通过getActualTypeArguments方法获取泛型类型。此时为String
    Type[] typeArgs = parameterizedType.getActualTypeArguments();
    for (Type typeArg : typeArgs) {
        System.out.println(typeArg); // 打印出 String
    }
}

上述代码以getGenericReturnType方法为例,getGenericParameterTypes方法是类似的。通过getGenericReturnType返回的是整个参数化类型,如代码中的List<String>。通过ParameterizedTypegetActualTypeArguments方法返回的才是泛型里的参数类型,如List<String>中的String

通过Method对象获取方法注解

  1. Annotation[] getAnnotations()
  2. <T extends Annotation> T getAnnotation(Class<T> annotationClass)
  3. Annotation[] getDeclaredAnnotations()

获取方法注解和获取类注解类似,下面我们看一下示例:

Class<Student> clazz = Student.class;
Method method = clazz.getDeclaredMethod("setAge", int.class);

MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println(annotation.name()); // 打印出annotation

通过Method对象获取参数注解

  1. Annotation[][] getParameterAnnotations() 返回一个二维数组,每一个方法的参数包含一个注解数组

示例代码如下:

Annotation[][] parameterAnnotations = method.getParameterAnnotations();
// 遍历获得各个参数的注解数组
for (Annotation[] annotations : parameterAnnotations) {
    // 遍历注解数组,获取每个注解
    for (Annotation annotation : annotations) {
        if (annotation instanceof MyAnnotation) {
            MyAnnotation myAnnotation = (MyAnnotation) annotation;
            System.out.println(myAnnotation.name()); // 打印出annotation
        }
    }
}

Field对象

使用Java反射机制你可以运行期检查一个类的变量信息(成员变量)或者获取或者设置变量的值。通过使用java.lang.reflect.Field类就可以实现上述功能。

获取或设置(get/set)变量值

  1. Object get(Object obj) 获取成员变量的值,参数obj为对象实例,如果是静态成员变量,则传入null即可。
  2. void set(Object obj, Object value) 设置成员变量的值,参数obj为对象实例,如果是静态成员变量,则传入null即可。value为欲设置的值。

除了上诉两个方法,Field类还提供了获取和设置基础类型的方法。例如setBooleangetBoolean等方法。

代码示例如下:

Class<Student> clazz = Student.class;
Field ageField = clazz.getDeclaredField("age");

Student student = new Student(15);
// 通过get方法获取成员变量的值
Object age = ageField.get(student);
System.out.println(age); // 输出15

// 通过set方法设置成员变量的值
ageField.set(student, 20);
System.out.println(ageField.get(student)); // 输出20

通过Field对象获取成员变量的泛型参数类型

  1. Type getGenericType() 获取带泛型参数的成员变量类型(即List<String>,而不仅仅是raw type)

和Method对象的方法类似,获取的Type是ParameterizedType,强转之后,通过ParameterizedType的getActualTypeArguments()方法可以获取成员变量实际的泛型参数类型。

代码示例如下:

Class<Student> clazz = Student.class;
Field parentField = clazz.getDeclaredField("parent");

Type type = parentField.getGenericType();
if (type instanceof ParameterizedType) {
    ParameterizedType parameterizedType = (ParameterizedType) type;
    Type[] typeArgs = parameterizedType.getActualTypeArguments();
    for (Type typeArg : typeArgs) {
        System.out.println(typeArg); // 输出class java.lang.String
    }
}

通过Field对象获取注解

  1. Annotation[] getAnnotations()
  2. <T extends Annotation> T getAnnotation(Class<T> annotationClass)
  3. Annotation[] getDeclaredAnnotations()

和Method的方法类似。示例如下:

Field ageField = clazz.getDeclaredField("age");
MyAnnotation myAnnotation = ageField.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.name()); // 打印出annotation

AnnotatedElement接口

上文中我们介绍了通过Class,Constructor,Method,Field来获取注解,可以观察到他们调用的方法都是相同的,因为他们都实现了AnnotatedElement接口。

首先我们看一下文档:

Represents an annotated element of the program currently running in this
VM. This interface allows annotations to be read reflectively. All
annotations returned by methods in this interface are immutable and
serializable. The arrays returned by methods of this interface may be modified
by callers without affecting the arrays returned to other callers.

该接口表示当前运行在VM的程序中,被注解标注的元素,这就要求该注解的RetentionRetentionPolicy.RUNTIME。当然这个接口位于java.lang.reflect包中,所以当然是为了反射而存在的咯。而且接口中方法返回的注解都是不可变的(immutable),所以不用担心因为注解在运行时被改变影响到其它代码。

接下来我们看一下接口的几个方法:

  1. boolean isAnnotationPresent(Class<? extends Annotation> annotationClass): 判断该类型的注解是否存在。
  2. <T extends Annotation> T getAnnotation(Class<T> annotationClass): 根据注解类型返回该AnnotatedElement上的注解,如果不存在,则返回null
  3. Annotation[] getAnnotations() : 返回所有的注解,如果没有注解存在,则返回空数组。
  4. Annotation[] getDeclaredAnnotations() : 返回所有的注解,不包括从父类中继承来的,如果注解不存在,则返回空数组。

Java8以前的注解不允许重复出现相同类型的注解,但是Java8可以通过元注解@Repeatable来表示一个注解是可重复出现的,所以AnnotatedElement接口又添加了几个和重复注解相关的方法。

  1. <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass): 返回所有同一种类型的注解。如果不存在,则返回空数组。
  2. <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass): 返回所有同一种类型的注解,不包括从父类继承来的。如果不存在,则返回空数组。
  3. <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass): 根据注解类型返回注解,不包括从父类中继承来的。如果不存在,返回null。这个和重复注解没关系,但也是Java8中添加的新方法。

参考资料

公共技术之 Java反射 Reflection

Java Reflection教程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 一、概述 1、Java反射机制(Java-Reflect): 在运行状态中,对于任意一个类,都能够知道这个类中的所...
    年少懵懂丶流年梦阅读 4,281评论 0 5
  • 前言 现在在我们构建自己或公司的项目中,或多或少都会依赖几个流行比较屌的第三方库,比如:Butter Knife、...
    戴定康阅读 3,885评论 0 17
  • 1.反射含义是什么? Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意...
    Marlon666阅读 377评论 0 0
  • 1. 了解 Java 中的反射 1.1 什么是 Java 的反射 Java 反射是可以让我们在运行时获取类的函数、...
    Ten_Minutes阅读 506评论 0 4
  • 一、概述 Java反射机制定义 Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法...
    CoderZS阅读 1,596评论 0 26