【Lehr】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);
					/任务完成!
				}
			}
		}
发布了33 篇原创文章 · 获赞 26 · 访问量 2612

猜你喜欢

转载自blog.csdn.net/qq_43948583/article/details/90951847