彻底读懂 Java 反射【前置知识点】

本文详细介绍了Java的反射机制,包括Class类、获取Class对象的四种方式、动态创建实例、调用构造方法、成员变量和方法,以及反射在配置文件读取和框架设计中的应用。同时,讨论了反射的优缺点,如动态编译和安全风险,并给出反射在JDBC和Spring框架中的使用案例。
摘要由CSDN通过智能技术生成

1. 什么是 Java 的反射机制

反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。

在 Java 运行时环境中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

Java 反射机制主要提供了以下功能:

  • 在运行时(动态编译)判断任意一个对象所属的类。
  • 在运行时构造任意一个类的对象。
  • 在运行时判断任意一个类所具有的成员变量和方法。
  • 在运行时调用任意一个对象的方法和属性。

这种动态获取信息以及动态调用对象的方法的功能称为 Java 语言的反射机制

2. Class 类

在程序运行期间,Java 运行时系统始终为所有的对象维护一个被称为运行时的类型标识。 这个信息跟踪着每个对象所属的类。 虚拟机利用运行时类型信息选择相应的方法执行。

可以通过专门的 Java 类访问这些信息。保存这些信息的类被称为 ClassClass 类用于表示 .class 文件(字节码

说的通俗点,反射就是把 Java 类中的各种成分映射成一个个的 Java 对象

例如一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。

如图是类的正常加载过程:

.class 文件读入内存的时候,JVM 会为之创建一个 Class 对象

3. 获取 Class 类对象的四种方式

Class 类对象将一个类的方法、变量等信息告诉运行的程序。Java 提供了四种方式获取 Class 对象:

🔸 1. 知道具体类的情况下可以使用

Class alunbarClass = TargetObject.class;

但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化

🔸 2. 通过 Class.forName()传入类的路径获取

Class alunbarClass1 = Class.forName("com.xxx.TargetObject");

Class.forName(className) 方法,内部实际调用的是一个 native 方法 forName0(className, true, ClassLoader.getClassLoader(caller), caller);

第 2 个 boolean 参数表示类是否需要初始化,Class.forName(className) 默认是需要初始化

一旦初始化,就会触发目标对象的 static 块代码执行,static 参数也会被再次初始化。

🔸 3. 通过对象实例 instance.getClass() 获取

Employee e = new Employee();
Class alunbarClass2 = e.getClass(); // 获取该对象实例的 Class 类对象

🔸 4. 通过类加载器 xxxClassLoader.loadClass() 传入类路径获取

class clazz = ClassLoader.LoadClass("com.xxx.TargetObject");

通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行

虚拟机为每个类型管理一个 Class 对象。 因此,可以利用 == 运算符实现两个类对象比较的操作。 例如,

if(e.getClass() == Employee.getClass())

4. 动态地创建一个类的实例

① 使用 class.newInstance

⭐ 还有一个很有用的方法 newInstance(), 可以用来动态地创建一个类的实例。例如,

e.getClass().newInstance(); 

创建了一个与 e 具有相同类类型的实例。 🚨 newlnstance方法调用默认的构造函数(无参构造函数)初始化新创建的对象。如果这个类没有默认的构造函数, 就会抛出一个异常

forNamenewlnstance 配合起来使用, 可以根据存储在字符串中的类名创建一个对象 :

String s = "java.util.Random"; 
Object m = Class.forName(s).newInstance();

② 使用开源库 Objenesis

Objenesis 是一个Java的库,主要用来创建特定的对象。

由于不是所有的类都有无参构造器又或者类构造器是 private,在这样的情况下,如果我们还想实例化对象,class.newInstance 是无法满足的

public class Test {
    private int i;
 
    public Test(int i){
        this.i = i;
    }
 
    public void show(){
        System.out.println("test..." + i);
    }
 
}
------------------------
    
public static void main(String[] args) {
        Objenesis objenesis = new ObjenesisStd(true);
        Test test = objenesis.newInstance(Test.class);
        test.show();
    }

使用非常简单,Objenesis 由子类 ObjenesisObjenesisStd实现,构造方法中使用了缓存

public ObjenesisStd(boolean useCache) {
    super(new StdInstantiatorStrategy(), useCache);
}

详细源码此处就不深究了,了解即可。

5. 通过反射获取构造方法并调用

批量获取:

  • public Constructor[] getConstructors():获取所有"公有的"构造方法
  • public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

单个获取:

  • public Constructor getConstructor(Class... parameterTypes): 获取一个指定参数类型的"公有的"构造方法:
  • public Constructor getDeclaredConstructor(Class... parameterTypes): 获取一个指定参数类型"构造方法",可以是私有的,或受保护、默认、公有;

举个例子:

package fanshe;
 
public class Student {
	
	//---------------构造方法-------------------
	//(默认的构造方法)
	Student(String str){
		System.out.println("(默认)的构造方法 s = " + str);
	}
	
	// 无参构造方法
	public Student(){
		System.out.println("调用了公有、无参构造方法执行了。。。");
	}
	
	// 有一个参数的构造方法
	public Student(char name){
		System.out.println("姓名:" + name);
	}
	
	// 有多个参数的构造方法
	public Student(String name ,int age){
		System.out.println("姓名:"+name+"年龄:"+ age);//这的执行效率有问题,以后解决。
	}
	
	// 受保护的构造方法
	protected Student(boolean n){
		System.out.println("受保护的构造方法 n = " + n);
	}
	
	// 私有构造方法
	private Student(int age){
		System.out.println("私有的构造方法   年龄:"+ age);
	}
 
}

----------------------------------
    
package fanshe;
 
import java.lang.reflect.Constructor;
 
public class Constructors {
 
	public static void main(String[] args) throws Exception {
        
		// 加载Class对象
		Class clazz = Class.forName("fanshe.Student");
		
		
		// 获取所有公有构造方法
		Constructor[] conArray = clazz.getConstructors();
		for(Constructor c : conArray){
			System.out.println(c);
		}
		
		
		// 获取所有的构造方法(包括:私有、受保护、默认、公有)
		conArray = clazz.getDeclaredConstructors();
		for(Constructor c : conArray){
			System.out.println(c);
		}
        
		
		// 获取公有、无参的构造方法
         // 因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
		// 返回的是描述这个无参构造函数的类对象。
		Constructor con = clazz.getConstructor(null);
		System.out.println("con = " + con);
		//调用构造方法
		Object obj = con.newInstance();
		//	System.out.println("obj = " + obj);
		//	Student stu = (Student)obj;
		
        
		// 获取私有构造方法
		con = clazz.getDeclaredConstructor(char.class);
		System.out.println(con);
		//调用构造方法
		con.setAccessible(true); // 为了调用 private 方法/域 我们需要取消安全检查
		obj = con.newInstance('男');
	}
	
}

6. 通过反射获取成员变量并使用

批量获取:

  • public Field[] getFields():获取所有公有的字段
  • public Field[] getDeclaredFields():获取所有的字段 (包括私有、受保护、默认的)

单个获取:

  • public Field getField(String name): 获取一个指定名称的公有的字段
  • public Field getDeclaredField(String name): 获取一个指定名称的字段,可以是私有、受保护、默认的

举个例子:

package fanshe.field;
 
public class Student {
	public Student(){
		
	}
	//**********字段*************//
	public String name;
	protected int age;
	char sex;
	private String phoneNum;
	
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", sex=" + sex
				+ ", phoneNum=" + phoneNum + "]";
	}

}

----------------------------------
    
package fanshe.field;
import java.lang.reflect.Field;

public class Fields {
 
		public static void main(String[] args) throws Exception {
			// 获取Class对象
			Class stuClass = Class.forName("fanshe.field.Student");
			// 获取所有公有的字段
			Field[] fieldArray = stuClass.getFields();
			for(Field f : fieldArray){
				System.out.println(f);
			}
            
             // 获取所有的字段(包括私有、受保护、默认的)
			fieldArray = stuClass.getDeclaredFields();
			for(Field f : fieldArray){
				System.out.println(f);
			}
            
             // 获取指定名称的公有字段
			Field f = stuClass.getField("name");
             System.out.println(f);
			Object obj = stuClass.getConstructor().newInstance(); // 获取该对象的实例
			// 为字段设置值
			f.set(obj, "刘德华"); // 为Student对象中的name属性赋值
			
			
			// 获取私有字段
			f = stuClass.getDeclaredField("phoneNum");
             System.out.println(f);
			f.setAccessible(true); //暴力反射,解除私有限定
			f.set(obj, "18888889999");
			
		}
	}

7. 通过反射获取成员方法并调用

批量获取:

  • public Method[] getMethods():获取所有"公有方法"(包含了父类的方法也包含 Object 类)
  • public Method[] getDeclaredMethods(): 获取所有的成员方法,包括私有的(不包括继承的)

单个获取:

  • public Method getMethod(String name, Class<?>... parameterTypes): 获取一个指定方法名和参数类型的成员方法:

调用方法

  • Object invoke(Object obj, Object... args):obj 要调用方法的对象;args 调用方法时所传递的实参

举个例子:

package fanshe.method;
 
public class Student {

	public void show1(String s){
		System.out.println("调用了:公有的,String参数的show1(): s = " + s);
	}
	protected void show2(){
		System.out.println("调用了:受保护的,无参的show2()");
	}
	void show3(){
		System.out.println("调用了:默认的,无参的show3()");
	}
	private String show4(int age){
		System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
		return "abcd";
	}
}

-------------------------------------------

package fanshe.method;
 
import java.lang.reflect.Method;
 
public class MethodClass {
 
	public static void main(String[] args) throws Exception {
		// 获取 Class对象
		Class stuClass = Class.forName("fanshe.method.Student");
        
		// 获取所有公有方法
		stuClass.getMethods();
		Method[] methodArray = stuClass.getMethods();
		for(Method m : methodArray){
			System.out.println(m);
		}
        
		// 获取所有的方法,包括私有的
		methodArray = stuClass.getDeclaredMethods();
		for(Method m : methodArray){
			System.out.println(m);
		}
        
		// 获取公有的show1()方法
		Method m = stuClass.getMethod("show1", String.class);
		System.out.println(m);
		Object obj = stuClass.getConstructor().newInstance(); // 实例化一个Student对象
		m.invoke(obj, "刘德华");
		
		// 获取私有的show4()方法
		m = stuClass.getDeclaredMethod("show4", int.class);
		System.out.println(m);
		m.setAccessible(true); //解除私有限定
		Object result = m.invoke(obj, 20); //需要两个参数,一个是要调用的对象(获取有反射),一个是实参
		System.out.println("返回值:" + result);
	}
}

8. 通过反射获取配置文件内容

student 类:

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

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

className = cn.fanshe.Student
methodName = show

测试类:

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
 
/*
 * 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改
 * 我们只需要将新类发送给客户端,并修改配置文件即可
 */
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 的文件内容就可以了。代码就一点不用改动。配置文件更改为:

className = cn.fanshe.Student2
methodName = show2

9. 反射机制优缺点

优点: 动态编译。运行期类型的判断,动态加载类,提高代码灵活度。

缺点

  • 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。
  • 安全问题:让我们可以动态操作改变类的属性同时也增加了类的安全隐患。

10. 反射的应用场景

反射是框架设计的灵魂。

在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。

举例:

  • 我们在使用 JDBC 连接数据库时使用 Class.forName()通过反射加载数据库的驱动程序
  • Spring 框架的 IOC(动态加载管理 Bean)创建对象以及 AOP(动态代理)功能都和反射有联系
  • 动态配置实例的属性

📚 References

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞天小牛肉

您的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值