Java反射机制的基本概念与使用

本篇文章分为以下几个部分:

1.认识反射

2.反射的源头(Class类)

3.利用反射操作构造方法

4.利用反射调用类中的方法

5.反射中的invoke方法

6.利用反射调用类中的属性

反射在我们普通程序开发中基本使用不到,但是在我们底层的程序设计中使用特别广泛,例如代理模式、工厂模式等一些设计模式,包括我们使用的开发工具以及各大开源框架底层都使用到了反射的原理。所以掌握了Java的反射机制对我们理解各大开源框架都有很好的帮助。

 

1.认识反射

反射,从这个“反”字可以看出与我们平时正常的使用逻辑肯定不一样,那么到底什么地方不一样了?想要了解“反”,就得先了解一下“正”的概念。

在正常情况下,如果要使用一个类,必须要经过以下几个步骤:

(1)使用important导入类所在的包(类:java.lang.Class)

(2)通过关键字new进行类对象实例化(构造方法:java.lang.reflect.Constructor)

(3)产生对象可以使用“对象.属性”进行类中属性的调用(属性:java.lang.reflect.Field)

(4)通过“对象.方法()”调用类中的方法(方法:java.lang.reflect.Method)

括号中的红色字体是每个步骤对应反射中使用到的类,如果现在不了解,可以先不用管,后面会一一介绍,这里是为了方便进行比较。

在反射中,使用一个类并不需要导入类的所在包,只要知道类的完整路径就可以知道该类中的所有信息。

反射不需要有明确的类型对象,所有的对象都使用Object表示。可以直接用Object的与反射机制的混合调用类中的方法。

 

2.反射的源头(Class类)

在认识反射机制之前,必须要介绍一下Class类,Class类是整个反射操作的源头,该类的定义如下:

 

public final class Class
extends Object
implements Serializable, GenericDeclaration, Type, AnnotatedElement

 

Class类的实例表示正在运行的Java应用程序中的类和接口。

如果要想使用Class类进行操作,就必须首先产生Class类这个对象,一共有三种方法:

(1)Object类中提供了一个返回Class类的方法,定义如下:

 
public final Class getClass()

(2)利用“类.class”取得。

(3)利用Class类的Static方法取得。

  

public static Class forName(String className)
                        throws ClassNotFoundException

在程序开发过程中,使用第二种方法比较多。但是在程序框架设计中,都是使用第三种方法,也就是反射机制用到的方法。

 

class类实例化对象:

Class类如果使用forName()方法之后,就可以调用Class类中newInstance()无参构造函数方法进行操作,该方法定义如下:

public T newInstance()
              throws InstantiationException,
                     IllegalAccessException

 该方法表示创建此Class对象所表示的类的一个新实例。

具体实例如下:

 

class Student {

	public Student() {
		System.out.println("Student类的构造方法");
	}

	@Override
	public String toString() {
		return "Student类的toString方法";
	}
}

public class ReflectDemo {

	public static void main(String[] args) throws Exception {
		Class cls = Class.forName("com.iflytek.Student");
		// 相当于关键字实例化对象,Object obj = new Student();
		Object obj = cls.newInstance();
		System.out.println(obj);
	}

}

输出结果为:

 

Student类的构造方法
Student类的toString方法

通过上面的实例可以看出,调用newInstace()方法时,程序会默认调用Student类的无参构造方法,并且获取到了Student类的实例化对象,可以调用Student类里面的方法属性。

 

3.利用反射操作构造方法

 

在Class类中有两个方法可以获取类中的构造方法,分别是:

 

获取类中所有的构造方法:

public Constructor[] getConstructors()
                                 throws SecurityException

 获取类中指定的构造方法:

public Constructor getConstructor(Class... parameterTypes)
                              throws NoSuchMethodException,
                                     SecurityException

 

这两个方法返回的都是反射包(java.lang.reflect.*)中的Constructor类,该类提供了单个构造方法的信息以及对它的访问权限。

下面以获取String类中的所有构造方法为例,代码如下:

public class ReflectStringDemo {
	public static void main(String[] args) throws Exception{
		Class cls = Class.forName("java.lang.String");
		//获取所有构造函数
		Constructor[] cons = cls.getConstructors();
		//循环打印
		for (int i = 0; i < cons.length; i++) {
			System.out.println(cons[i]);
		}
	}
}

 打印结果:

public java.lang.String(byte[])
public java.lang.String(byte[],int,int)
public java.lang.String(byte[],java.nio.charset.Charset)
public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int,int,java.nio.charset.Charset)
public java.lang.String(java.lang.StringBuilder)
public java.lang.String(java.lang.StringBuffer)
public java.lang.String(int[],int,int)
public java.lang.String(char[],int,int)
public java.lang.String(char[])
public java.lang.String(java.lang.String)
public java.lang.String()
public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int)
public java.lang.String(byte[],int,int,int)

 上面实例获取了String类中的所有构造方法,包括构造方法中的参数、异常等。

获取所有构造方法看上去并不难,如果想要进行指定构造方法的调用,则必须关注Constructor类,使用newInstance()方法进行实例。

public T newInstance(Object... initargs)
              throws InstantiationException,
                     IllegalAccessException,
                     IllegalArgumentException,
                     InvocationTargetException

 获取指定有参构造方法并且实例化对象实例:

import java.lang.reflect.Constructor;

class Student2 {
	private String name;

	private Integer age;

	public Student2(String name, Integer age) {
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return "name:" + this.name + ";age:" + this.age;
	}
}

public class ReflectConstructorDemo {

	public static void main(String[] args) throws Exception {
		Class cls = Class.forName("com.iflytek.Student2");
		Constructor con = cls.getConstructor(String.class, Integer.class);
		// 这里就相当于Object obj = new Student2("马小超",20);
		Object obj = con.newInstance("马小超", 20);
		System.out.println(obj);
	}

}

 打印结果:

name:马小超;age:20

 通过上面可以看出,如果要实例化一个对象,使用无参构造方法比有参构造方法简单的多,使用无参直接调用newInstance()方法,使用有参则先获取有参构造方法,再通过Constructor中的newInstance()方法,并用指定的初始化参数初始化改实例。很多框架中的底层代码默认都是使用无参构造方法来实例化对象,所以在简单Java类开发中都要明确给出无参构造方法。

 

4.利用反射调用类中的方法 获取类中的方法可以分为两大类,每个大类中又可以分为两小类,风别是:

 

获取包括父类集成而来的方法:

 

         获取全部方法:

       

public Method[] getMethods()
                    throws SecurityException

 获取指定方法:

public Method getMethod(String name,Class... parameterTypes)
                                   throws NoSuchMethodException,
                                    SecurityException

 获取本类中定义的方法:

 

         获取全部方法:

public Method[] getDeclaredMethods()
                            throws SecurityException

 获取指定方法:

public Method getDeclaredMethod(String name,
                                Class... parameterTypes)
                         throws NoSuchMethodException,
                                SecurityException

 实例:

import java.lang.reflect.Method;


class Student3{
	public void fun(){};
	
	public void talk(){};
}


public class ReflectMethodStuDemo {
	public static void main(String[] args) throws ClassNotFoundException{
		Class cls = Class.forName("com.iflytek.Student3");
		//获取本类中定义的方法
		Method[] method = cls.getDeclaredMethods();
		//循环打印
		for (int i = 0; i < method.length; i++) {
			System.out.println(method[i]);
		}
	}
}

 打印结果:

public void com.iflytek.Student3.fun()
public void com.iflytek.Student3.talk()

 如果把上述代码中的getDeclaredMethods()换成getMethods(),打印出来的结果如下:

public void com.iflytek.Student3.fun()
public void com.iflytek.Student3.talk()
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 final void java.lang.Object.wait() 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()

 

使用getMethods()方法时,不仅获取到了Student3类中的方法,Object类中的所有方法也被获取到。

上面的程序是直接调用了Method类中的toString()方法输出的,输出格式并不是很理想,没有异常等相关信息。如果有需要,我们也可以自己整理拼接方法输出。

需要用到Method类中的如下几种方法:

getModifiers():以整数形式返回此 Method 对象所表示方法的 Java 语言修饰符。

getReturnType():返回一个 Class 对象,该对象描述了此Method 对象所表示的方法的正式返回类型。

getName():以 String 形式返回此 Method 对象表示的方法名称。

getParameterTypes():按照声明顺序返回 Class 对象的数组,这些对象描述了此Method 对象所表示的方法的形参类型。

getExceptionTypes():返回 Class 对象的数组,这些对象描述了声明将此Method 对象表示的底层方法抛出的异常类型。

在这需要注意的是,利用getModifiers()获取修饰符并不是简单的输出public、static等,而是以整数形式返回所表示的方法的Java语言修饰符。可借助Modifier类的toString()方法来完成。

自己手动拼接输出类中的方法信息的具体代码如下:

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

interface Message {
	public void get();
}

class Student1 implements Message {

	public void fun() {
	}

	public void print() {
	}

	public void get() {
	}
}

public class ReflectMethodDemo {

	public static void main(String[] args) throws Exception {
		Class cls = Class.forName("com.iflytek.Student1");
		Method[] me = cls.getMethods();
		for (int i = 0; i < me.length; i++) {
			// 此时用了method的toString方法输出,如果有需要,用户也可以自己拼凑输出
			// System.out.println(me[i]);
			// 取得修饰符
			System.out.print(Modifier.toString(me[i].getModifiers()) + " ");
			// 取得返回值类型
			System.out.print(me[i].getReturnType().getSimpleName() + " ");
			// 取得方法名称
			System.out.print(me[i].getName() + "(");
			// 取得方法参数
			Class params[] = me[i].getParameterTypes();
			if (params.length > 0) {
				for (int j = 0; j < params.length; j++) {
					System.out.print(params[j].getSimpleName() + " arg-" + j);
					if (j < params.length - 1) {
						System.out.print(", ");
					}
				}
			}
			System.out.print(") ");
			// 取得异常
			Class[] exp = me[i].getExceptionTypes();
			if (exp.length > 0) {
				System.out.print("throws ");
				for (int j = 0; j < exp.length; j++) {
					System.out.print(exp[j].getSimpleName());
					if (j < exp.length - 1) {
						System.out.println(", ");
					}
				}
			}
			System.out.println("{}");
			System.out.println();
		}
	}
}

 打印结果:

public void get() {}

public void print() {}

public void fun() {}

public final void wait(long arg-0, int arg-1) throws InterruptedException{}

public final native void wait(long arg-0) throws InterruptedException{}

public final void wait() throws InterruptedException{}

public boolean equals(Object arg-0) {}

public String toString() {}

public native int hashCode() {}

public final native Class getClass() {}

public final native void notify() {}

public final native void notifyAll() {}

 通过打印的结果来看,手动拼接的没有直接获取的简单粗暴,看起来比较舒服,该有的都有,自己想要什么结果就可以拼接什么结果。

 

上面的代码一般在编写开发工具中实现,随笔提示就是此类代码的。上面所说的内容在在我们开发过程中是很少用到,那么肯定有人不明白,既然开发过程中用不到,为什么要说这么多了?

前面的知识都是让大家对Java的反射机制有个更好的认识,以及反射的基本使用方法。前面的算是热身,接下来要给大家介绍一个反射中比较重要的方法。

5.反射中的invoke方法

 

调用(invoke):对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。

 

public Object invoke(Object obj,
                     Object... args)
              throws IllegalAccessException,
                     IllegalArgumentException,
                     InvocationTargetException

该方法的英文名字是invoke,中文名称就叫“调用”,该方法在Method类中,Method类本身就代表一个方法,当Method类中的对象调用invoke方法时,就相当于调用了Method对象所代表的方法,方法里面传入对应的参数,实现动态调用方法。这里可能比较难理解,看一下一个简单的实例:

 

import java.lang.reflect.Method;

class Student4 {
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

public class ReflectInvokeDemo {
	public static void main(String[] args) throws Exception {
		Class cls = Class.forName("com.iflytek.Student4");
		// 实例化对象
		Object obj = cls.newInstance();
		//获取setName()方法
		Method setNameMethod = cls.getMethod("setName", String.class);
		//获取getName()方法
		Method getNameMethod = cls.getMethod("getName");
		//调用setName()方法,相当于 对象.setName("马小超");
		setNameMethod.invoke(obj, "马小超");
		//调用getName()方法并输出
		System.out.println(getNameMethod.invoke(obj));
	}
}

上面的例子简单的实现了invoke的用法,在动态代理中和Spring框架中都用到了invoke方法,可以实现方法的动态调用,所以在程序设计时使用很广泛。

 

 

6.利用反射调用类中的属性

 

调用类中的属性和调用类中的方法差不多,也是分为两大类,每个大类里面分为两小类,如下:

 

获取包括继承而来的属性:

 

         获取全部属性:

public Field[] getFields()
                  throws SecurityException

    获取指定属性:

public Field getField(String name)
               throws NoSuchFieldException,
                      SecurityException

 获取本类定义的属性:

 

        获取全部属性:

public Field[] getDeclaredFields()
                          throws SecurityException

 获取指定的属性:

public Field getDeclaredField(String name)
                       throws NoSuchFieldException,
                              SecurityException

 利用反射获取类中的属性,是不提倡使用的,因为违背了面向对象的封装特性。 在Field类中定义了进行属性调用的方法:

 

设置属性内容:

public void set(Object obj,
                Object value)
         throws IllegalArgumentException,
                IllegalAccessException

 获取属性类容:

public Object get(Object obj)
           throws IllegalArgumentException,
                  IllegalAccessException

 实例:

import java.lang.reflect.Field;

class Student5 {

	private String name;

}

public class ReflectFiledDemo {
	public static void main(String[] args) throws Exception {
		Class cls = Class.forName("com.iflytek.Student5");
		//实例化
		Object obj = cls.newInstance();
		//获取属性
		Field nameField = cls.getDeclaredField("name");
		//取消访问检查
		nameField.setAccessible(true);
		//给属性赋值
		nameField.set(obj, "马小超");
		//获取属性值并输出
		System.out.println(nameField.get(obj));
	}
}

 大家在阅读上面代码时会看到有一行代码被加粗了,为什么了?原因是Student类中的name属性是private属性的,不对外开放,如果Field类直接访问该属性,会报权限错误。

 

在Construction,Method,Field三个类中有一个共同的父类AccessibleObject,定义了取消封装的操作:setAccessible(Boolean flag)

 

public void setAccessible(boolean flag)
                   throws SecurityException

该方法默认的是参数是false,表示反射的对象应该实施 Java 语言访问检查。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。

 Java反射机制的基本概念及使用就这么多了,相信大家对反射已经不会感到很陌生了,后面我会介绍一下反射机制在工厂模式、代理模式以及Spring框架中的使用,让大家对反射有更深入的理解。

https://blog.csdn.net/mlc1218559742/article/details/52754310

 

你可能感兴趣的:(Java反射机制的基本概念与使用)