Java反射机制详解
作用
反射的定义:在运行状态中,能获取任意一个类的所有方法和属性;能调用一个对象的所有方法和属性。这种动态获取类信息和动态调用对象方法和属性的功能就是Java的反射机制。
注意定义中的措辞,是所有的方法和属性,即使是私有的也能调用。所以功能是非常强大的。但在我们日常开发中很少会用到反射机制,因为正是这种强大的机制反而会破坏我们应用代码的封装性。日常中不用不代表就没用,很多框架的设计其实都用到了反射机制。如果熟悉Spring的话不难发现,Spring中有大量关于反射的应用。比如我们在配置XML时就会被要求写对应类的全限定名,在Spring的BeanFactor中就会读取这些配置文件,并通过反射去创建Class对象。连接数据库时,会要求指定驱动程序,比如com.mysql.jdbc.Driver,其实也通过反射机制加载的。
原理
在此之前先要明白,在Java中万物皆对象,某个类本身可以是个对象,类里面的结构,比如属性、方法等也可以是个对象。在JVM进行类加载的过程中,在第一步也就是加载步骤,会在内存中生成一个代表该类的Class对象。需要注意这个Class对象和该类的实例对象不是同一个东西。Class对象的类型就是Class,大家可以看看Class的API或者直接看它的源码。我们使用Class.forName("类的全限定名")时其实就是获取这个类的Class对象。具体的API可以看下面。通过这个Class对象我们就可以获取这个类的属性、方法、注解等信息。那么是如何获取这些信息的?首先Class这个类提供了获取这些信息的方法,既然提供了获取的方法,那说明这些信息肯定存在于某些地方。还是得从JVM层面来说,一个类的静态成员,即静态方法和属性都是存在方法区的,这些成员在类加载时就生成了,所以它们与该类的实例对象没有什么关系,只与这个类本身也就是Class对象相关。而那些非静态成员会随着实例对象的创建一起存进Java堆当中,所以我们获取这些非静态成员时,一定是根据某个实例对象来获取的。这两种情况会在下面的示例代码中展现出来。
API
下面的API摘自:https://www.jianshu.com/p/9be58ee20dee,感兴趣的可以去看看。
与Java反射相关的类如下:
类名 |
用途 |
Class类 |
代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 |
代表类的成员变量(成员变量也称为类的属性) |
Method类 |
代表类的方法 |
Constructor类 |
代表类的构造方法 |
Class类
Class代表类的实体,在运行的Java应用程序中表示类和接口。在这个类中提供了很多有用的方法,这里对他们简单的分类介绍。
- 获得类相关的方法
方法 |
用途 |
asSubclass(Class<U> clazz) |
把传递的类的对象转换成代表其子类的对象 |
Cast |
把对象转换成代表类或是接口的对象 |
getClassLoader() |
获得类的加载器 |
getClasses() |
返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() |
返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) |
根据类名返回类的对象 |
getName() |
获得类的完整路径名字 |
newInstance() |
创建类的实例 |
getPackage() |
获得类的包 |
getSimpleName() |
获得类的名字 |
getSuperclass() |
获得当前类继承的父类的名字 |
getInterfaces() |
获得当前类实现的类或是接口 |
- 获得类中属性相关的方法
方法 |
用途 |
getField(String name) |
获得某个公有的属性对象 |
getFields() |
获得所有公有的属性对象 |
getDeclaredField(String name) |
获得某个属性对象 |
getDeclaredFields() |
获得所有属性对象 |
- 获得类中注解相关的方法
方法 |
用途 |
getAnnotation(Class<A> annotationClass) |
返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() |
返回该类所有的公有注解对象 |
getDeclaredAnnotation(Class<A> annotationClass) |
返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() |
返回该类所有的注解对象 |
- 获得类中构造器相关的方法
方法 |
用途 |
getConstructor(Class...<?> parameterTypes) |
获得该类中与参数类型匹配的公有构造方法 |
getConstructors() |
获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) |
获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() |
获得该类所有构造方法 |
- 获得类中方法相关的方法
方法 |
用途 |
getMethod(String name, Class...<?> parameterTypes) |
获得该类某个公有的方法 |
getMethods() |
获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) |
获得该类某个方法 |
getDeclaredMethods() |
获得该类所有方法 |
- 类中其他重要的方法
方法 |
用途 |
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) |
根据传递的参数创建类的对象 |
示例
首先创建一个被反射的类:
public class People { //静态属性 public static String Version="1.1.1"; private String Name; private Integer Age; private String Habit; //公共构造函数 public People(String name, Integer age, String habit) { Name = name; Age = age; Habit = habit; } //私有构造函数 private People(String name){ Name=name; } public People(){ } //私有方法 private void isPrivate(String data){ System.out.println("这是个私有方法,传入的参数为:"+data); } //公有方法 public String getName() { return Name; } //静态方法 public static void StaticVoid(){ System.out.println("这是一个静态方法"); } }
接下来通过Java提供的反射方法获取这个类的构造函数、方法、属性:
public class reflectionTest { public static void main(String[] args) { try { System.out.println("----------------------获取Class对象-----------------------"); //因为我这里People和reflectionTest在同一包下,如果不在同一包下需要传入完整的类路径 Class clazz = Class.forName("People"); System.out.println(clazz); /** * 还有两种获取Class对象的方法,这几种方法获取的Class对象都是同一个对象: * 一般都会用foeName的方式,因为下面第一种方法需要导包 * 第二种方法,对象都创建出来了,除非是要调用私有的东西,那我还反射个毛。 * 1.Class clazz=People.class; * 2.People p=new People(); * Class clazz=p.getClass(); * */ System.out.println("----------------------获取构造函数对象-----------------------"); //获取所有构造函数,不包括私有的,如果想包括私有就用getDeclaredConstructors Constructor[] constructors = clazz.getConstructors(); for (Constructor c : constructors) { System.out.println(c); } //获取私有构造函数并调用(String.class为构造函数参数类型) Constructor constructor = clazz.getDeclaredConstructor(String.class); //忽略访问修饰符,如果是共有的就不需要这一步 constructor.setAccessible(true); //根据这个构造函数创建一个People实例对象 People people = (People) constructor.newInstance("张三"); //最后通过对象输出刚刚设置的姓名 System.out.println(people.getName()); System.out.println("----------------------获取方法对象-----------------------"); //获取所有公共方法,包括父类的方法,比如Object的equals,如果想包含私有方法就用getDeclaredMethods,但只有本类的方法 Method[] methods = clazz.getMethods(); for (Method method : methods) { System.out.println(method); } //获取一个私有方法并调用 Method method = clazz.getDeclaredMethod("isPrivate", String.class); //忽略访问修饰符 method.setAccessible(true); //访问私有方法,如果这个私有方法有返回值用一个变量接收就行了。 //通过invoke调用该私有方法,除了要传入这个方法本来的参数,比如这里的耶low,还需要一个该方法对应类的对象,我们就用上面反射获取的people对象 method.invoke(people, "耶low"); //获取静态方法并调用,发现调用静态方法不需要实例对象,这也符合原理中所说的。 Method method2=clazz.getMethod("StaticVoid"); method2.invoke(null); System.out.println("----------------------获取属性对象-----------------------"); //获取本类的所有属性,包括私有的 Field[] fields=clazz.getDeclaredFields(); for(Field field:fields){ System.out.println(field); } //获取私有属性并调用 Field field=clazz.getDeclaredField("Name"); field.setAccessible(true); //设置这个私有属性 field.set(people,"李四"); //然后获取一下Name,看看设置成功没 System.out.println(people.getName()); //获取静态属性 Field field2=clazz.getField("Version"); //输出静态属性的值 System.out.println(field2.get(clazz)); } catch (Exception e) { e.printStackTrace(); } } }