面试官再问你什么是反射,就把这篇文章发给他!

1 什么是反射?

反射是一种可以间接操作目标对象的机制。当使用反射时,JVM 在运行的时候才动态加载类,对于任意类,知道其属性和方法,并不需要提前在编译期知道运行的对象是谁,允许运行时的 Java 程序获取类的信息并对其进行操作。

对象的类型在编译期就可以确定,但程序运行时可能需要动态加载一些类(之前没有用到,故没有加载进 jvm),使用反射可以在运行期动态生成对象实例并对其进行操作。

2 反射的原理

在获取到 Class 对象之后,反向获取和操作对象的各种信息。

3 反射的使用

我们先建一个类

public class People {

	private int age;
	
	private String name;
	
	private People() {
		age = 18;
		name = "Tony";
	}
	
	public People(int age,String name) {
		this.age = age;
		this.name = name;
	}
	
	private void print() {
		System.out.println(this.toString());
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
}

获得 Class 对象

获取 Class 对象有三种方法:

  1. 调用对象的 getClass 方法
  2. 任何数据类型都拥有的 class 属性
  3. 通过 Class 类的静态方法 forName(String className) 进行调用,该方法最常用

在运行期间,一个类只能有一个 Class 对象产生

获取类的构造函数

通过 getDeclaredConstructors 方法我们可以得到类的所有构造方法。

public class Test {

	public static void main(String[] args) {
		Class c = People.class;
		//得到类的所有构造方法
		Constructor[] constructors = c.getDeclaredConstructors();
		for(int i = 0; i < constructors.length; i++) {
			//获得构造方法的类型
			System.out.println("构造方法的类型:" + Modifier.toString(constructors[i].getModifiers()));
			//获得构造方法的所有参数
			Class[] parametertypes = constructors[i].getParameterTypes();
			for (int j = 0; j < parametertypes.length; j++) {
	             System.out.print(parametertypes[j].getName() + " ");
			}
			System.out.println("");
		}
	}
}

返回结果如下:

在这里插入图片描述
通过该方法,我们可以获取类中所有构造方法和构造方法中的参数。

获取类中特定的构造方法

在 getDeclaredConstructor 方法中,我们未传入参数,表示希望得到类的特定构造方法。同时在代码中要进行异常捕获,因为可能不存在对应的构造方法。

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Constructor constructor;
		try {
			//得到类的特定构造方法,无参构造方法不传参数
			constructor = c.getDeclaredConstructor();
			//获得构造方法的类型
			System.out.println("构造方法的类型:" + Modifier.toString(constructor.getModifiers()));
			//获得构造方法的所有参数
			System.out.println("构造方法的参数:");
			Class[] parametertypes = constructor.getParameterTypes();
			for (int j = 0; j < parametertypes.length; j++) {
		            System.out.print(parametertypes[j].getName() + " ");
			}
		} catch (Exception e) {}
	}
}

结果如下:
在这里插入图片描述
如果我们想得到这个构造函数

	public People(int age,String name) {
		this.age = age;
		this.name = name;
	}

在 getDeclaredConstructor 方法中,我们可以传入一个 Class 数组,里面包含 int 和 java.lang.String 的 Class 对象。

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Class[] p = {int.class,String.class}; 
		Constructor constructor;
		try {
			//得到类的特定构造方法,这次传入int和java.lang.String两个参数
			constructor = c.getDeclaredConstructor(p);
			//获得构造方法的类型
			System.out.println("构造方法的类型:" + Modifier.toString(constructor.getModifiers()));
			//获得构造方法的所有参数
			System.out.println("构造方法的参数:");
			Class[] parametertypes = constructor.getParameterTypes();
			for (int j = 0; j < parametertypes.length; j++) {
		            System.out.print(parametertypes[j].getName() + " ");
			}
		} catch (Exception e) {}
	}
}

结果如下:
在这里插入图片描述

调用构造方法

在上面。我们已经学习了如何获取类中特定的构造方法,在这里,我们不仅要获取,还要对类的构造方法进行调用。

我们先修改类的两个构造函数,分别加上一打印语句,代码如下:

	private People() {
		age = 18;
		name = "Tony";
		System.out.println("private People()调用成功");
	}
	
	public People(int age,String name) {
		this.age = age;
		this.name = name;
		System.out.println("public People(int age,String name)调用成功");
	}

我们先使用这个 public 访问类型的构造函数

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Class[] p = {int.class,String.class};
		Constructor constructor;
		try {
			constructor = c.getDeclaredConstructor(p);
			//创建实例
			constructor.newInstance(10,"HaWei");
		} catch (Exception e) {}
	}
}

结果如下
在这里插入图片描述
那么我们如何通过反射调用类的 private 访问类型的构造函数呢?其实大体与上面一样,只是我们需要设置constructors.setAccessible(true);罢了。

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Constructor constructor;
		try {
			//获取类的无参构造函数
			constructor = c.getDeclaredConstructor();
			constructor.setAccessible(true);
			//创建实例
			constructor.newInstance();
		} catch (Exception e) {}
	}
}

结果如下
在这里插入图片描述

调用类的私有方法

我们尝试调用一下类的这个私有方法

	private void print() {
		System.out.println(this.toString());
	}

关于调用方法,我们可以通过 getDeclaredMethod 来获取该方法,然后通过调用 invoke 执行。

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Constructor constructor;
		try {
			//获取类的无参构造函数
			constructor = c.getDeclaredConstructor();
			constructor.setAccessible(true);
			//创建实例
			People obj = (People) constructor.newInstance();
			
			//获取需要调用的方法,需要两个参数,第一个参数是方法名,第二个参数是参数类型(本例不需要传入参数)
			Method method = c.getDeclaredMethod("print");
			method.setAccessible(true);
			//调用方法,需要两个参数,第一个参数是类的实例,第二个参数是方法参数
			method.invoke(obj);
			
		} catch (Exception e) {}
	}
}

结果如下:
在这里插入图片描述

获取类的私有字段并修改值

public class Test {

	public static void main(String[] args){
		Class c = People.class;
		Constructor constructor;
		try {
			//获取类的无参构造函数
			constructor = c.getDeclaredConstructor();
			constructor.setAccessible(true);
			//创建实例
			People obj = (People) constructor.newInstance();
			
			//修改之前的name字段值
			System.out.println("修改之前:" + obj.getName());
			
			//获取类的name字段
			Field field = c.getDeclaredField("name");
			field.setAccessible(true);
			//修改类的私有字段
			field.set(obj,"HaWei");
			
			//修改之后的name字段值
			System.out.println("修改之后:" + obj.getName());
			
		} catch (Exception e) {}
	}
}

结果如下:
在这里插入图片描述

4 反射的优点

可以在运行时获得类的内容,对于 Java 这种先编译再运行的语言,能够让我们很方便的写出灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

5 反射的缺点

  1. 会消耗一定的系统资源
  2. 反射调用方法时可以忽略权限检查,因此可能会破坏封装性从而导致安全问题

6 反射的用途

  1. 进行反编译,把 .class 文件变为 .java 文件
  2. 通过反射机制访问 java 对象的属性,方法
  3. 开发各种通用框架(例如 Spring),许多框架都是配置化的,为了保证框架的通用性,可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象
  4. 可以通过反射运行配置文件内容,利用反射和配置文件可以使应用程序更新时,对源码无需进行任何修改,只需要将新类发送给客户端,并修改配置文件即可
  5. 可以通过反射越过泛型检查,泛型使用在编译期,编译过后泛型擦除,反射作用在运行期,所以是可以通过反射越过泛型检查的

参考:Java反射技术详解
Java基础篇:反射机制详解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章