反射

深入理解Java类型信息(Class对象)与反射机制
Java基础之—反射
Java类型信息(Class对象)与反射机制
浅谈反射机制

1. 什么是反射

JAVA反射机制是在运行状态中,对于任意一个类,只要给定类的名字和所在的Package就能够得到这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

通过反射可以把java类中的各种成分映射成一个个的Java对象(现在不理解没关系 往下看),也就是我们可以通过反射拿到对应这个类的成员变量,方法,构造函数,他们都是可以通过类对象得到的对象,再通过这些对象进行操作

java的反射是以利于Class类和java.lang.reflect这个类库中的类来实现的;

java.lang.reflect

先来看java.lang.reflect这个类库中的类:

Field类: 表示类中的成员变量
Method: 表示类中的方法
Constructor: 表示类的构造方法
Array: 提供了动态创建数组和访问数组元素的静态方法
Class

而对于ClassClass是用来表示运行时类型信息的对应类
类加载子系统
在这里插入图片描述
如图所示,字节码被加载到方法区(只是加载了这个类的信息,和实例对象都还没有关系,那是在初始化才进行的),构成了instanceKlass的数据结构,在java的堆内存会产生一个对应的_java_mirror的对象(类对象),持有instanceKlass的内存地址(instanceKlass也持有_java_mirror对象的内存地址)

如果以后创建了实例对象,实例对象的对象头指向类对象,再通过类对象到元空间找到.class
这里的类对象就是我们要关注的Class对象,一个类只有一个Class对象,Class对象存储了一个类的所有信息,比如所有方法,所有字段等等

通过new创建实例和反射创建实例,都绕不开Class对象

2. 怎么获得Class对象

Class类只有一个私有的构造函数,所以只有JVM能创建Class实例,为了获得Class类的实例,有以下方法

① 通过对象使用getClass()方法
class Student{
	.....
}
public class Fanshe {
	public static void main(String[] args) {
		//第一种方式获取Class对象  
		Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
		Class stuClass = stu1.getClass();//获取Class对象
		System.out.println(stuClass.getName());
		
	}
}

但是我认为这个方法并没有什么用,我通过反射技术是想得到Class类对象再通过类对象创建实例对象,从而解决松耦合等等问题(如Spring),但这个有了实例对象再获得类对象就没啥用了

② 通过.class

任何数据类型(包括基本数据类型)都有一个“静态”的class属性

package fanshe;
/**
 * 获取Class对象的三种方式
 * 1 Object ——> getClass();
 * 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
 * 3 通过Class类的静态方法:forName(String  className)(常用)
 *
 */
public class Fanshe {
	public static void main(String[] args) {
		//第二种方式获取Class对象
		Class stuClass2 = Student.class;
		System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个

通过字面量的方法获取Class对象的引用不会自动初始化该类。更加有趣的是字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型,这点在反射技术应用传递参数时很有帮助

基本数据类型还有对应的基本包装类型,其包装类型有一个标准字段TYPE,而这个TYPE就是一个引用,指向基本数据类型的Class对象

boolean.class = Boolean.TYPE;
char.class = Character.TYPE;
byte.class = Byte.TYPE;
short.class = Short.TYPE;
int.class = Integer.TYPE;
long.class = Long.TYPE;
float.class = Float.TYPE;
double.class = Double.TYPE;
void.class = Void.TYPE;
③ 通过Class类的静态方法:forName(String className)(常用)
package fanshe;
/**
 * 获取Class对象的三种方式
 * 1 Object ——> getClass();
 * 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
 * 3 通过Class类的静态方法:forName(String  className)(常用)
 *
 */
public class Fanshe {
	public static void main(String[] args) {
		//第三种方式获取Class对象
		try {
			Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		
	}
}

注意调用forName方法时需要捕获一个名称为ClassNotFoundException的异常,因为forName方法在编译器是无法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException异常。

我们使用第三种方法就可以通过配置文件来创建对象了(如Spring里面通过配置xml文件)

3. 通过反射获取构造方法并使用

3.1 获取所有公有构造方法

public static void main(String[] args) throws Exception {
	//1.加载Class对象
	Class clazz = Class.forName("fanshe.Student");
	
	
	//2.获取所有公有构造方法
	System.out.println("**********************所有公有构造方法*********************************");
	Constructor[] conArray = clazz.getConstructors();
	for(Constructor c : conArray){
		System.out.println(c);
	}
}

3.2 获取所有的构造方法(包括:私有、受保护、默认)

conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
	System.out.println(c);
}

3.3 返回指定参数类型、具有public访问权限的构造函数对象

Constructor cs1 =clazz.getConstructor(String.class)//传入的是构造器的参数的类类型

3.4 返回指定参数类型、非公有的构造函数对象

Constructor cs1 =clazz.getDeclaredConstructor(String.class)//传入的是构造器的参数的类类型

3.5 调用构造方法

Object obj = con.newInstance();
Student stu = (Student)obj;

newInstance()底层就是调用无参构造对象的newInstance()

所以,本质上Class对象要想创建实例,其实都是通过构造器对象。如果没有空参构造对象,就无法使用clazz.newInstance(),必须要获取其他有参的构造对象然后调用构造对象的

newInstance()

4. 通过反射调用方法

package fanshe.method;
 
import java.lang.reflect.Method;
 
/*
 * 获取成员方法并调用:
 * 
 * 1.批量的:
 * 		public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
 * 		public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
 * 2.获取单个的:
 * 		public Method getMethod(String name,Class<?>... parameterTypes):
 * 					参数:
 * 						name : 方法名;
 * 						Class ... : 形参的Class类型对象
 * 		public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
 * 
 * 	 调用方法:
 * 		Method --> public Object invoke(Object obj,Object... args):
 * 					参数说明:
 * 					obj : 要调用方法的对象;
 * 					args:调用方式时所传递的实参;
):
 */
public class MethodClass {
 
	public static void main(String[] args) throws Exception {
		//1.获取Class对象
		Class stuClass = Class.forName("fanshe.method.Student");
		//2.获取所有公有方法
		System.out.println("***************获取所有的”公有“方法*******************");
		stuClass.getMethods();
		Method[] methodArray = stuClass.getMethods();
		for(Method m : methodArray){
			System.out.println(m);
		}
		System.out.println("***************获取所有的方法,包括私有的*******************");
		methodArray = stuClass.getDeclaredMethods();
		for(Method m : methodArray){
			System.out.println(m);
		}
		System.out.println("***************获取公有的show1()方法*******************");
		Method m = stuClass.getMethod("show1", String.class);
		System.out.println(m);
		//实例化一个Student对象
		Object obj = stuClass.getConstructor().newInstance();
		m.invoke(obj, "刘德华");
		
		System.out.println("***************获取私有的show4()方法******************");
		m = stuClass.getDeclaredMethod("show4", int.class);
		System.out.println(m);
		m.setAccessible(true);//解除私有限定
		Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
		System.out.println("返回值:" + result);
		
		
	}
}

5. 获取成员变量并调用

package fanshe.field;
import java.lang.reflect.Field;
/*
 * 获取成员变量并调用:
 * 
 * 1.批量的
 * 		1).Field[] getFields():获取所有的"公有字段"
 * 		2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
 * 2.获取单个的:
 * 		1).public Field getField(String fieldName):获取某个"公有的"字段;
 * 		2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
 * 
 * 	 设置字段的值:
 * 		Field --> public void set(Object obj,Object value):
 * 					参数说明:
 * 					1.obj:要设置的字段所在的对象;
 * 					2.value:要为字段设置的值;
 * 
 */
public class Fields {
 
		public static void main(String[] args) throws Exception {
			//1.获取Class对象
			Class stuClass = Class.forName("fanshe.field.Student");
			//2.获取字段
			System.out.println("************获取所有公有的字段********************");
			Field[] fieldArray = stuClass.getFields();
			for(Field f : fieldArray){
				System.out.println(f);
			}
			System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************");
			fieldArray = stuClass.getDeclaredFields();
			for(Field f : fieldArray){
				System.out.println(f);
			}
			System.out.println("*************获取公有字段**并调用***********************************");
			Field f = stuClass.getField("name");
			System.out.println(f);
			//获取一个对象
			Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();
			//为字段设置值
			f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"
			//验证
			Student stu = (Student)obj;
			System.out.println("验证姓名:" + stu.name);
			
			
			System.out.println("**************获取私有字段****并调用********************************");
			f = stuClass.getDeclaredField("phoneNum");
			System.out.println(f);
			f.setAccessible(true);//暴力反射,解除私有限定
			f.set(obj, "18888889999");
			System.out.println("验证电话:" + stu);
			
		}
	}

6. 反射的使用:通过反射运行配置文件内容

Student类

public class Student {
	public void show(){
		System.out.println("is show()");
	}
}

配置文件以txt文件为例子(pro.txt):

className = cn.fanshe.Student
methodName = show

测试类

/*
 * 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改
 * 我们只需要将新类发送给客户端,并修改配置文件即可
 */
public class Demo {
	public static void main(String[] args) throws Exception {
		//通过反射获取Class对象
		Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"
		//2获取show()方法
		Method m = stuClass.getMethod(getValue("methodName"));//show
		//3.调用show()方法
		m.invoke(stuClass.getConstructor().newInstance());
		
	}
	
	//此方法接收一个key,在配置文件中获取相应的value
	public static String getValue(String key) throws IOException{
		Properties pro = new Properties();//获取配置文件的对象
		FileReader in = new FileReader("pro.txt");//获取输入流
		pro.load(in);//将流加载到配置文件对象中
		in.close();
		return pro.getProperty(key);//返回根据key获取的value值
	}
}

当我们升级这个系统时,不要Student类,而需要新写一个Student2的类时,这时只需要更改pro.txt的文件内容就可以了。代码就一点不用改动,(开闭原则)

7. 反射的使用:通过反射越过泛型检查

import java.lang.reflect.Method;
import java.util.ArrayList;
 
/*
 * 通过反射越过泛型检查
 * 
 * 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
 */
public class Demo {
	public static void main(String[] args) throws Exception{
		ArrayList<String> strList = new ArrayList<>();
		strList.add("aaa");
		strList.add("bbb");
		
	//	strList.add(100);
		//获取ArrayList的Class对象,反向的调用add()方法,添加数据
		Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
		//获取add()方法
		Method m = listClass.getMethod("add", Object.class);
		//调用add()方法
		m.invoke(strList, 100);
		
		//遍历集合
		for(Object obj : strList){
			System.out.println(obj);
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值