Java学习手册:反射机制

版权声明:本文为博主原创文章,未经博主允许不得转载,转载请务必注明出处: https://blog.csdn.net/MaybeForever/article/details/88652783

一、什么是反射?

能够分析类能力的程序称为反射(reflective)。反射是一种功能强大且复杂的机制,它提供了封装程序集、模块和类型的对象,它允许程序在运行时进行自我检查,也允许对其内部成员进行操作。在Java运行时,对于任意一个类的对象,可以通过反射获取这个类的信息。

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

二、反射的作用

反射机制可以用来:
1、在运行时分析类的能力。
2、在运行时查看对象,例如,编写一个toString()方法供所有类使用。
3、实现通用的数组操作代码。
4、利用Method对象,这个对象很像C++中的指针。

具体而言,反射机制提供的功能主要有:
①得到一个对象所属的类;
②获取一个类的所有成员变量和方法;
③在运行时创建对象;
④在运行时调用对象的方法。

反射机制非常重要的一个作用就是可以在运行时动态地创建类的对象,示例如下:

class Base{
	public void f(){
		System.out.println("Base");
	}
}
class Sub extends Base{
	public void f(){
		System.out.println("Sub");
	}
}
public class Test{
	public static void main(String[] args){
		try{//使用反射机制加载类
			Class c = Class.forName("Sub");
			Base b = (Base)c.newInstance();
			b.f();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}
//程序输出结果如下:
Sub

三、在反射机制中,如何获取Class类?

1、Class.forName(“全限定类名(即类的路径)”)
2、类名.Class
3、实例.getClass()

注意:使用forName()方法更加具备灵活性。(原因如下:)
除forName()方法外,其他两种:“类名.Class”,"实例.getClass()"都需要导入一个明确的类,如果一个类操作不明确时,使用起来可能就会受到一些限制。但是forName()传入时只需要以字符串的方式传入即可,这样就让程序具备了更大的灵活性。

四、Java创建对象的方式有几种?

答案:4种。
1、通过new语句实例化一个对象。
正常方式:引入需要地“包.类”名称 → 通过new实例化 → 取得实例化对象)
2、通过反射机制创建对象。
反射方式:实例化对象 → getClass()方法 → 得到完整地“包.类”名称)
3、通过clone()方法创建一个对象。
4、通过反序列化的方式创建对象。

五、Java反射机制的实现原理

注:以下示例的所有代码已上传至Github:https://github.com/ambition-hb/ReflectDemo

1、认识Class类

所有类的对象实际上都是Class类的实例。在Java中Object类是一切类的父类,Class类也继承自Object类,那么所有类的对象实际上也就都是java.lang.Class类的实例,所以所有的对象都可以转变为java.lang.Class类型表示。(请看如下例子解析:)

//通过一个对象得到完整的“包.类”名称(待补充)
package com.haobi.testdemo;
class A{
}
public class TestDemo01{
	public static void main(String[] args){
		A a = new A();
		System.out.println(a.getClass().getName());
	}
}
//程序输出结果如下:
com.haobi.testdemo.A

从上述程序可以看出,通过一个对象得到了对象所在的完整的“包.类”名称,在A类中,并没有getClass()方法,那么getClass()方法是哪里定义的呢?在Java语言中,任何一个类如果没有明确地声明继承自哪个父类时,则默认继承Object类,所以getClass()方法是从Object类中继承而来地。此方法的定义如下:

public final Class getClass()

以上方法返回值的类型是一个Class类,实际上,此类也是Java反射的源头。

Class表示一个类的本身,通过Class可以完整地得到一个类中的完整结构,包括此类中的方法定义、属性定义等。在Class类中本身没有定义任何的构造方法,所以如果要使用则首先必须通过forName()的静态方法实例化对象。除此之外,也可以使用“类.class”或“对象.getClass()”方法实例化。 使用forName()的静态方法实例化Class对象是较为常见的一种方式,而且,使用forName()方法更加具有灵活性。以上三者方法中,除forName()方法外,其他两种:“类名.Class”,"实例.getClass()"都需要导入一个明确的类,如果一个类操作不明确时,使用起来可能就会受到一些限制。但是forName()传入时只需要以字符串的方式传入即可,这样就让程序具备了更大的灵活性。

2、Class类的使用

通过无参构造实例化对象

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

//通过无参构造实例化对象
package com.haobi;

class Person{//Java Bean:类必须是具体的和公共的,并且具有无参数的构造器
	private String name;
	private int 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 String toString() {
		return "姓名:"+this.name+",年龄:"+this.age;
	}
}

public class InstanceDemo01 {
	public static void main(String[] args) {
		Class<?> c = null;//指定泛型
		try {
			c = Class.forName("com.haobi.Person");//传入要实例化的完整包.类名称
		}catch(ClassNotFoundException e) {
			e.printStackTrace();
		}
		Person per = null;//声明Person对象,Person类中存在无参构造方法
		try {
			per = (Person)c.newInstance();//实例化Person对象
		}catch(Exception e) {
			e.printStackTrace();
		}
		per.setName("浩比");
		per.setAge(18);
		System.out.println(per);//内容输出,调用toString()
	}
}
//程序输出结果如下:
姓名:浩比,年龄:18
//注意:如果类中不存在无参构造方法,则会爆出如下错误:
java.lang.InstantiationException: com.haobi.Person
	at java.lang.Class.newInstance(Unknown Source)
	at com.haobi.InstanceDemo01.main(InstanceDemo01.java:37)
Caused by: java.lang.NoSuchMethodException: com.haobi.Person.<init>()
	at java.lang.Class.getConstructor0(Unknown Source)
	... 2 more
Exception in thread "main" java.lang.NullPointerException
	at com.haobi.InstanceDemo01.main(InstanceDemo01.java:41)

在实际的Java应用程序开发中,反射是最为重要的操作原理,如Strus、Spring框架等。在大部分的操作中基本上都是操作无参构造方法,所以在使用反射开发时,类中一定要保留无参构造方法。

调用有参构造实例化对象

如果类中存在有参构造方法,则无法调用无参构造进行实例化,但可以通过其他的方式进行实例化操作,只是在操作时需要明确地调用类中的构造方法,并将参数传递进去之后才可以进行实例化操作。操作步骤如下:
(1)通过Class类中的getConstructors()取得本类中的全部构造方法。
(2)向构造方法中传递一个对象数组进去,里面包含了构造方法中所需的各个参数。
(3)之后通过Constructor实例化对象。

//通过有参构造实例化对象
package com.haobi;
import java.lang.reflect.Constructor;//导入反射包
class Person{
	private String name;
	private int age;
	public Person(String name, int age) {//通过构造设置属性内容
		this.setName(name);
		this.setAge(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 String toString() {
		return "姓名:"+this.name+",年龄:"+this.age;
	}
}

public class InstanceDemo02 {
	public static void main(String[] args) {
		Class<?> c = null;//指定泛型
		try {
			c = Class.forName("com.haobi.Person");//传入要实例化的完整包.类名称
		}catch(ClassNotFoundException e) {
			e.printStackTrace();
		}
		Person per = null;//声明Person对象
		Constructor<?> cons[] = null;//声明一个表示构造方法的数组
		cons = c.getConstructors();//通过反射取得全部构造
		try {
			//向构造方法中传递参数,此方法使用可变参数接收,并实例化对象
			per = (Person)cons[0].newInstance("浩比",18);//(下附详细解释)
		}catch(Exception e) {
			e.printStackTrace();
		}
		System.out.println(per);//内容输出,调用toString()
	}
}
//程序输出结果如下:
姓名:浩比,年龄:18

以上程序通过Class类取得了一个类中的全部构造方法,并以对象数组的形式返回,因为显示的调用了类中的构造方法,且在Person类中只有一个构造方法,所以直接取出对象数组中的第一个元素即可==(下标为0就表示调用第一个构造方法)==。在声明对象数组时,必须考虑到构造方法中参数的类型顺序。

3、反射的应用——取得类的结构

在实际开发中,以上程序是反射应用最多的地方。但反射机制提供的功能远不止上述两点,还可以通过反射得到一个类的完整结构,需要使用到java.lang.reflect包中的以下几个类:

  • Constructor:表示类中的构造方法。
  • Field:表示类中的属性。
  • Method:表示类中的方法。
    注:以上三个类都是AccessibleObject类的子类。

首先定义接口与类,供后续方法使用。代码如下:

package com.haobi;

interface China{//定义China接口
	public static final String NATIONAL = "China";//定义全局常量
	public static final String AUTHOR = "浩比";//定义全局常量
	public void sayChina();//定义无参的抽象方法
	public String sayHello(String name, int age);//定义有参的抽象方法
}

public class Person implements China{//定义Person类实现接口,必须实现接口的所有抽象方法
	private String name;
	private int age;
	public Person() {//声明无参构造
	}
	public Person(String name) {//声明有一个参数的构造方法
		this.name = name;
	}
	public Person(String name, int age) {//声明有两个参数的构造方法
		this(name);//调用有一个参数的构造方法
		this.setAge(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("作者:"+AUTHOR+",国籍:"+NATIONAL);
	}
	public String sayHello(String name,int age) {//覆写方法,返回信息
		return name+",您好!我今年"+age+"岁了!";
	}
}

取得所实现的全部接口

要取得一个类所实现的全部接口,则必须使用Class类中的getInterfaces()方法。此方法的定义如下:

public Class[] getInterfaces()

getInterfaces()方法返回一个Class类的对象数组,之后直接利用Class类中的getName()方法取得类的名称。

示例代码如下:

package com.haobi;
/*
 * 取得Person类所实现的全部接口
 */
public class GetInterfaceDemo {
	public static void main(String[] args) {
		Class<?> c1 = null;//声明Class对象
		try {
			c1 = Class.forName("com.haobi.Person");//实例化Class对象
		} catch (Exception e) {
			e.printStackTrace();
		}
		Class<?> c[] = c1.getInterfaces();//取得实现的全部接口
		for(int i=0;i<c.length;i++) {
			System.out.println("实现的接口名称:"+c[i].getName());//输出接口的名称
		}
	}
}
//程序输出结果如下:
实现的接口名称:com.haobi.China

由于接口是类的特殊形式,而且一个类可以实现多个接口,所以此时以Class数组的形式将全部的接口对象返回,并利用循环的方式将内容依次输出。

取得父类

一个类可以实现多个接口,但是只能继承一个父类,所以如果要取得一个类的父类,可以直接使用Class类中的getSuperclass()方法。此方法定义如下:

public Class<? super T>getSuperclass()

示例代码如下:

package com.haobi;
/*
 * 取得Person的父类
 */
public class GetSuperClasssDemo {
	public static void main(String[] args) {
		Class<?> c1 = null;//声明Class对象
		try {
			c1 = Class.forName("com.haobi.Person");//实例化Class对象
		} catch (Exception e) {
			e.printStackTrace();
		}
		Class<?> c2 = c1.getSuperclass();//取得父类
		System.out.println("父类名称:"+c2.getName());//输出信息
	}
}
//程序输出结果如下:
父类名称:java.lang.Object

Person类在编写时没有明确的继承一个父类,所以默认继承Object类。

取得全部构造方法

要取得一个类中的全部构造方法,则必须使用Class类中的getConstructors()方法。

示例代码如下:

package com.haobi;
import java.lang.reflect.Constructor;//导入反射操作包
/*
 * 取得Perosn类中的全部构造方法
 */
public class GetConstructorDemo {
	public static void main(String[] args) {
		Class<?> c1 = null;//声明Class对象
		try {
			c1 = Class.forName("com.haobi.Person");//实例化Class对象
		} catch (Exception e) {
			e.printStackTrace();
		}
		Constructor<?> con[] = c1.getConstructors();//取得全部构造方法
		for(int i=0;i<con.length;i++) {//循环输出
			System.out.println("构造方法:"+con[i]);
		}
	}
}
//程序输出结果如下:
构造方法:public com.haobi.Person(java.lang.String,int)
构造方法:public com.haobi.Person(java.lang.String)
构造方法:public com.haobi.Person()

以上程序取得了Person类的全部的构造方法,是直接通过输出Constructor对象得到的完整构造方法信息。

取得全部方法

要取得一个类中的全部方法,可以使用Class类中的getMethods()方法,此方法返回一个Method类的对象数组。而如果想要进一步取得方法的具体信息,例如方法的参数、抛出的异常声明等,则必须依靠Method类。

示例1代码如下:

package com.haobi;
//取得Person类的全部方法
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class GetMethodDemo1 {
	public static void main(String[] args) {
		Class<?> c1 = null;//声明Class对象
		try {
			c1 = Class.forName("com.haobi.Person");//实例化Class对象
		} catch (Exception e) {
			e.printStackTrace();
		}
		Method m[] = c1.getMethods();//取得全部的方法
		for(int i=0;i<m.length;i++) {//循环输出
			System.out.println("全部方法:"+m[i]);
		}
	}
}
//程序输出结果如下:
全部方法:public java.lang.String com.haobi.Person.getName()
全部方法:public void com.haobi.Person.setName(java.lang.String)
全部方法:public int com.haobi.Person.getAge()
全部方法:public void com.haobi.Person.sayChina()
全部方法:public java.lang.String com.haobi.Person.sayHello(java.lang.String,int)
全部方法:public void com.haobi.Person.setAge(int)
全部方法:public final void java.lang.Object.wait() throws java.lang.InterruptedException
全部方法:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
全部方法:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
全部方法:public boolean java.lang.Object.equals(java.lang.Object)
全部方法:public java.lang.String java.lang.Object.toString()
全部方法:public native int java.lang.Object.hashCode()
全部方法:public final native java.lang.Class java.lang.Object.getClass()
全部方法:public final native void java.lang.Object.notify()
全部方法:public final native void java.lang.Object.notifyAll()

从程序的运行结果可以发现,程序不仅将Person类的方法输出,也把从Object类中继承的方法同样进行了输出。

示例2代码如下:(取得方法的具体信息)

package com.haobi;
import java.lang.reflect.Method;//导入操作包
import java.lang.reflect.Modifier;//导入操作包
/*
 * 取得Person类的全部方法
 */
public class GetMethodDemo {
	public static void main(String[] args) {
		Class<?> c1 = null;//声明Class对象
		try {
			c1 = Class.forName("com.haobi.Person");//实例化Class对象
		} catch (Exception e) {
			e.printStackTrace();
		}
		Method m[] = c1.getMethods();//取得全部的方法
		for(int i=0;i<m.length;i++) {//循环输出
			Class<?> r = m[i].getReturnType();//得到方法的返回值类型
			Class<?> p[] = m[i].getParameterTypes();//得到全部的参数类型
			int xx = m[i].getModifiers();//得到方法的修饰符
			System.out.print(Modifier.toString(xx)+" ");//--修饰符
			System.out.print(r.getName()+" ");//--返回值类型
			System.out.print(m[i].getName());//--方法名
			System.out.print("(");//--左括号
			for(int x=0;x<p.length;x++) {
				System.out.print(p[x].getName()+" "+"arg"+x);//--参数
				if(x<p.length-1) {//判断是否输出参数的分割符","
					System.out.print(",");//--参数分隔符
				}
			}
			Class<?> ex[] = m[i].getExceptionTypes();//得到全部的异常抛出
			if(ex.length>0) {//有异常
				System.out.print(") throws");//--右括号+throws
			}else {//没有异常
				System.out.print(")");//--右括号
			}
			for(int j=0;j<ex.length;j++) {
				System.out.print(ex[j].getName());//--异常信息
				if(j<ex.length-1) {
					System.out.print(",");//--异常信息间隔符","
				}
			}
			System.out.println();//换行
		}
	}
}
//程序输出结果如下:
public java.lang.String getName()
public void setName(java.lang.String arg0)
public void sayChina()
public void setAge(int arg0)
public int getAge()
public java.lang.String sayHello(java.lang.String arg0,int arg1)
public final void wait() throwsjava.lang.InterruptedException
public final void wait(long arg0,int arg1) throwsjava.lang.InterruptedException
public final native void wait(long arg0) throwsjava.lang.InterruptedException
public boolean equals(java.lang.Object arg0)
public java.lang.String toString()
public native int hashCode()
public final native java.lang.Class getClass()
public final native void notify()
public final native void notifyAll()

取得全部属性

在反射操作中也同样可以取得一个类中的全部属性,但是在取得属性时有以下两种不同的操作。

  • 得到实现的接口或父类中的公共属性:
public Field[] getFields() throws SecurityException
  • 得到本类中的全部属性:
public Field[] getDeclaredFields() throws SecurityException

以上方法返回的都是Field的数组,每一个Field对象表示类中的一个属性。

示例代码如下:

package com.haobi;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/*
 * 取得Person类中的属性
 */
public class GetFieldDemo {
	public static void main(String[] args) {
		Class<?> c1 = null;//声明Class对象
		try {
			c1 = Class.forName("com.haobi.Person");//实例化Class对象
		} catch (Exception e) {
			e.printStackTrace();
		}
		Field f1[] = c1.getDeclaredFields();//取得本类属性
		for(int i=0;i<f1.length;i++) {
			System.out.println("本类属性:"+f1[i]);
		}
		Field f2[] = c1.getFields();//取得父类的公共属性
		for(int j=0;j<f2.length;j++) {
			System.out.println("接口或父类的公共属性:"+f2[j]);
		}
	}
}
//程序输出结果如下:
本类属性:private java.lang.String com.haobi.Person.name
本类属性:private int com.haobi.Person.age
接口或父类的公共属性:public static final java.lang.String com.haobi.China.NATIONAL
接口或父类的公共属性:public static final java.lang.String com.haobi.China.AUTHOR

猜你喜欢

转载自blog.csdn.net/MaybeForever/article/details/88652783