微信搜索:码农StayUp
主页地址:https://gozhuyinglong.github.io
源码分享:https://github.com/gozhuyinglong/blog-demos

1. 前言

在OOP的世界里,万物皆对象。也就是说,我们可以将任何东西抽象成一个对象。

比如人,可以抽象成一个Person类,通过new Person()来实例化一个对象;再比如鸭子,可以抽象成一个Duck类,也可以对其进行实例化……那么这一个个类本身是不是也可以抽象成一个类呢?Java提供了一个特殊的类Class,用来描述类的内部信息,是反射的核心类。

下图是本篇讲述内容:

2. Java反射机制概述

Java反射(Reflection)允许应用程序在运行时借助于反射API,来获取所有类或接口的内部信息,并且能直接操作任意对象的内部属性及方法。反射机制的核心类为java.lang.Class

  • 类加载完后,会在堆内存的方法区中产生一个Class类型的对象。
  • Class类没有公开的构造函数,是由类加载器的defineClass方法构造而成。所以Class对象不是“new”出来的,而是通过方法来获取的。
  • 这个Class对象具有类的完整结构信息,并且一个类只有一个Class对象。

3. 获取Class对象

获取Class对象有以下四种方式:

  1. 通过类对象获取;
  2. 通过类直接调用class获取;
  3. 通过Class.forName获取;
  4. 通过类加载器获取。

下面使用代码展示获取 Person 类的Class对象的四种方式:

@Testpublic void testClassFor() {    // 1.通过类实例获取    Person person = new Person();    Class<? extends Person> clazz1 = person.getClass();    System.out.println("01 - " + clazz1);    // 2.通过类直接调用class获取    Class<Person> clazz2 = Person.class;    System.out.println("02 - " + clazz2);    // 3.通过Class.forName获取    Class<?> clazz3 = null;    try {        clazz3 = Class.forName("io.github.gozhuyinglong.reflection.Person");    } catch (ClassNotFoundException e) {        // 当找不到指定类时,会抛出此异常        e.printStackTrace();    }    System.out.println("03 - " + clazz3);    // 4.通过类加载器获取    ClassLoader classLoader = this.getClass().getClassLoader();    Class<?> clazz4 = null;    try {        clazz4 = classLoader.loadClass("io.github.gozhuyinglong.reflection.Person");    } catch (ClassNotFoundException e) {        // 当找不到指定类时,会抛出此异常        e.printStackTrace();    }    System.out.println("04 - " + clazz4);    // hashCode相等,说明这四种方式获取的是同一个实例    System.out.println("05 - " + clazz1.hashCode());    System.out.println("06 - " + clazz2.hashCode());    System.out.println("07 - " + clazz3.hashCode());    System.out.println("08 - " + clazz4.hashCode());}

输出结果:

01 - class io.github.gozhuyinglong.reflection.Person02 - class io.github.gozhuyinglong.reflection.Person03 - class io.github.gozhuyinglong.reflection.Person04 - class io.github.gozhuyinglong.reflection.Person05 - 72174889506 - 72174889507 - 72174889508 - 721748895

通过上面的输出结果可以看出,这四个Class对象的hashCode相同,说明使用这四种方式获取的是同一个对象。

4. 一些特殊的类和接口的Class对象

在源码注释中提到一些特殊的类和接口:

  • 枚举是一种类。
  • 注解是一种接口。
  • 数组也属于一个反映为Class对象的类。具有相同元素类型和维数的数组,也具有相同的Class对象(也就是说,元素类型不同,或数组维数不同,其Class对象也不同)。
  • 原始Java类型(boolean, byte, char, short, int, long, float,double)和关键字 void 也表示为Class对象。

下面通过代码来验证:

@Testpublic void testClassOther() {    // 枚举是一种类    Class<PersonEnum> clazz1 = PersonEnum.class;    System.out.println("01 - " + clazz1);    // 注解是一种接口    Class<PersonAnnotation> clazz2 = PersonAnnotation.class;    System.out.println("02 - " + clazz2);    // 数组也属于一个反应 Class 实例的类    Person[] personArray3 = new Person[1];    Class<? extends Person[]> clazz3 = personArray3.getClass();    System.out.println("03 - " + clazz3);    // 具有相同元素类型和维数的数组,也具有相同的 Class 实例    Person[] personArray4 = new Person[4];    Class<?> clazz4 = personArray4.getClass();    Person[][] personArray5 = new Person[1][];    Class<?> clazz5 = personArray5.getClass();    // 两个一维数组的 hashCode 相等,说明是同一实例    System.out.println("04 - " + clazz3.hashCode());    System.out.println("05 - " + clazz4.hashCode());    // 一维数组与二维数组的 hashCode 不相等,说明不是同一实例    System.out.println("06 - " + clazz5.hashCode());    // 原始 Java 类型和关键字 void 也表示为 Class 实例    Class<Integer> clazz6 = int.class;    System.out.println("07 - " + clazz6);    Class<Double> clazz7 = double.class;    System.out.println("08 - " + clazz7);    Class<Void> clazz8 = void.class;    System.out.println("09 - " + clazz8);}

输出结果:

01 - class io.github.gozhuyinglong.reflection.PersonEnum02 - interface io.github.gozhuyinglong.reflection.PersonAnnotation03 - class [Lio.github.gozhuyinglong.reflection.Person;04 - 72174889505 - 72174889506 - 164253485007 - int08 - double09 - void

通过输出结果可以看出,确如源码中描述那样。

5. Java反射API

Java提供了一套反射API,该API由Class类与java.lang.reflect类库组成。该类库包含了FieldMethodConstructor等类。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。

反射允许以编程的方式访问已加载类的字段、方法和构造函数信息,并在安全限制内利用反射对其进行操作。

下面将介绍一些常用的类:

5.1 Class(类)

java.lang.Class类用来描述类的内部信息,Class的实例可以获取类的包、注解、修饰符、名称、超类、接口等。

@Testpublic void testClass() throws Exception {    Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");    // 获取该类所在包路径    Package aPackage = clazz.getPackage();    System.out.println("01 - " + aPackage);    // 获取该类上所有注解    Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();    for (Annotation temp : declaredAnnotations) {        System.out.println("02 - " + temp);    }    // 获取类上的修饰符    int modifiers = clazz.getModifiers();    String modifier = Modifier.toString(modifiers);    System.out.println("03 - " + modifier);    // 获取类名称    String name = clazz.getName();    System.out.println("04 - " + name);    // 获取简单类名    String simpleName = clazz.getSimpleName();    System.out.println("05 - " + simpleName);    // 获取直属超类    Type genericSuperclass = clazz.getGenericSuperclass();    System.out.println("06 - " + genericSuperclass);    // 获取直属实现的接口    Type[] genericInterfaces = clazz.getGenericInterfaces();    for (Type temp : genericInterfaces) {        System.out.println("07 - " + temp);    }}

输出结果:

01 - package io.github.gozhuyinglong.reflection02 - @io.github.gozhuyinglong.reflection.PersonAnnotation()03 - public final04 - io.github.gozhuyinglong.reflection.Person05 - Person06 - class io.github.gozhuyinglong.reflection.PersonParent07 - interface io.github.gozhuyinglong.reflection.PersonInterface

5.2 Constructor(构造函数)

java.lang.reflect.Constructor提供了类的构造函数信息。可以获取构造函数上的注解信息、参数类型等。

@Testpublic void testConstructor() throws Exception {    Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");    // 获取一个声明为 public 构造函数实例    Constructor<?> constructor1 = clazz.getConstructor(String.class, int.class, PersonEnum.class);    System.out.println("01 - " + constructor1);    // 获取所有声明为 public 构造函数实例    Constructor<?>[] constructorArray1 = clazz.getConstructors();    for (Constructor<?> constructor : constructorArray1) {        System.out.println("02 - " + constructor);    }    // 获取一个声明的构造函数实例    Constructor<?> constructor2 = clazz.getDeclaredConstructor(String.class);    System.out.println("03 - " + constructor2);    // 获取所有声明的构造函数实例    Constructor<?>[] constructorArray2 = clazz.getDeclaredConstructors();    for (Constructor<?> constructor : constructorArray2) {        System.out.println("04 - " + constructor);    }    // 根据构造函数创建一个实例    Object o1 = constructor1.newInstance("杨过", 25, PersonEnum.MAN);    System.out.println("05 - " + o1);    // 将构造函数的可访问标志设为 true 后,可以通过私有构造函数创建实例    constructor2.setAccessible(true);    Object o2 = constructor2.newInstance("小龙女");    System.out.println("06 - " + o2);    // 获取该构造函数上的所有注解    Annotation[] annotations = constructor1.getDeclaredAnnotations();    for (Annotation annotation : annotations) {        System.out.println("07 - " + annotation);    }    // 获取该构造函数上的所有参数类型    Type[] genericParameterTypes = constructor1.getGenericParameterTypes();    for (Type genericParameterType : genericParameterTypes) {        System.out.println("08 - " + genericParameterType);    }}

输出结果:

01 - public io.github.gozhuyinglong.reflection.Person(java.lang.String,int,io.github.gozhuyinglong.reflection.PersonEnum)02 - public io.github.gozhuyinglong.reflection.Person(java.lang.String,int,io.github.gozhuyinglong.reflection.PersonEnum)02 - public io.github.gozhuyinglong.reflection.Person(java.lang.String,int)02 - public io.github.gozhuyinglong.reflection.Person()03 - private io.github.gozhuyinglong.reflection.Person(java.lang.String)04 - public io.github.gozhuyinglong.reflection.Person(java.lang.String,int,io.github.gozhuyinglong.reflection.PersonEnum)04 - public io.github.gozhuyinglong.reflection.Person(java.lang.String,int)04 - private io.github.gozhuyinglong.reflection.Person(java.lang.String)04 - public io.github.gozhuyinglong.reflection.Person()05 - Person{name='杨过', age=25, sex='MAN'}06 - Person{name='小龙女', age=0, sex='null'}07 - @io.github.gozhuyinglong.reflection.PersonAnnotation()08 - class java.lang.String08 - int08 - class io.github.gozhuyinglong.reflection.PersonEnum

5.3 Field(属性)

java.lang.reflect.Field提供了类的属性信息。可以获取属性上的注解、修饰符、属性类型、属性名等。

@Testpublic void testField() throws Exception {    Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");    // 获取一个该类或父类中声明为 public 的属性    Field field1 = clazz.getField("hobby");    System.out.println("01 - " + field1);    // 获取该类及父类中所有声明为 public 的属性    Field[] fieldArray1 = clazz.getFields();    for (Field field : fieldArray1) {        System.out.println("02 - " + field);    }    // 获取一个该类中声明的属性    Field field2 = clazz.getDeclaredField("name");    System.out.println("03 - " + field2);    // 获取该类中所有声明的属性    Field[] fieldArray2 = clazz.getDeclaredFields();    for (Field field : fieldArray2) {        System.out.println("04 - " + field);    }    // 获取该属性上的所有注解    Annotation[] declaredAnnotations = field2.getDeclaredAnnotations();    for (Annotation declaredAnnotation : declaredAnnotations) {        System.out.println("05 - " + declaredAnnotation);    }    // 获取修饰符    String modifier = Modifier.toString(field2.getModifiers());    System.out.println("06 - " + modifier);    // 获取属性类型,返回类对象    Class<?> type = field2.getType();    System.out.println("07 - " + type);    // 获取属性类型,返回Type对象    Type genericType = field2.getGenericType();    System.out.println("08 - " + genericType);    // 获取属性名称    String name = field2.getName();    System.out.println("09 - " + name);}

输出结果:

01 - public java.lang.String io.github.gozhuyinglong.reflection.PersonParent.hobby02 - public int io.github.gozhuyinglong.reflection.Person.height02 - public java.lang.String io.github.gozhuyinglong.reflection.PersonParent.hobby03 - private java.lang.String io.github.gozhuyinglong.reflection.Person.name04 - private java.lang.String io.github.gozhuyinglong.reflection.Person.name04 - private int io.github.gozhuyinglong.reflection.Person.age04 - public int io.github.gozhuyinglong.reflection.Person.height05 - @io.github.gozhuyinglong.reflection.PersonAnnotation()06 - private07 - class java.lang.String08 - class java.lang.String09 - name

5.4 Method(方法)

java.lang.reflect.Method提供了类的方法信息。可以获取方法上的注解、修饰符、返回值类型、方法名称、所有参数。

@Testpublic void testMethod() throws Exception {    Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");    // 获取一个该类及父类中声明为 public 的方法,需要指定方法的入参类型    Method method = clazz.getMethod("setName", String.class);    System.out.println("01 - " + method);    // 获取该类及父类中所有声明为 public 的方法    Method[] methods = clazz.getMethods();    for (Method temp : methods) {        System.out.println("02 - " + temp);    }    // 获取一个在该类中声明的方法    Method declaredMethod = clazz.getDeclaredMethod("display");    System.out.println("03 - " + declaredMethod);    // 获取所有在该类中声明的方法    Method[] declaredMethods = clazz.getDeclaredMethods();    for (Method temp : declaredMethods) {        System.out.println("04 - " + temp);    }    // 获取该方法上的所有注解    Annotation[] declaredAnnotations = method.getDeclaredAnnotations();    for (Annotation temp : declaredAnnotations) {        System.out.println("05 - " + temp);    }    // 获取修饰符    String modifier = Modifier.toString(method.getModifiers());    System.out.println("06 - " + modifier);    // 获取返回值类型,返回类对象    Class<?> returnType = method.getReturnType();    System.out.println("07 - " + returnType);    // 获取返回值类型,返回Type对象    Type genericReturnType = method.getGenericReturnType();    System.out.println("08 - " + genericReturnType);    // 获取方法名称    String name = method.getName();    System.out.println("09 - " + name);    // 获取所有入参    Parameter[] parameters = method.getParameters();    for (Parameter temp : parameters) {        System.out.println("10 - " + temp);    }}

输出结果:

01 - public void io.github.gozhuyinglong.reflection.Person.setName(java.lang.String)02 - public java.lang.String io.github.gozhuyinglong.reflection.Person.toString()02 - public java.lang.String io.github.gozhuyinglong.reflection.Person.getName()02 - public void io.github.gozhuyinglong.reflection.Person.setName(java.lang.String)02 - public int io.github.gozhuyinglong.reflection.Person.getAge()02 - public void io.github.gozhuyinglong.reflection.Person.setAge(int)02 - public java.lang.String io.github.gozhuyinglong.reflection.Person.sayHello()02 - public io.github.gozhuyinglong.reflection.PersonEnum io.github.gozhuyinglong.reflection.PersonParent.getSex()02 - public void io.github.gozhuyinglong.reflection.PersonParent.setSex(io.github.gozhuyinglong.reflection.PersonEnum)02 - public final void java.lang.Object.wait() throws java.lang.InterruptedException02 - public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException02 - public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException02 - public boolean java.lang.Object.equals(java.lang.Object)02 - public native int java.lang.Object.hashCode()02 - public final native java.lang.Class java.lang.Object.getClass()02 - public final native void java.lang.Object.notify()02 - public final native void java.lang.Object.notifyAll()03 - private java.lang.String io.github.gozhuyinglong.reflection.Person.display()04 - public java.lang.String io.github.gozhuyinglong.reflection.Person.toString()04 - public java.lang.String io.github.gozhuyinglong.reflection.Person.getName()04 - public void io.github.gozhuyinglong.reflection.Person.setName(java.lang.String)04 - private java.lang.String io.github.gozhuyinglong.reflection.Person.display()04 - public int io.github.gozhuyinglong.reflection.Person.getAge()04 - public void io.github.gozhuyinglong.reflection.Person.setAge(int)04 - public java.lang.String io.github.gozhuyinglong.reflection.Person.sayHello()05 - @io.github.gozhuyinglong.reflection.PersonAnnotation()06 - public07 - void08 - void09 - setName10 - java.lang.String arg0

5.5 Modifier(修饰符)

java.lang.reflect.Modifier提供了访问修饰符信息。通过ClassFieldMethodConstructor等对象都可以获取修饰符,这个访问修饰符是一个整数,可以通过Modifier.toString方法来查看修饰符描述。并且该类提供了一些静态方法和常量来解码访问修饰符。

@Testpublic void testModifier() throws Exception {    Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");    // 获取类的修饰符值    int modifiers1 = clazz.getModifiers();    System.out.println("01 - " + modifiers1);    // 获取属性的修饰符值    int modifiers2 = clazz.getDeclaredField("name").getModifiers();    System.out.println("02 - " + modifiers2);    // 获取构造函数的修饰符值    int modifiers3 = clazz.getDeclaredConstructor(String.class).getModifiers();    System.out.println("03 - " + modifiers3);    // 获取方法的修饰符值    int modifiers4 = clazz.getDeclaredMethod("display").getModifiers();    System.out.println("04 - " + modifiers4);    // 判断修饰符值是否 final 类型    boolean isFinal = Modifier.isFinal(modifiers1);    System.out.println("05 - " + isFinal);    // 判断修饰符值是否 public 类型    boolean isPublic = Modifier.isPublic(modifiers2);    System.out.println("06 - " + isPublic);    // 根据修饰符值,获取修饰符标志的字符串    String modifier = Modifier.toString(modifiers1);    System.out.println("07 - " + modifier);    System.out.println("08 - " + Modifier.toString(modifiers2));}

输出结果:

01 - 1702 - 203 - 204 - 205 - true06 - false07 - public final08 - private

5.6 Parameter(参数)

java.lang.reflect.Parameter提供了方法的参数信息。可以获取方法上的注解、参数名称、参数类型等。

@Testpublic void testParameter() throws Exception {    Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");    // 获取构造函数的参数    Constructor<?> constructor = clazz.getConstructor(String.class, int.class, PersonEnum.class);    Parameter[] parameterArray1 = constructor.getParameters();    for (Parameter temp : parameterArray1) {        System.out.println("01 - " + temp);    }    // 获取方法的参数    Method method = clazz.getMethod("setName", String.class);    Parameter[] parameterArray2 = method.getParameters();    for (Parameter temp : parameterArray2) {        System.out.println("02 - " + temp);    }    Parameter parameter = parameterArray1[0];    // 获取参数上的注解    Annotation[] annotationArray = parameter.getAnnotations();    for (Annotation temp : annotationArray) {        System.out.println("02 - " + temp);    }    // 获取参数名称    String name = parameter.getName();    System.out.println("03 - " + name);    // 获取参数类型    Type parameterizedType = parameter.getParameterizedType();    System.out.println("04 - " + parameterizedType);    Class<?> type = parameter.getType();    System.out.println("05 - " + type);}

输出结果:

01 - java.lang.String arg001 - int arg101 - io.github.gozhuyinglong.reflection.PersonEnum arg202 - java.lang.String arg002 - @io.github.gozhuyinglong.reflection.PersonAnnotation()03 - arg004 - class java.lang.String05 - class java.lang.String

5.7 AccessibleObject(可访问标志)

java.lang.reflect.AccessibleObject类是FieldMethodConstructor类的超类。

该类提供了对类、方法、构造函数的访问控制检查的能力(如:私有方法只允许当前类访问)。

访问检查在设置/获取属性、调用方法、创建/初始化类的实例时执行。

可以通过setAccessible方法将可访问标志设为true(默认为false),会关闭访问检查。这样即使是私有的属性、方法或构造函数,也可以访问。

6. 通过反射动态创建对象并执行方法

可以利用反射来创建对象,并可执行方法,下面看代码示例:

  • 通过Class类的newInstance创建一个实例。(该方法调用无参构造器)。
  • 通过构造函数Constructor类创建一个实例。
  • 获取方法,再通过 invoke 方法来调用,第一个参数为实例,后面参数为方法的Parameter
  • 获取字段,因为 age 字段是私有的,所以将其设置为可访问(不设置会报异常)。并通过 set 方法来赋值。
@Testpublic void testInvoke() throws Exception {    Class<?> clazz = Class.forName("io.github.gozhuyinglong.reflection.Person");    // 通过Class类的newInstance创建一个实例。(该方法调用无参构造器)    Object o1 = clazz.newInstance();    System.out.println("01 - " + o1.toString());    // 通过构造函数Constructor类创建一个实例    Constructor<?> constructor = clazz.getConstructor(String.class, int.class, PersonEnum.class);    Object o2 = constructor.newInstance("杨过", 25, PersonEnum.MAN);    System.out.println("02 - " + o2.toString());    // 先获取方法,再通过 invoke 方法来调用,第一个参数为实例,后面参数为方法的Parameter    Method method = clazz.getMethod("setName", String.class);    method.invoke(o1, "小龙女");    System.out.println("03 - " + o1.toString());    // 获取字段,因为 age 字段是私有的,所以将其设置为可访问(不设置会报异常)。并通过 set 方法来赋值    Field field = clazz.getDeclaredField("age");    field.setAccessible(true);    field.set(o1, 28);    System.out.println("04 - " + o1.toString());}

执行结果:

01 - Person{name='null', age=0, sex='null'}02 - Person{name='杨过', age=25, sex='MAN'}03 - Person{name='小龙女', age=0, sex='null'}04 - Person{name='小龙女', age=28, sex='null'}

7. 反射的缺点

引自官方指南:https://docs.oracle.com/javase/tutorial/reflect/index.html

反射虽是强大的,但不可随意使用。如果可以在不使用反射的情况下执行操作,则应避免使用它。因为通过反射访问代码时,会有以下缺点。

7.1 性能开销

反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。

7.2 安全限制

使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是个问题了。

7.3 内部暴露

由于反射允许代码执行一些在正常情况下不被允许的操作,比如访问私有的属性和方法。所以使用反射可能会导致意料之外的副作用:代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

8. 完整代码

完整代码请访问我的Github,若对你有帮助,欢迎给个⭐,感谢~~

©著作权归作者所有:来自51CTO博客作者码农StayUp的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. linux下挂载NTFS文件系统出现symbol lookup error: 报错解决方
  2. shell单分支if条件句语法详解与生产案例详解
  3. Java 对象的哈希值是每次 hashCode() 方法调用重计算么?
  4. 这样规范写代码,同事直呼“666”
  5. JUC的世界II
  6. Java程序员必备基础:Object的十二个知识点
  7. 实例演示抽象类和接口的区别
  8. 定义一个接口和抽象类、对比接口与抽象类的区别与联系
  9. jQuery 中常用的 DOM 操作以及使用 jQuery 完成跨域请求操作

随机推荐

  1. 还不知道如何使用 IDEA ?教你三招快速掌握
  2. 聊聊如何从零开始自学编程
  3. 从源码解析 Spring JDBC 异常抽象
  4. 为什么将 Intellij IDEA 作为日常开发的
  5. 支付路由系统演进史
  6. 小心递归中内存泄漏
  7. 想来微软实习吗?
  8. 我是怎么把博客粉丝转到公众号的
  9. 如何更好地结构化表示一个 URL?
  10. 5:Zabbix5.0 监控服务器网口流量