java学习之reflection反射

时间:2022-10-27 10:32:08

本文旨在学习java的发射机制(java reflection),欢迎交流讨论指正。

1)先来看看什么是所谓的反射机制。Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。(来自于百度百科的定义)

简而言之,java反射指的是允许用户借由某个Class相关的元信息对象间接调用Class对象的功能。

2.1)废话少说,直接开场。看看我们怎么来获得一个类的变量、构造函数、方法。为此,我们先构建一个基础POJO类。

package com.learn.reflect;

public class Car {
	private String brand;
	private String color;
	private int maxSpeed;
	
	public Car(){}
	public Car(String brand, String color, int maxSpeed){
		this.brand = brand;
		this.color = color;
		this.maxSpeed = maxSpeed;
	}
	
	public void introduce(){
		System.out.println("brand:" + brand + ";color:" + color + ";maxSpeed:" + maxSpeed);
	}
	
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
	public String getColor() {
		return color;
	}
	public void setColor(String color) {
		this.color = color;
	}
	public int getMaxSpeed() {
		return maxSpeed;
	}
	public void setMaxSpeed(int maxSpeed) {
		this.maxSpeed = maxSpeed;
	}
}

2.2)下面我们将看到java是如何取得所有的变量、构造函数、方法的。

package com.learn.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectTest {
		
	private ClassLoader loader = Thread.currentThread().getContextClassLoader();

	private void testReflectionMethod() throws Throwable{
		Class<?> clazz = loader.loadClass("com.learn.reflect.Car");
		System.out.println("-----Field-----");
		for(Field field : clazz.getDeclaredFields())
			System.out.println(field);
		System.out.println("-----Constructor-----");
		for(Constructor<?> cons : clazz.getDeclaredConstructors())
			System.out.println(cons);
		System.out.println("-----Method-----");
		for(Method method : clazz.getDeclaredMethods())
			System.out.println(method);
	}public static void main(String[] args) throws Throwable {
		ReflectTest rt = new ReflectTest();
		rt.testReflectionMethod();
	}
}


 下面是Console的输出结果: 
-----Field-----
private java.lang.String com.learn.reflect.Car.brand
private java.lang.String com.learn.reflect.Car.color
private int com.learn.reflect.Car.maxSpeed
-----Constructor-----
public com.learn.reflect.Car()
public com.learn.reflect.Car(java.lang.String,java.lang.String,int)
-----Method-----
public void com.learn.reflect.Car.setBrand(java.lang.String)
public void com.learn.reflect.Car.setMaxSpeed(int)
public void com.learn.reflect.Car.introduce()
public java.lang.String com.learn.reflect.Car.getBrand()
public int com.learn.reflect.Car.getMaxSpeed()
public void com.learn.reflect.Car.setColor(java.lang.String)
public java.lang.String com.learn.reflect.Car.getColor()
 

从输出可以看到,上述的方法确实获得了所有的变量、构造函数、方法。在这里需要提及的是,不论对于任何Field、Constructor、Method,如果有在方法名中有Declared字段,则可以获得所有的访问控制的变量、构造函数、方法。比如说getFields方法,只能获得public权限的变量,而getDeclaredFields方法则可以获得所有控制权限的变量。获得构造函数与方法的方法与此类似。同样,如果你知道某个构造函数或者方法的配参,那么你可以直接获得相对应的构造函数或者方法。可以看下面的代码。

2.3)来看看如何利用反射来构建一个对象的实例。

package com.learn.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectTest {
		
	private ClassLoader loader = Thread.currentThread().getContextClassLoader();

	private Car initByDefaultConst() throws Throwable{
		Class<?> clazz = loader.loadClass("com.learn.reflect.Car");
		Constructor<?> cons = clazz.getDeclaredConstructor((Class[])null);
		Car car = (Car)cons.newInstance();
		Method setBrand = clazz.getMethod("setBrand", String.class);
		setBrand.invoke(car, "红旗CA72");
		Method setColor = clazz.getMethod("setColor", String.class);
		setColor.invoke(car, "黑色");
		Method setMaxSpeed = clazz.getMethod("setMaxSpeed", int.class);
		setMaxSpeed.invoke(car, 200);
		return car;
	}
	
	public static void main(String[] args) throws Throwable {
		ReflectTest rt = new ReflectTest();
		Car car = rt.initByDefaultConst();
		car.introduce();
	}
}
Console的输出结果是:
brand:红旗CA72;color:黑色;maxSpeed:200

可以看到,使用newInstance()函数便可以在知道constructor的情况下获得一个对象的实例。而同时可以用反射获得的对应方法的invoke函数,来为相对应的变量进行赋值。这样最终就可以获得一个实例,而其与用构造函数定义的实例并无二致。

就这样,我们很容易利用反射机制来新建一个的实例。

2.4)下面主要讨论如何对于一个private变量进行赋值的问题。

package com.learn.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectTest {	
	private ClassLoader loader = Thread.currentThread().getContextClassLoader();

	private void testClassField() throws Throwable{
		System.out.println("The output is from class testClassField.");
		Class<?> clazz = loader.loadClass("com.learn.reflect.Car");
		Method getColor = clazz.getMethod("getColor", null);
		System.out.println(getColor.getReturnType());	//class java.lang.String
		Field field = clazz.getDeclaredField("color");
		System.out.println(field);	//private java.lang.String com.learn.reflect.Car.color	//22222
		Car car = initByDefaultConst();
		car.introduce();	//11111
//		field.set(car, "红色");	//Error because the variable color is private.	//AAAAA
		car.setColor("红色");		//The set method	//BBBBB
		car.introduce();	//33333
		Method setColor = clazz.getMethod("setColor", String.class);
		setColor.invoke(car, "黄色");		//The invoke method of setColor	//CCCCC
		car.introduce();	//44444
		field.setAccessible(true);
//		To change accessibility of field becomes true, then the method set of field can be used.
		System.out.println(field);	//private java.lang.String com.learn.reflect.Car.color	//66666
		field.set(car, "蓝色");	//DDDDD
		car.introduce();	//55555
		field = clazz.getDeclaredField("maxSpeed");
		field.setAccessible(true);
		field.setInt(car, 250);
		car.introduce();
	}
	public static void main(String[] args) throws Throwable {
		ReflectTest rt = new ReflectTest();
		rt.testClassField();
	}
}

下面是Console的输出结果:

The output is from class testClassField.
class java.lang.String
private java.lang.String com.learn.reflect.Car.color	//22222
brand:红旗CA72;color:黑色;maxSpeed:200	//11111
brand:红旗CA72;color:红色;maxSpeed:200	//33333
brand:红旗CA72;color:黄色;maxSpeed:200	//44444
private java.lang.String com.learn.reflect.Car.color	//66666
brand:红旗CA72;color:蓝色;maxSpeed:200	//55555
brand:红旗CA72;color:蓝色;maxSpeed:250

下面对产生的结果进行一些简单的分析。

a. 首先可以使用initByDefaultConst中所使用的方法构建一个对应的实例(请看代码中11111位置产生的输出)。

b. 尽管可以用2.2中所介绍的clazz.getDeclaredFields函数获得对应的变量(查看22222处)。但是却无法使用field.set直接更改color这个变量的值(查看AAAAA处)。不过这个时候,还是可以直接调用car.setColor来更改变量的值(查看BBBBB处)。所以产生了(33333处)更改了color后的输出。

c. 使用clazz.getMethod获得相对应的方法,此后在调用invoke函数便可以修改掉变量的值(查看CCCCC处,及44444处的输出)。这很重要!因为invoke函数获得的是Method,而method是public的,所以可以直接对相对应的变量进行改动,这和b中的setColor是一样的。也就是说如果你对一个private变量,设置了public的set函数,那么就可以用getMethod获得修改这个private的能力。反射逆天之一。

d. 来看看反射逆天之二。field.setAccessible(true)。b中已经说了在调用这句话前(AAAAA处),field.set是无效的。但是在这个函数后(DDDDD处),却成功地调用了field.set函数。并且成功的修改了变量的值(看55555处的输出)。而实际上,在调用了field.setAccessible(true)之后,66666处的输出显示,field仍然是private。但是,却明明可以肆无忌惮的修改变量了啊!

综上所述,反射机制就像是能看见现实中的任何阴影一样,让你避无可避,逃无可逃。即使你是private变量,仍然可以随意修改你的值,想怎么改就怎么改。注意两大逆天神器!

提供一个相关的学习链接:http://my.oschina.net/u/1407116/blog/209383?fromerr=9FCqOwmP