反射

Java反射是Java语言的一个很重要的特征。

一、概述

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

总之:反射也称对类的解剖,把类的各个组成部门映射成一个个响应的Java类。

要想剖解一个类,必须要取得每一个字节码文件对应的Class类型对象。下面就认识Class类。

二、Class类

首先,看一段代码

public class GetClassDemo {
	public static void main(String[] args) {
		X x = new X();
		System.out.println(x.getClass().getName());
	}
}
class X {
}
程序运行结果可以发现,通过一个对象得到了对象所在的完整的“包.类”名称,那么getClass()方法是哪里定义的了?任何一个类如果没有明确地声明继承自那个父类,则默认继承Object类,所以getClass()方法是Object类中的,此方法定义如下:

public final Class<?> getClass()

此方法返回值的类型是一个Class类,实际上此类是Java反射的源头。通过Class可以完整地得到一个类中的完整结构,包括此类的方法定义、属性定义等。Class类的一些常用方法:

// 传入完整的“包.类”名称实例化Class对象
public static Class<?> forName(String className) throws ClassNotFoundException
// 得到一个类中的全部构造方法
public Constructor<?>[] getConstructors() throws SecurityException
// 得到本类继承而来的全部属性
public Field[] getFields() throws SecurityException
// 取得本类中单独定义的全部属性
public Field[] getDeclaredFields() throws SecurityException
// 得到一个类的全部方法
public Method[] getMethods() throws SecurityException
// 返回一个Method对象,并设置一个方法中的所有参数类型
public Method getMethod(String name, Class<?>... parameterTypes)
                 throws NoSuchMethodException, SecurityException
// 得到一个类完整的“包.类”名称
public String getName()
// 根据Class定义的类实例化对象
public Object newInstance() throws InstantiationException, IllegalAccessException
在Class类中本身没有定义任何的构造方法,所以要使用首先必须通过 forName()方法实例化对象。除此之外,也可以使用“类.class”或“对象.getClass()”方法实例化。

范例:实例化Class类对象。

public class GetClassDemo2 {
	public static void main(String[] args) throws ClassNotFoundException {
		// 方式一
		Y y = new Y();
		Class clazz = y.getClass();
		Y y2 = new Y();
		Class clazz2 = y.getClass();
		System.out.println(y == y2); // false
		System.out.println(clazz == clazz2); // true

		// 方法二
		Class clazz3 = Y.class;
		System.out.println(clazz == clazz3); // true

		Class clazz4 = Class.forName("com.pegasus.demo1.Y");
		System.out.println(clazz == clazz4); // true
	}
}

class Y {
}
从以上程序的3种使用方法上可以发现,除forName()方法外,其他的两种:“对象.getClass()”、“类.class”都需要导入一个明确的类,如果一个类操作不明确,使用时可能会出现问题,但是forName()传入时只需要以字符串的方式传入即可,这让程序具备更强大的灵活性,推荐使用。
三、Class类的应用

①. 通过无参构造实例化对象

如果要想通过Class本身实例化其他类的对象,则可以使用newInstance()方法,但是要保证被实例化的类存在一个无参构造方法。

public class InstanceDemo1 {
	public static void main(String[] args) throws Exception {
		Class<?> clazz = Class.forName("com.pegasus.demo1.Person");
		Person person = null; // 声明Person对象
		person = (Person) clazz.newInstance(); // 实例化Person对象
		person.setName("pegasus");
		System.out.println(person);
	}
}
class Person {
	private String name;
	public String getName() {return name;	}
	public void setName(String name) {this.name = name;	}
	public String toString() {
		return "Person [name=" + name + "]";
	}
}
使用以上操作必须要注意,被实例化对象必须存在无参的构造方法,如果不存在,肯定无法实例化,下面代码所示:

public class InstanceDemo1 {
	public static void main(String[] args) throws Exception {
		Class<?> clazz = Class.forName("com.pegasus.demo1.Person");
		Person person = null; // 声明Person对象
		person = (Person) clazz.newInstance(); // 实例化Person对象
		person.setName("pegasus");
		System.out.println(person);
	}
}
class Person {
	private String name;
	public Person(String name) {
		this.name = name;
	}
	public String getName() {return name;	}
	public void setName(String name) {this.name = name;	}
	public String toString() {
		return "Person [name=" + name + "]";
	}
}
运行抛出错误,因类中没有无参的构造方法,所以无法直接使用newInstance()实例化。

②. 调用有参构造实例化对象
在上面的例子中,没有无参的构造方法,则不能使用newInstance()实例化,但是还有其他的方式进行实例化操作,只需在操作时需要明确地调用类中的构造方法,并将参数传递进去后才可以进行实例化操作,操作步骤如下:

Ⅰ 通过Class类中的getConstructors()取得本类中的全部构造方法。

Ⅱ 向构造方法中传递一个对象数组进去,里面包含了构造方法中所需的各个参数。

Ⅲ 之后通过Constructor实例化对象。

Constructor常用方法:

// 得到构造方法的名称
public String getName()
// 得到构造方法的修饰符
public int getModifiers()
// 得到构造方法中参数的类型
public Class<?>[] getParameterTypes()
// 向构造方法中传递参数,实例化对象
public T newInstance(Object... initargs) throws InstantiationException,
                     IllegalAccessException, IllegalArgumentException, InvocationTargetException
范例:调用类中的有参构造,使用newInstance()方法。

public class InstanceDemo2 {
	public static void main(String[] args) throws Exception {
		Class<?> clazz = Class.forName("com.pegasus.demo1.Person");
		Person person = null; // 声明Person对象
		Constructor<?>[] constructor = null; // 声明构造方法的数组
		constructor = clazz.getConstructors();
		// 向构造方法中传递参数,此方法使用可变参数接收,并实例化对象
		person = (Person) constructor[0].newInstance("Pegasus");
		System.out.println(person);
	}
}
class Person {
	private String name;
	public Person(String name) {this.name = name;	}
	public String getName() {return name;	}
	public void setName(String name) {this.name = name;	}
	public String toString() {
		return "Person [name=" + name + "]";
	}
}
四、反射的应用 ------ 取得类的结构

上面程序是反射应用最多的地方,当然,反射机制所提供的功能远不止如此,还可以通过反射得到一个类的完整结构。

下面通过Person类详细的介绍。

public interface China {
	public static final String NATIONAL = "China";  //全局常量
	public void sayChina();   
	public String sayHello(String name, int age);
}

public class Person implements China{
	private String name;
	private int age;	
	public Person() {}
	public Person(String name) {this.name = name;	}
	public Person(String name, int age) {
		this(name);
		this.age = 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 void sayChina() {
		System.out.println("国籍:" + NATIONAL);
	}
	public String sayHello(String name, int age) {
		return name + ", 你好! 我今年" + age + "岁了!";
	}
}
①. 取得一个类所实现的全部接口,使用Class类中的getInterfaces()方法。

Class<?> clazz = Class.forName("com.pegasus.reflect.Person");
Class<?>[] cls = clazz.getInterfaces();
for (int i = 0; i < cls.length; i++) {
	System.out.println("实现的接口名称:" + cls[i].getName());
}
②. 取得父类,使用Class类的getSuperclass()方法。

Class<?> clazz = Class.forName("com.pegasus.reflect.Person");
Class sup = clazz.getSuperclass();
System.out.println("父類名稱:" + sup.getName());
③. 取得全部构造方法

Class<?> clazz = Class.forName("com.pegasus.reflect.Person");
Constructor[] constructor = clazz.getConstructors();
for (int i = 0; i < constructor.length; i++) {
	System.out.println("构造方法:" + constructor[i]);
}
④. 取得全部方法

要取得一个类中的全部方法,可以使用Class类中的getMethods()方法,此方法返回Method类的对象数组。Method类的常用方法:

// 得到方法的名称
public String getName()
// 得到方法的全部参数类型
public Class<?>[] getParameterTypes()
// 得到方法的返回值类型
public Class<?> getReturnType()
// 通过反射调用中的方法
public Object invoke(Object obj, Object... args) throws IllegalAccessException,
                     IllegalArgumentException, InvocationTargetException
使用getMethods()方法取得Person类的全部方法定义

Class<?> clazz = Class.forName("com.pegasus.reflect.Person");
Method[] method = clazz.getMethods();
for (int i = 0; i < method.length; i++) {
	Class ret = method[i].getReturnType(); // 得到方法的返回值类型
	Class[] params = method[i].getParameterTypes(); // 得到方法的参数类型
	int xx = method[i].getModifiers(); // 返回方法的修饰符
	System.out.print(Modifier.toString(xx) + " "); // 修饰符
	System.out.print(ret.getName() + " ");// 返回方法名称
	System.out.print(method[i].getName());
	System.out.print("(");
	for (int j = 0; j < params.length; j++) {
		System.out.print(params[j].getName() + " " + "arg" + j);
		if (j < params.length - 1) {
			System.out.print(",");
		}
	}
	Class ex[] = method[i].getExceptionTypes(); // 取得全部的异常输出
	if (ex.length > 0) {
		System.out.println(") throws ");
	} else {
		System.out.print(")");
	}
	for (int m = 0; m < ex.length; m++) {
		System.out.print(ex[m].getName());
		if (m < ex.length - 1) {
			System.out.print(",");
		}
	}
	System.out.println();
}

运行结果:

public void setAge(int arg0)
public java.lang.String sayHello(java.lang.String arg0,int arg1)
public int getAge()
public void sayChina()
public java.lang.String getName()
public void setName(java.lang.String arg0)
public final native java.lang.Class getClass()
public native int hashCode()
public boolean equals(java.lang.Object arg0)
public java.lang.String toString()
public final native void notify()
public final native void notifyAll()
public final void wait(long arg0,int arg1) throws java.lang.InterruptedException
public final native void wait(long arg0) throws java.lang.InterruptedException
public final void wait() throws java.lang.InterruptedException

其实不用如此麻烦,可以使用Method类toString()方法,可以方便的打印出方法:

Class<?> clazz = Class.forName("com.pegasus.reflect.Person");
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
	System.out.println(methods[i].toString());
}
getMethods()方法只获取公共和父类中的方法,对私有的方法并获取不到,可以使用getDeclaredMethods()方法,具体使用不详细介绍了。

⑤. 取得全部属性

有两种不同的操作,一是得到实现的接口或父类中的公共属性:

public Filed[] getFields() throws SecurityException.

二是得到本类中的全部属性:

public Field[] getDeclaredFields() throws SecurityException.

Field类的常用方法:

// 得到一个对象中属性的具体内容
public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException
// 设置指定对象中属性的具体内容
public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException
// 得到属性的修饰符
public int getModifiers()
// 返回此属性的名称
public String getName()
// 判断此属性是否被外部访问,此是它的父类AccessibleObject的方法
public boolean isAccessible()
// 设置一个属性是否可以被外部所访问,也是它的父类AccessibleObject的方法
public void setAccessible(boolean flag) throws SecurityException
// 返回此Field类的信息
public String toString()
取得Person类的属性

public static void main(String[] args) throws Exception {
	Class<?> clazz = Class.forName("com.pegasus.reflect.Person");
	System.out.println("本类属性");
	Field[] fields = clazz.getDeclaredFields();
	for (int i = 0; i < fields.length; i++) {
		System.out.println(fields[i].toString());
	}
	System.out.println("公共属性");
	fields = clazz.getFields();
	for (int i = 0; i < fields.length; i++) {
		System.out.println(fields[i].toString());
	}
}
五、Java反射机制的深入应用

①. 通过反射调用类中的方法

使用反射调用类中的方法可以通过Method类完成,操作步骤如下:

Ⅰ 通过Class类的getMethod(String name, Class...parameterTypes)方法取得一个Method的对象,并设置此方法操作时所需的参数类型。

Ⅱ 使用invoke进行调研,并向方法中传递要设置的参数。

范例:给一个ArrayList<Integer>对象,往这个集合中添加一个字符串数据。

ArrayList<Integer> arrayList = new ArrayList<Integer>();
Class clazz = arrayList.getClass();
// 通过遍历找到添加元素的方法,即add(Object)
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
	// System.out.println(methods[i]);
}
methods[1].invoke(arrayList, "pegasus");
Method method = clazz.getMethod("add", Object.class);
method.invoke(arrayList, "hello ");
method.invoke(arrayList, "world!");
System.out.println(arrayList);
②. 调用setter及getter方法

public class InvokeSetGetDemo {
	public static void main(String[] args) throws Exception {
		Class clazz = Class.forName("com.pegasus.reflect.Person");
		Object obj = clazz.newInstance();
		setter(obj, "name", "pegasus", String.class);
		setter(obj, "age", 30, int.class);
		System.out.println("姓名: ");
		getter(obj, "name");
		System.out.println("年龄: ");
		getter(obj, "age");

	}

	private static void getter(Object obj, String att) throws Exception {
		Method method = obj.getClass().getMethod("get" + initStr(att));
		System.out.println(method.invoke(obj));
	}

	private static void setter(Object obj, String att, Object value, Class type)
			throws Exception {
		Method method = obj.getClass().getMethod("set" + initStr(att), type);
		method.invoke(obj, value);
	}

	private static String initStr(String s) {
		String str = s.substring(0, 1).toUpperCase() + s.substring(1);
		return str;
	}
}
此程序注意:设置方法名称,在设置方法名称时,本程序直接使用的是属性名称,例如name或age。但是实际方法名称是setName()、getName()、setAge()、getAge(),所以属性名称首字母需要大写。

③. 通过反射操作属性

public static void main(String[] args) throws Exception {
	Class clazz = Class.forName("com.pegasus.reflect.Person");
	Object obj = clazz.newInstance();
	Field nameField = clazz.getDeclaredField("name");
	nameField.setAccessible(true);  // 将name属性设置成可被外部访问
	nameField.set(obj, "pegasus");
	System.out.println("姓名:" + nameField.get(obj));
} 
此种方法,也被称为“暴力反射”。

注意:在开发中使用反射操作属性时最好通过setter及getter方法。









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值