Java反射基本知识
Class类
Class类是什么
用Java核心技术卷
的官方话来说:
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类,虚拟机利用运行时类型信息选择相应的方法执行。我们子在编程时可以通过专门的Java类访问这些信息,保存这些信息的类就被称为Class。
简单地说,就是:
在Java中,有一个类,专门来描述类本身,这就是Class类,它能描述一个Java类中有哪些接口,构造器,方法,类名,属性等等。
Class类只能由JVM来创建。而且每个Java类只能对应有一个Class实例,这个实例里面就储存了这个Java类的特性:接口是什么,父类是什么,有哪些方法等待。
在以下所有的例子中,我们需要用到一个叫Person的类,具体如下:
public class Person {
//有名字和年龄
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//无参构造器
public Person(){}
//有参构造器
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//一个私有方法
private void secretMethod(){}
}
如何使用Class类
获取到JVM已经创建好的Class类有三种方法:
1.利用类获取
Class clazz1 = Person.class;
2.利用全类名获取
Class clazz2 = Class.forName("com.reflection.Person");
3.利用实例来获取
Class clazz3 = person.getClass();
这种方法往往用于你并不具体知道这个对象是个什么类的情况下,例如:
//这里只是举例,如果从外部传入就不一定知道是Person类了
Object person = new Person();
Class clazz2 = person.getClass();
System.out.println(clazz3);
输出结果:正确获取了具体类
在这里插入图片描述
现在,你拿到的这个叫clazz的对象,是一个类型为Class的,储存了Person这个类的信息的对象,你可以通过使用clazz,利用反射操作,查看和修改Person类的属性和方法了。
接下来我们可以先用这个clazz对象来创建一个Person实例对象了!
利用newInstance()
方法:
Object obj = clazz1.newInstance();
System.out.println(obj);
输出结果:
obj就是个Person类,成功创建!
这里需要注意一下,
newInstance()
方法其实是调用的Person类的无参构造器,所以如果Person类里没有写无参构造器,这里就会失败。
JVM只会给每个Java类创建一个Class实例,我们可以比较刚才三种获取Class方式得到的对象的地址来验证这一点:
System.out.println(clazz1==clazz2);
System.out.println(clazz2==clazz3);
输出结果:
在这里插入图片描述
三类加载器
类加载器(ClassLoader),就是把类加载到JVM里用的。当JVM启动的时候,Java会按以下顺序自顶而下地启动三类加载器并加载相应的类,而且他们依次呈继承关系:
1.引导类加载器(Bootstrap):用C++编写,是JVM自带的类装载器,负责Java负责Java平台核心库,==用来装载核心类==(比如Object类,String类等)。由于引导类加载器涉及到虚拟机本地实现细节,所以不允许直接通过引用进行操作,==不可被直接访问==。
2.扩展类加载器(Extension):
由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的
负责将 <JAVA_HOME >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库 加载到内存中。开发者可以直接使用标准扩展类加载器。
3.系统类加载器(System):
由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的
将==用户写的类==加载到内存中。开发者可以直接使用系统类加载器。
用代码验证继承关系:
//获取系统类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader);
//获取系统类加载器的父类----扩展类加载器
System.out.println(classLoader.getParent());
//获取扩展类加载器的父类----引导类加载器
System.out.println(classLoader.getParent().getParent());
输出结果:
最后一个输出是
null
,因为引导类加载器是无法被直接访问的。
现在再试着验证什么加载器加载什么类:
//测试自己写的类是哪个加载器加载的
System.out.println(Person.class.getClassLoader());
//测试JDK提供的类由哪个加载器负责
System.out.println(Class.forName("java.lang.Object").getClassLoader());
输出结果:
null
仍然是因为引导类加载器无法被直接访问。
反射
能够分析类的能力的程序(如Method方法和Field方法),被称为反射
。
反射是Java被视为动态语言的关键,允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作对象的内部属性和方法。借助反射,能在设计或运行中添加新类时,有快速地应用开发工具动态地查询新添加类的能力
如果说Class是对一个类的描述,那么我们可以用Field,Method和Constructor等反射方法来具体描述这个类,得到具体的属性,方法,或者构造器。
Method
Method是用来描述一个类中的方法的
获取Class对应类"所具有的方法"
1.利用getMethods()
方法,虽然无法获取==private方法==,但能把==继承到的方法==全部获取到:
//clazz1还是上文中的Person类对应的Class实例
Method[] methods1 = clazz1.getMethods();
for(Method method:methods1)
{
System.out.println("All:"+method);
}
输出结果:没用Person中那个被设为private的secretMethod()
,但是有继承了Object类的toString()
、equals()
等方法。
2.通过getDeclaredMethods()
获取,可以获取私有方法,但是无法获取继承方法。
Method[] methods2 = clazz1.getDeclaredMethods();
for(Method method:methods2)
{
System.out.println("Declared:"+method);
}
输出结果:
在这里插入图片描述
获取指定方法和执行方法
获取指定方法getDeclaredMethod
:用名字和形参列表来唯一确定 (注意这里方法结尾没用 s )
Method method3 = clazz1.getDeclaredMethod("setAge", int.class);
//int不是类,但是int.class就是个Class类型的对象
//这里注意int和Integer自己斟酌怎么选用
System.out.println("Now I get :"+method3);
输出结果:
在这里插入图片描述
//执行方法invoke
:需要传入目标实例和参数。
Person object = (Person) clazz1.newInstance();
//给object对象使用method3(setAge)方法,将age设置为10
method3.invoke(object, 10);
System.out.println(object.getAge());
输出结果:
成功修改。
Field
Field中封装了该类中的属性。
获取Class对应类"所具有的属性"
1.使用getFields()
方法,获取可访问的属性。
Field[] field1 = clazz1.getFields();
for(Field field:field1)
{
System.out.println("All:"+field);
}
由于Person中的两个属性都是private,所以什么都不输出,因为Field[] 里是空的。
2.使用getDeclaredFields()
访问具体的属性,可以访问到私有属性。
Field[] field2 = clazz1.getDeclaredFields();
for(Field field:field2)
{
System.out.println("Declared:"+field);
}
输出结果:
在这里插入图片描述
成功获取到age属性和name属性。
获取和修改指定的属性
通过getDeclaredField("属性名")
获得某属性:
Field field3 = clazz1.getDeclaredField("name");
System.out.println(field3);
输出结果:
在这里插入图片描述
通过get(目标对象)
获取指定对象的某属性的值:
//先创建一个对象
Person tommy = new Person("Tommy",18);
//私有变量先设置成可以访问的
field3.setAccessible(true);
System.out.println(field3.get(tommy));
输出结果:
在这里插入图片描述
通过set(目标对象,新设置的属性的值)
来修改属性的值:
field3.set(tommy, "Jack");
System.out.println(tommy.getName());
输出结果:
在这里插入图片描述
注意,通过这种反射方式修改属性是不会调用相应的set方法的。
利用反射获取父类信息
对于一个class对象,可以利用getSuperclass()
来获取父类对象的Class实例。
我们看这样一个例子:
有三个类:Grandpa,Father,Son。其中Son继承Father,Father继承Grandpa。
在Grandpa类里有一个私有的属性private int grandpaMoney = 1000
。我们目前用反射得到了Son类的Class实例,但现在希望借此来实例化一个Grandpa类,并修改grandpaMoney
为2000。
代码思路如下:
//初始条件
Son son = new Son();
Class sonClass = Son.class;
//开始利用反射
Class clazz = sonClass
//如果在当前类找不到这个属性,就一直向上知道Object类
for(;clazz!=Object.class; clazz=clazz.getSuperclass())
{
//获取全部属性
Field[] myField = clazz.getDeclaredFields();
//遍历该类中的属性
for(Field f:myField)
{
//如果名字是那个,就开始修改
if(f.getName().equals("grandpaMoney"))
{
//由于是私有的,设置为可修改
f.setAccessible(true);
//利用反射实例化一个对象
Object obj = clazz5.newInstance();
//修改
f.set(obj, 2000);
/任务完成!
}
}
}