作用
反射的定义:在运行状态中,能获取任意一个类的所有方法和属性;能调用一个对象的所有方法和属性。这种动态获取类信息和动态调用对象方法和属性的功能就是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();
}
}
}