Java 反射机制详解(一)

Java
284
0
0
2023-05-26
标签   Java反射

前言

Java 中有两种方式可以让我们在运行时识别对象和类的信息。一种是 RTTI (运行时类型识别:Run-Time Type Identification),它假定了我们在编译时已经知道了所有的类型;另一种是我们本文要说的 反射机制 ,它允许我们在运行时获取和使用类的信息。无论是 RTTI 还是 反射 ,其本质都是一样的,都是去动态获取类的信息。它们唯一不同的是, RTTI 在编译时期知道要解析的类型,而 反射 是在运行时才知道要解析的类型。

反射概述

反射就是把 Java 类中的各个部分(属性、方法、构造方法等)映射成一个个对象。 Class 类与 java.lang.reflect 类库一起对反射的概念提供了支持,类库中包含了 Field Method Constructor 类,每个类都实现了 Member 接口。这些类型的对象都是由 JVM 运行时创建的,用来表示 未知 类里对应的成员。

这样我们就可以使用 Constructor 创建新的对象,用 get set 方法读取和修改类中与 Field 对象关联的字段,用 invoke 方法调用类中与 Method 对象关联的方法等。 Java 反射机制是在运行状态中的,对于任意一个类我们可以通过反射获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。

重要的是,要认识到反射机制并没有什么特别之处,当我们通过反射和一个未知类型的对象打交道时, JVM 只是简单的对这个对象做检查,看它属于哪个类,在用它做其它事情之前必须先加载那个类 Class 对象。所以那个类的 字节码 文件对象对于 JVM 来说必须是可获取的,要么在本地机器上,要么通过网络获取。

反射 API 的使用

想要通过反射获取一个类的信息之前,首先要先获取这个类的 Class 对象,在 Java 中所有类型都有与之关联的 Class 对象。

获取类的 Class 对象

Java 中获取一个类的 Class 对象有三种方式: 第 ① 种 使用 Class 类的 forName 静态方法,当我们知道一个类的全路径时,可以通过 Class.forName 方法获取类的 Class 对象。

 Class stringClass = Class.forName("java.lang.String");
System.out.println(stringClass);  

运行结果

 class java.lang.String  

第 ② 种 使用 .class 获取,这种方式只适合在编译前就已经知道了要操作的 Class

 Class stringClass = String.class;
System.out.println(stringClass);  

运行结果

 class java.lang.String  

第 ③ 种 使用 getClass() 方法获取

 Class stringClass = "mghio".getClass();
System.out.println(stringClass);  

运行结果

 class java.lang.String  

通过反射创建类对象

通过反射创建类对象有两种方式:

第 ① 种 通过调用 Class 对象的 newInstance() 方法创建

 Class<Person> personClass = Person.class;
Person person = personClass.newInstance();  

第 ② 种 通过调用 Constructor 对象的 newInstance() 方法创建

 Class<Person> personClass = Person.class;
Constructor personConstructor = personClass.getConstructor();
Person person = (Person) personConstructor.newInstance();  

两者的区别是,通过 Class newInstance 方法只能通过无参构造方法创建,这就要求这个类必须有一个无参的构造方法,而通过 Constructor newInstance 可以指定参数来选择特定的构造方法来创建对象。以下代码就是指定参数然后通过特定的构造方法创建对象的。

 Class<Person> personClass = Person.class;
Constructor personConstructor = personClass.getConstructor();
Person person = (Person) personConstructor.newInstance("mghio", "中国上海");  

通过反射获取类的属性

Class 类提供了两种方式获取一个类的属性,第 ① 种是通过 Class 对象的 getFields 方法获取类的属性,该方法只能获取类的 public 属性。

 Class<Person> personClass = Person.class;
Field[] fields = personClass.getFields();
System.out.println(Arrays.toString(fields));  

运行结果

 [public java.lang.String cn.mghio.blogmghiocode.reflect.Person.id, 
public java.lang.String cn.mghio.blogmghiocode.reflect.Person.name]  

第 ② 种是通过 Class 对象的 getDeclaredFields 方法获取类的属性,该方法可以获取类的所有属性(包括 private 修饰的属性)。

 Class<Person> personClass = Person.class;
Field[] fields = personClass.getDeclaredFields();
System.out.println(Arrays.toString(fields));  

运行结果

 [public java.lang.String cn.mghio.blogmghiocode.reflect.Person.id, 
public java.lang.String cn.mghio.blogmghiocode.reflect.Person.name, 
protected java.lang.Integer cn.mghio.blogmghiocode.reflect.Person.age, 
private java.lang.String cn.mghio.blogmghiocode.reflect.Person.address]  

通过反射获取类的方法

Class 也提供了两种方式获取类的方法,第 ① 种是通过 Class 对象的 getMethods 方法获取类的方法(包括继承而得的方法)。

 Class<Person> personClass = Person.class;
Method[] methods = personClass.getMethods();
System.out.println(Arrays.toString(methods));  

运行结果

 [public java.lang.String cn.mghio.blogmghiocode.reflect.Person.toString(), 
public java.lang.String cn.mghio.blogmghiocode.reflect.Person.getAddress(), 
...
public final native java.lang.Class java.lang.Object.getClass(), 
public final native void java.lang.Object.notify()]  

第 ② 种是通过 Class 对象的 getDeclaredMethods 方法获取类的方法(只包含类中定义的方法,不包含继承而来的方法)。

 Class<Person> personClass = Person.class;
Method[] methods = personClass.getDeclaredMethods();
System.out.println(Arrays.toString(methods));  

运行结果

 [public  java .lang.String cn.mghio.blogmghiocode.reflect.Person.toString(), 
public java.lang.String cn.mghio.blogmghiocode.reflect.Person.getAddress(), 
... 
protected void cn.mghio.blogmghiocode.reflect.Person.protectedMethod(), 
private void cn.mghio.blogmghiocode.reflect.Person.privateMethod()]  

从以上结果可以看出这个方法只获取当前类中定义的方法,包含 private 方法,不会获取从父类中继承而来的方法。

通过反射获取类的构造方法

Class 也提供了两种方式获取类的构造方法,第 ① 种是通过 Class 对象的 getConstructors 方法获取类的构造方法(只能获取当前类的 public 构造方法)。

 Class<Person> personClass = Person.class;
Constructor[] constructors = personClass.getConstructors();
System.out.println(Arrays.toString(constructors));  

运行结果

 [public cn.mghio.blogmghiocode.reflect.Person(java.lang.String,java.lang.String,java.lang. Integer ,java.lang.String)]  

第 ② 种是通过 Class 对象的 getDeclaredConstructors 方法获取类的构造方法(只包含类中定义的所有构造方法)。

 Class<Person> personClass = Person.class;
Constructor[] constructors = personClass.getDeclaredConstructors();
System.out.println(Arrays.toString(constructors));  

运行结果

 [public cn.mghio.blogmghiocode.reflect.Person(java.lang.String,java.lang.String,java.lang.Integer,java.lang.String), 
protected cn.mghio.blogmghiocode.reflect.Person(java.lang.String,java.lang.String), 
private cn.mghio.blogmghiocode.reflect.Person()]  

通过反射获取类的类名

Class 类提供了两种方式获取类的类名,第 ① 种是通过 getName 方法获取类的全限定名(包含包名)。

 Class<Person> personClass = Person.class;
String fullPersonClassName = personClass.getName();
System.out.println(fullPersonClassName);  

运行结果

 cn.mghio.blogmghiocode.reflect.Person  

第 ② 种是通过 Class 对象的 getSimpleName 方法获取类的类名(不包含包名)。

  Class<Person> personClass = Person.class;
String fullPersonClassName = personClass.getSimpleName();
System.out.println(fullPersonClassName);  

运行结果

 Person  

通过反射获取类的修饰符

可以通过 Class 类来获取一个类的修饰符,也就是我们熟知的 public protected private 等关键字,通过调用 getModifiers 方法来获取一个类的修饰符。

 Class<Person> personClass = Person.class;
int modifyInt = personClass.getModifiers();
System.out.println(modifyInt);  

运行结果

 1  

返回 1 表示类 Person 的修饰符为 public ,修饰符在 Modifier 类中都被包装成一个 int 类型的数字,部分修饰符定义如下

 /**
  * The {@code int} value representing the {@code public}
  * modifier.
  */public static final int PUBLIC           = 0x00000001;

/**
  * The {@code int} value representing the {@code private}
  * modifier.
  */public static final int PRIVATE          = 0x00000002;

/**
  * The {@code int} value representing the {@code protected}
  * modifier.
  */public static final int PROTECTED        = 0x00000004;  

通过反射获取类的包信息

Class 对象通过 getPackage 方法获取类的包相关信息,可以使用 Class 对象通过如下的方式获取包信息

 Class<Person> personClass = Person.class;
Package packageClazz = personClass.getPackage();
System.out.println(packageClazz.getName());  

运行结果

 cn.mghio.blogmghiocode.reflect  

通过反射获取类的父类

可以通过 Class 类来获取一个类的父类,通过调用 getModifiers 方法来获取一个类的父类。

 Class<Person> personClass = Person.class;
Class superclass = personClass.getSuperclass();
System.out.println(superclass.getName());  

运行结果

 java.lang.Object  

以上运行结果表示 Person 类的父类是 Object 类,可以看到 superclass 对象其实就是一个 Class 类的实例,所以也可以继续在这个对象上进行反射操作。

通过反射获取类的实现接口

可以通过 Class 类来获取一个类的父类,通过调用 getInterfaces 方法来获取一个类实现的接口。

 Class<Person> personClass = Person.class;
Class<?>[] interfaces = personClass.getInterfaces();
System.out.println(Arrays.toString(interfaces));  

运行结果

 [interface cn.mghio.blogmghiocode.reflect.IPerson]  

Java 中一个类可以实现多个接口,因此 getInterfaces 方法返回一个 Class 数组,在 Java 中接口也同样有对应的 Class 对象。这个方法需要注意的是, getInterfaces 方法仅仅只返回当前类所实现的接口。当前类的父类如果实现了接口,这些接口是不会在返回的 Class 集合中的,尽管实际上当前类其实已经实现了父类接口。

通过反射获取 泛型 信息

当我们在声明一个类或者接口的时候可以指定它可以参数化,常用的 List 接口就是一个参数化接口的例子。比如想要检查 List 接口的参数化类型,我们是没有办法能知道它具体的参数化类型是什么。

这个类型就可以是一个应用中所有的类型。但是,当你检查一个使用了被参数化的类型的变量或者方法,你可以获得这个被参数化类型的具体参数。 第 ① 种 泛型方法返回类型 当你获得了 Method 对象,那么就可以获取到这个方法的泛型返回类型信息。

如果方法是在一个被参数化类型之中(例如: T foo()),那么将无法获得它的具体类型,但是如果方法返回的是一个泛型类(例如:List foo()),那么就可以获得这个泛型类的具体参数化类型。下面这个例子中的类定义了一个返回类型是泛型的方法。

 /**
 * @author mghio
 * @date: 2019-12-29
 * @version: 1.0
 * @description: 通过反射获取泛型信息
 * @since JDK 1.8
 */public class ReflectGenericDemo {

  protected List<Integer> stringList = Arrays.asList(2, 55, 3, 90, 81);

  public List<Integer> getStringList(){
    return this.stringList;
  }

}  

我们可以获取上面这个类 ReflectGenericDemo 的方法 getStringList 的泛型返回类型。

 /**
 * @author mghio
 * @date: 2019-12-29
 * @version: 1.0
 * @description: 通过反射获取泛型信息
 * @since JDK 1.8
 */public class ReflectGenericDemoTests {

  @Test
  public void testMethodReturnGenericType() throws NoSuchMethodException {
    Class<ReflectGenericDemo> reflectClass = ReflectGenericDemo.class;
    Method method = reflectClass.getMethod("getStringList", (Class<?>) null);
    Type returnType = method.getGenericReturnType();
    if (returnType instanceof ParameterizedType) {
      ParameterizedType type = (ParameterizedType) return Type ;
      Type[] typeArguments = type.getActualTypeArguments();
      for (Type typeArgument : typeArguments) {
        Class typeArgumentClass = (Class) typeArgument;
        System.out.println("typeArgumentClass = " + typeArgumentClass);
      }
    }
  }

}  

运行结果

 typeArgumentClass = class java.lang.Integer  

typeArguments 数组只有一个值,这个数组中唯一的值是 Integer Class 类的实例,同时 Class 类也实现了 Type 接口。

第 ② 种 泛型方法返回类型 泛型方法参数类型,我们也可以通过反射来获取方法参数的泛型类型。

 /**
 * @author mghio
 * @date: 2019-12-29
 * @version: 1.0
 * @description: 通过反射获取泛型信息
 * @since JDK 1.8
 */public class ReflectGenericDemo {

  protected List<Integer> stringList = Arrays.asList(2, 55, 3, 90, 81);

  public void setStringList(List<Integer> stringList) {
    this.stringList = stringList;
  }
}  

可以通过以下方式获取方法参数的泛型类型。

 /**
 * @author mghio
 * @date: 2019-12-29
 * @version: 1.0
 * @description: 通过反射获取泛型信息
 * @since JDK 1.8
 */public class ReflectGenericDemoTests {

  @Test
  public void testMethodParameterGenericType() throws NoSuchMethodException {
    Class<ReflectGenericDemo> reflectClass = ReflectGenericDemo.class;
    Method method = reflectClass.getMethod("setStringList", List.class);
    Type[] genericParameterTypes = method.getGenericParameterTypes();
    for (Type genericParameterType : genericParameterTypes) {
      if (genericParameterType instanceof ParameterizedType) {
        ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
        Type[] parameterArgTypes = parameterizedType.getActualTypeArguments();
        for (Type parameterArgType : parameterArgTypes) {
          Class parameterArgClass = (Class) parameterArgType;
          System.out.println("parameterArgClass = " + parameterArgClass);
        }
      }
    }
  }

}  

运行结果

 parameterArgClass = class java.lang.Integer  

第 ③ 种 泛型变量类型 可以通过反射来访问类中定义变量的泛型类型,不管这个变量是一个类的静态成员变量或是实例成员变量。

 /**
 * @author mghio
 * @date: 2019-12-29
 * @version: 1.0
 * @description: 通过反射获取泛型信息
 * @since JDK 1.8
 */public class ReflectGenericDemo {

  private List<Integer> stringList = Arrays.asList(2, 55, 3, 90, 81);

}  

我们可以通过以下代码来获取类 ReflectGenericDemo 的私有变量 stringList 的泛型变量类型。

 /**
 * @author mghio
 * @date: 2019-12-29
 * @version: 1.0
 * @description: 通过反射获取泛型信息
 * @since JDK 1.8
 */public class ReflectGenericDemoTests {

  @Test
  public void testFieldGenericType() throws NoSuchFieldException {
    Class<ReflectGenericDemo> reflectClass = ReflectGenericDemo.class;
    Field field = reflectClass.getDeclaredField("stringList");
    Type type = field.getGenericType();
    if (type instanceof ParameterizedType) {
      ParameterizedType fieldGenericType = (ParameterizedType) type;
      Type[] fieldGenericTypes = fieldGenericType.getActualTypeArguments();
      for (Type genericType : fieldGenericTypes) {
        Class fieldGenericTypeClass = (Class) genericType;
        System.out.println(fieldGenericTypeClass);
      }
    }
  }

}  

运行结果

 class java.lang.Integer 

数组 fieldGenericTypes 只有一个元素,它代表类 Integer Class 类的实例。我们可以得出通过反射获取泛型信息的套路都是先获取 Class 类对象,然后通过该对象获取相应的类,如果是要获取变量的泛型信息就先获取到 Field 类,如果是要获取方法的泛型信息就先获取到 Method 类,最后再通过是否是 ParameterizedType 的实例来判断是否是泛型类型。

总结

我们介绍了 Java 泛型的基本使用,反射可能在我们日常的工作中不怎么接触到,但是,在很多框架中都有运用,比如, Spring IOC/DI 也是反射;还有 JDBC classForName 也是反射。所有深入了解 Java 反射机制很有必要。

方法

描述

Constructor getConstructor(Class[] params)

根据构造方法的参数,返回一个 public 类型的构造方法

Constructor getConstructors()

返回所有 public 类型的构造方法数组

Constructor getDeclaredConstructor(Class[] params)

根据构造方法的参数,返回一个具体的构造方法(所有的类型)

Constructor getDeclaredConstructors()

返回该类中所有的构造方法数组(所有的类型)

Method getMethod(String name, Class[] params)

根据方法名和参数,返回一个 public 类型的方法

Method[] getMethods()

返回所有 public 类型的方法数组

Method getDeclaredMethod(String name, Class[] params)

根据方法名和参数,返回一个具体的方法(所有的类型)

Method[] getDeclaredMethods()

返回该类中的所有的方法数组(所有的类型)

Field getField(String name)

根据变量名,返回一个 public 类型的成员变量

Field[] getFields()

返回 public 类型的成员变量的数组

Field getDeclaredField(String name)

根据变量名,返回一个成员变量(所有的类型)

Field[] getDelcaredField()

返回所有成员变量组成的数组(所有的类型)