反射原理
除了int,float等基本类型外,Java的其他类型全部都是class(包括interface),即class的本质是数据类型(Type)。而class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时(也是第一次要使用时才读取),将其加载进内存。每加载一种class,JVM就为其创建一个Class类型的实例(例如:Object 类中的 getClass( ) 方法就将返回一个 Class 类型的实例),并关联起来。注意:这里的Class类型是一个名叫Class的class。源码如下:
public final class Class {
private Class() {}
}
以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并与之关联起来:
Class cls = new Class(String);
这个Class实例是JVM内部创建的,我们自己的Java程序是无法创建Class实例的,因为Class类的构造方法是private,所以,JVM持有的每个Class实例都指向一个数据类型(类或interface):
并且,一个Class实例包含了该class的所有完整信息:
由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等。因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法就称之为反射(Reflection)。
获取Class实例
那如何获取一个class的Class实例呢?有如下三个方法:
方法一
直接通过一个class的静态变量".class"获取:
Class cls = String.class;
方法二
如果我们有一个实例变量(对象),可以通过该实例变量提供的getClass()方法获取:
Object o = new Object();
Class cls = o.getClass();
方法三
如果知道一个class的完整类名,可以通过静态方法Class.forName()方法获取:
Class cls = Class.forName("java.lang.String");
注意四点:
- 因为Class实例在JVM中是唯一的,所以,上述方法获取的Class实例是同一个实例,可以用==比较。
- 一个 Class 对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如, int 不是类,但 int.class 是一个 Class 类型的对象(基础类型都是如此)。此外String[].class这类数组的类是[Ljava.lang.String,多了一个"[L",这是java的历史遗留造成的,会返回一个奇怪的名称
- instanceof与Class实例比较的区别:instanceof不但匹配指定类型,还匹配指定类型的子类。而用==判断class实例可以精确地判断数据类型,但不能作子类型比较。
- JVM在第一次读取到一种class类型时(也是第一次要使用时才读取),才将其加载进内存,这就是JVM动态加载class的特性,此特性对于Java程序非常重要。你可以在运行期间根据条件来控制加载哪一个class,比如用Class.forName(name)没找到该类,就加载另一个什么的。
简单应用
public static void main(String[] args) throws ClassNotFoundException {
//用class的静态变量,获取Class实例
Class stringClass = String.class;
printClassInfo(stringClass);
//用实例对象提供的getClass()方法,获取Class实例
Object object = new Object();
Class objectClass = object.getClass();
printClassInfo(objectClass);
//用静态方法Class.forName()获取Class实例
Class runnableClass = Class.forName("java.lang.Runnable");
printClassInfo(runnableClass);
}
static void printClassInfo(Class cls) {
System.out.println("Class name: " + cls.getName());
System.out.println("Simple name: " + cls.getSimpleName());
if (cls.getPackage() != null) {
System.out.println("Package name: " + cls.getPackage().getName());
}
System.out.println("is interface: " + cls.isInterface());
System.out.println("is enum: " + cls.isEnum());
System.out.println("is array: " + cls.isArray());
System.out.println("is primitive: " + cls.isPrimitive());
}
访问对象内容
上一节中主要讲了怎么在类的角度使用反射,那么站在对象的角度,反射又能做什么呢?
对任意的一个Object对象,只要我们获取了它的Class,就可以获取它的一切信息,包括私有变量与方法。
获取对象字段
会用到以下几个Class类的方法,(这些不是Field类的方法,只是返回Field类的对象而已,getName(),getType(),getModifiers()才是)
方法 | 用途 |
---|---|
getField(String name) | 根据字段名获取某个public的field(包括父类) |
getDeclaredField(String name) | 根据字段名获取当前类的某个field(不包括父类) |
getFields() | 获取所有public的field(包括父类) |
getDeclaredFields() | 获取当前类的所有field(不包括父类) |
利用反射拿到字段的一个Field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。
例如,对于一个Person实例,我们可以先拿到name字段对应的Field,再获取这个实例的name字段的值:
public static void main(String[] args) throws Exception {
Object p = new Person("Xiao Ming");
//获取Person的Class实例
Class c = p.getClass();
//获取Class实例即Person类的字段信息
Field f = c.getDeclaredField("name");
//获取p对象的字段值内容,用Field的field.get(Object)方法
Object value = f.get(p);
System.out.println(value); // "Xiao Ming"
}
static class Person {
private String name;
public Person(String name) {
this.name = name;
}
正常情况下,我们已经获取到值了,但是这里我们会报一个“java.lang.IllegalAccessException”错误,这是因为name被定义为一个private字段,正常情况下,Main类无法访问Person类的private字段。要修复错误,可以将private改为public,或者,在调用Object value = f.get ( p );前,先加一句:
//无论是否是public权限,都可以访问其值
field.setAccessible(true);
当然可以get()值,自然Field类也可以设置值
//第一个Object参数为指定实例,第二个为修改值
//如果是非public值,也要设置field.setAccessible(true)
field.set(Object, Object)
这里已经很明显可以看出,封装性被破坏了,那封装还有什么意义呢?
答案是:正常情况下我们总是通过p.name来访问Person的name字段,有权限限制,这样就达到了数据封装的目的,不会用到反射。反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,反射更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值,但是如果想进阶,反射是一定要会的,此外,setAccessible(true)也可能会失败,如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。
获取对象方法
属性能获取,自然方法也能获取,Class类提供了以下几个方法来获取Method,Method对象也提供了一些方法,例如:getName()、getReturnType()、getParameterTypes()、getModifiers()
方法 | 用途 |
---|---|
getMethod(String name, Class…<?> parameterTypes) | 获取某个public的Method(包括父类) |
getDeclaredMethod(String name, Class…<?> parameterTypes) | 获取当前类的某个Method(不包括父类) |
getMethods() | 获取所有public的Method(包括父类) |
getDeclaredMethods() | 获取当前类的所有Method(不包括父类) |
以下代码可以反射调用String类的substring(int a)方法
public static void main(String[] args) throws Exception {
//String对象
String string = "Hello World!";
//String的Class类实例
Class stringClass = String.class;
//获取String类的substring(int)方法,参数为int.class,IDE还会提示你选哪个方法与参数
Method substringMethod = stringClass.getMethod("substring", int.class);
//在string对象上调用该方法并获取结果
String r = (String) substringMethod.invoke(string, 6);
System.out.println(r);
}
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null。
// 获取Integer.parseInt(String)方法,参数为String:
Method m = Integer.class.getMethod("parseInt", String.class);
// 调用该静态方法并获取结果:
Integer n = (Integer) m.invoke(null, "12345");
// 打印调用结果:
System.out.println(n);
调用非public方法
和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用,代码用下
public static void main(String[] args) throws Exception {
Person p = new Person();
Method m = p.getClass().getDeclaredMethod("setName", String.class);
m.setAccessible(true);
m.invoke(p, "Bob");
System.out.println(p.name);
}
class Person {
String name;
private void setName(String name) {
this.name = name;
}
}
此外,setAccessible(true)也可能会失败,原因和Field一样,SecurityManager会根据规则进行检查,有可能阻止setAccessible(true)。
多态下调用方法
一个Person类定义了hello()方法,并且它的子类Student也覆写了hello()方法,那么,从Person.class获取的Method,作用于Student实例时,调用的方法到底是哪个?
使用子类的方法,反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。
调用构造方法
方法 | 用途 |
---|---|
getConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getDeclaredConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
通过反射来创建新的实例,可以调用Class提供的newInstance()方法:
Person p = Person.class.newInstance();
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例:
public static void main(String[] args) throws Exception {
// 获取构造方法Integer(int):
Constructor cons = Integer.class.getConstructor(int.class);
// 调用构造方法:
Integer n = (Integer) cons.newInstance(123);
System.out.println(n);
}
注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。
调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。
获取继承关系
通过Class对象可以获取继承关系,即它的父类与接口
方法 | 用途 |
---|---|
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
注意:
- 除Object外,其他任何非interface的Class都必定存在一个父类类型。对Object类使用getSuperclass()方法,会返回null
- 对所有interface的Class调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces(),如果一个类没有实现任何interface,那么getInterfaces()返回空数组。
简单应用
public static void main(String[] args) {
Class i = Integer.class;
Class n = i.getSuperclass();
System.out.println(n); //class java.lang.Number
Class[] is = i.getInterfaces();
for (Class s : is) {
System.out.println(s); //返回三个接口分别是Comparable,Constable,ConstantDesc
}
//对接口调用getSuperclass()总是返回null,获取接口的父接口要用getInterfaces()
System.out.println(java.io.Closeable.class.getSuperclass()); //null
Class[] closeableSuper = java.io.Closeable.class.getInterfaces();
for (Class cs : closeableSuper) {
System.out.println(cs); //interface java.lang.AutoCloseable
}
}
反射相关类与常用方法
以下是四种反射相关类,与常用方法,上文使用的方法,都可以查的到
类名 | 用途 |
---|---|
Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量(成员变量也称为类的属性) |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
Class类
获取类相关的方法
方法 | 用途 |
---|---|
asSubclass(Class<U> clazz ) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) | 根据类名返回类的Class实例 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
注:带有Declared修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法。其他的Annotation、Field、Constructor也是如此。
获取类中属性相关的方法
方法 | 用途 |
---|---|
getField(String name) | 根据字段名获取某个public的field(包括父类) |
getDeclaredField(String name) | 根据字段名获取当前类的某个field(不包括父类) |
getFields() | 获取所有public的field(包括父类) |
getDeclaredFields() | 获取当前类的所有field(不包括父类) |
获得类中方法相关的方法
方法 | 用途 |
---|---|
getMethod(String name, Class…<?> parameterTypes) | 获取某个public的Method(包括父类) |
getDeclaredMethod(String name, Class…<?> parameterTypes) | 获取当前类的某个Method(不包括父类) |
getMethods() | 获取所有public的Method(包括父类) |
getDeclaredMethods() | 获取当前类的所有Method(不包括父类) |
获得类中构造器相关的方法
方法 | 用途 |
---|---|
getConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getDeclaredConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
获取类中注解相关的方法
方法 | 用途 |
---|---|
getAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getDeclaredAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getAnnotations() | 返回该类所有的公有注解对象 |
getDeclaredAnnotations() | 返回该类所有的注解对象 |
类中其他重要的方法
方法 | 用途 |
---|---|
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 |
Field类
Field代表类的成员变量(成员变量也称为类的属性)
方法 | 用途 |
---|---|
equals(Object obj) | 属性与obj相等则返回true |
get(Object obj) | 获得obj中对应的属性值 |
set(Object obj, Object value) | 设置obj中对应属性值 |
Method类
Method代表类的方法
方法 | 用途 |
---|---|
invoke(Object obj, Object… args) | 传递object对象及参数调用该对象对应的方法 |
Constructor类
Constructor代表类的构造方法
方法 | 用途 |
---|---|
newInstance(Object… initargs) | 根据传递的参数创建类的对象 |
来源:CSDN
作者:小夏陌
链接:https://blog.csdn.net/qq_22136439/article/details/104295057