Fork me on GitHub

Java编程思想———反射:运行时的类信息

如果不知道某个对象的确切类型,RTTI可以告诉你。但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它。换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类。

反射允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。

Class类和java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了FieldMethod以及Constructor类。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()getMethods()getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。

当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类。在用它做其他事情之前必须先加载那个类的Class对象。因此,那个类的 .class文件对于JVM来说必须是可获取的:要么在本地机器上,要么可以通过网络获取 。所以RTTI和反射之间真正的区别只在于,RTTI来说,编译器在编译时打开和检查.class文件。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件

反射的基本运用

获取Class对象

方法有三种:

  1. 使用Class类的forName()静态方法
1
Class.forName()
  1. 直接获取某一个对象的class
1
2
Class<?> klass = int.class;
Class<?> classInt = Integer.TYPE;
  1. 调用某个对象的getClass()方法
1
2
StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();

判断是否为某个类的实例

一般地,我们用instanceof关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法,它是一个native方法。

1
public native boolean isInstance(Object obj);

创建实例

通过反射来生成对象主要有两种方式。

  1. 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
1
2
CLass<?> c = String.class;
Object str = c.newInstance();
  1. 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。
1
2
3
4
Class<?> c = String.class;
Constructor constructor = c.getConstructor(String.class);
Object obj = constructor.newInstance("23333");
System.out.println(obj);

获取方法

获取某个Class对象的方法集合,主要有以下几个方法。

  1. getDeclaredMethods()方法返回类或接口声明的所有方法,包括publicprotected、默认访问和private方法,但不包括继承的方法。
  2. getMethods()方法返回某个类的所有public方法,包括其继承类的public方法。
  3. getDeclaredMethod(String name, Class<?>... parameterTypes)方法返回类本身一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
  4. getMethod(String name, Class<?>... parameterTypes)方法返回类及其父类一个特定的方法。

获取构造器信息

获取类构造器主要通过Class类的getConstructor()方法得到Constructor类的一个实例,而Constructor类有一个newInstance(Object... initargs)方法可以创建一个对象实例,此方法可以根据传入的参数来调用相应的Constructor创建对象实例。

获取类的成员变量信息

  1. getDeclareFields():所有已声明的成员变量,不包含父类的成员变量。
  2. getFields():所有public成员变量,包含父类的public成员变量。
  3. getDeclareField(String name)方法返回一个类本身特定的成员变量,其中参数为成员变量名称。
  4. getField(String name)方法返回一个类及其父类特定的成员变量。

调用方法

当我们从类中获取了一个方法后,就可以用invoke()方法来调用这个方法。方法原型为:

1
2
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException

利用反射创建数组

数组在Java中是比较特殊的一种类型,它可以赋值给一个Object引用。

1
2
3
4
5
6
7
8
9
10
public static void testArray() throws ClassNotFoundException {
Class<?> klass = Class.forName("java.lang.String");
Object array = Array.newInstance(klass, 10);
//赋值
Array.set(array, 0, "hello");
Array.set(array, 1, "world");
Array.set(array, 2, "java");
//取值
System.out.println(Array.get(array, 2));
}

其中的Array类为java.lang.reflect.Array。我们通过Array.newInstance()创建数组对象,它的原型是:

1
2
3
public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException {
return newArray(componentType, length);
}

newArray()方法是一个native方法。Array类的set()get()方法都是native方法。

注意事项

由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。另外,反射调用方法时可以忽略访问权限检查,因此可能会破坏封装性而导致安全问题。

求鼓励,求支持!