Java反射机制详解

         第一次听说java反射机制的时候,觉得很高大上,毕竟从名字上了就可以知道它的专业性,事实上java反射也确实很牛逼,当我开始正真学习它的时候,我的整个学习过程中整个人也是懵逼的,但是呢!这也更激起了我想征服它的欲望,随着我与它的不断“亲近”与“磨合”,慢慢的也有了点收获,虽然平时太忙,但我还是觉得得花点时间记录总结下!
        我相信在java的学习过程中,大家肯定都听说过这么一句话“万物皆对象”。首先我先提个问题:既然在java面向对象的世界里,万物皆是对象,那么类是不是一个对象?是谁的对象?当然答案都是肯定的,类是对象,类是java.lang.Class类的实例对象。
        那么类的实例对象要怎么表示?我们平常要实例化一个对象,基本都是new出来的,举个列子:A  a = new A();那么a是不是就表示出来了,那么A也是一个实例对象,且是Class类的实例对象,那么,这要怎么表示呢?下面我通过小例子进行说明。我个人一直认为通过案例驱动学习是最好的学习方式,先要看到效果,你才好去学习它,了解他。它有三种表现方式:

package reflect;
public class ReflectDemo {
	public static void main(String[] args) {
		A a = new A();
		Class c1 = A.class;  //第一种表示方式
		Class c2 = a.getClass();  //第二种表示方式
		Class c3=null;
		try {
			c3 = Class.forName("reflect.A");  //第三种表示方式
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		System.out.println((c1==c2)+","+(c2==c3));
		try {
			A temp = (A)c1.newInstance();  //A必须要有要有无参的构造方法
			temp.print();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}
}

class A{
	public void print(){
	      System.out.println("我是A");
	}
}
输出结果为:
true,true
我是A
        通过这个小例子我们可以知道它的三种表示方法:第一种表示方法c1让我们知道任何一个类它都有一个隐含的静态成员变量class,第二种表示方法让我们知道该类的对象是通过getClass方法得到的,这两种在官网中就表示了类A的类类型,第三种表示方法不仅表示了类类型,还让我们知道这是一个动态加载类。在这里我们就要区分编译和运行的区别了,我建议大家初学java时不要用工具软件学习,直接用记事本编写代码,通过控制台运行输出结果。这对我们了解java的编译和运行是很有帮助的,在编译时刻加载的类是静态加载类、在运行时刻加载的类是动态加载类。我们还可以看到,输出的是true,true。说明这三种表示方法的结果是相等的。这样的话我们就可以通过类的类类型来创建实例对象了。可以看到输出结果:我是A。下面我来讲讲Class类的一些基本API操作。

首先我们先来学习怎样通过一个类的类类型来获取一个类中的所有成员变量、成员方法以及类的构造函数。
先来获取所有的成员变量:

public class ClassUtils {
	public static void main(String[] args) {
		String s="我是String";
		ClassUtils cu = new ClassUtils();
		cu.getClassVariables(s);
	}
	
	/**
	 * 得到类的所有成员变量信息
	 * @param obj:需要得到的信息类对象
	 */
	public void getClassVariables(Object obj){
		Class c = obj.getClass();
		//Field[] fs = c.getFields();获取的是所有的public成员变量的信息
		Field[] fs = c.getDeclaredFields(); //获得该类自己声明的所有变量信息,返回的结果是Filed对象的数组
		for(Field field:fs){
		   //得到成员变量的类型的类类型
		   Class variableType = field.getType(); //得到该字段的声明类型,返回一个Class对象
		   String typeName = variableType.getName();
		   //得到成员变量的名称
		   String variableName = field.getName();
		   System.out.println(typeName+"  "+variableName);
		}	
	}
}
输出结果:
[C  value
int  hash
long  serialVersionUID
[Ljava.io.ObjectStreamField;  serialPersistentFields
java.util.Comparator  CASE_INSENSITIVE_ORDER
int  HASHING_SEED
int  hash32
可以看到得到了String类的所有成员变量信息。

再来获取所有的成员函数:

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

public class ClassUtils {
	public static void main(String[] args) {
		String s="我是String";
		ClassUtils cu = new ClassUtils();
		cu.getClassMethod(s);
	}
	
	/**
	 * 得到类的所有成员函数
	 * @param obj:需要得到的信息类对象
	 */
	public void getClassMethod(Object obj){
		//想要要获取类的信息,首先要获取类的类类型
		Class c=obj.getClass();
		String str=null;
		System.out.println("类的名称是:"+c.getName());  //先获取类的名称
		//Method[] ms = c.getDeclaredMethods(); //获取的是该类自己声明的方法 注意:不看访问权限
		Method[] ms = c.getMethods(); //获取所有的public的函数,也包括父类继承而来的方法 返回的结果是Method对象的数组
		for(int i=0;i
输出结果(限于篇幅,仅贴出部分):
int    lastIndexOf(int)
int    lastIndexOf(int,int)
int    lastIndexOf(java.lang.String,int)
int    lastIndexOf(java.lang.String)
boolean    matches(java.lang.String)
int    offsetByCodePoints(int,int)
boolean    regionMatches(int,java.lang.String,int,int)
boolean    regionMatches(boolean,int,java.lang.String,int,int)
java.lang.String    replace(char,char)
java.lang.String    replaceAll(java.lang.String,java.lang.String)
java.lang.String    replaceFirst(java.lang.String,java.lang.String)
[Ljava.lang.String;    split(java.lang.String)
[Ljava.lang.String;    split(java.lang.String,int)
相信我注释写的很清楚了,这里我就不啰嗦了!

最后我们获取类的构造函数:

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

public class ClassUtils {
	public static void main(String[] args) {
		String s="我是String";
		ClassUtils cu = new ClassUtils();
		cu.getClassConstructor(s);
	}
	
	/**
	 * 得到类的构造函数信息
	 * @param obj:需要得到的信息类对象
	 */
	public void getClassConstructor(Object obj){
		Class c = obj.getClass();
		//eConstructor[] cs = c.getDeclaredConstructors(); //得到所有的构造函数
		Constructor[] cs = c.getConstructors(); //获取的是所有的public的构造函数
		for(Constructor constructor :cs){
			System.out.print(constructor.getName()+"(");
			//获取构造函数的参数列表,得到的是参数列表类型的类类型
			Class[] paramType = constructor.getParameterTypes();
			for(Class c1:paramType){
				System.out.print(c1.getName()+",");
			}
			System.out.println(")");
		}
	}
}
输出结果:
java.lang.String([B,)
java.lang.String([B,int,int,)
java.lang.String([B,java.nio.charset.Charset,)
java.lang.String([B,java.lang.String,)
java.lang.String([B,int,int,java.nio.charset.Charset,)
java.lang.String(java.lang.StringBuilder,)
java.lang.String(java.lang.StringBuffer,)
java.lang.String([I,int,int,)
java.lang.String([C,int,int,)
java.lang.String([C,)
java.lang.String(java.lang.String,)
java.lang.String()
java.lang.String([B,int,int,java.lang.String,)
java.lang.String([B,int,)
java.lang.String([B,int,int,int,)
       这里我同样不啰嗦,这些都是些基本方法,很简单,我在注释上也写的很清楚。但有一点我要建议下大家,在了解Class类的同时,也必须要了解Method类、Field类、Constructor类!因为懂了这4个类,基本上我们所需要的东西都能通过这4个类来取到!

下面再来了解下发射的基本操作:

public class MethodDemo{
	public static void main(String[] args) {
		Dw dw = new Dw();
		Class c = dw.getClass();
	    try {                
	            //方法的反射操作
	            //获取方法print(int,int)
		    	Method m = c.getMethod("print", new Class[]{int.class,int.class}); 
		    	Object obj = m.invoke(dw,new Object[]{10,20}); 
		    	System.out.println("----------------"); 
		    	//获取方法print(String,String) 
		    	Method m1 = c.getMethod("print",String.class,String.class); 
		    	obj = m1.invoke(dw, "java","反射"); 
		    	System.out.println("----------------");
		    	//获取方法print();
		    	Method m2 = c.getMethod("print", new Class[]{}); 
		    	m2.invoke(dw, new Object[]{});
	    	} catch (Exception e) {
	    		e.printStackTrace();
	    	} 
	    }
}
class Dw{
	public void print(){
		System.out.println("java反射");
	}
	public void print(String a,String b){
		System.out.println(a+","+b);
	}
	public void print(int a,int b){
		System.out.println(a+b);
	}
}
输出结果:
30
----------------
java,反射
----------------
java反射
        反射使用的方法是method.invoke(对象,参数列表),只有唯一的方法名和唯一的参数列表才能确定一个唯一的一个方法。查看API文档的时候,我们会发现invoke(Object obj, Object... args)他的参数列表使用的是...,这说明这是一个可变参数。那么在上面代码的第八行,我们还可以使用这样的写法:Method m = c.getMethod("print", int.class,int.class);因为那是可变参数,我们就可以有几个写几个。同样,在代码的第十二行,我也可以这样写:Object obj = m.invoke(dw, 10,20);结果也是一样的,需要注意的是:方法如果没有返回值就会返回null,有返回值就返回具体的返回值。在获取print方法的时候,由于此方法没有参数,我们可以写一个空的数组,如上面一样,还可以这样写:Method m2 = c.getMethod("print"); m2.invoke(dw);这种写法也是可以的,直接把空的数组省略掉。对于反射还可以只有简单理解,一般我们是直接dw.print(....);是直接通过dw去调用方法,这里我们可以理解为method.dw。就像是提升了一级一样。

  最后讲一下通过反射来了解泛型的本质。泛型通俗的讲就是用来固定输入的值为同一类型。输入不同类型的值,在编译的过程中就会报错。但是通过反射我们就可以绕过编译期,直接进入运行期。大家要记住:Class类,Field类,Method类,Constructor类都是在运行期执行的。下面通过一个例子讲下

ArrayList list = new ArrayList();
ArrayList list1 = new ArrayList();
这里我名定义两个ArrayList,分别是list和list1,我们先来添加数据进去,由于list是什么类型都可以装,我们就不赋值了,給list1添加数据
list1.add("你好");
list1.add(20);
我们会发现,后台会报错,因为list1 通过泛型固定了能接受值的类型,而这里输入的值为20是个整型,明显是加不进去的。但是通过泛型我们就可以加进去,首先来看下list和list1是不是相等的
Class c1 = list.getClass();
Class c2 = list1.getClass();
System.out.print(c1 == c2);
这里我们会发现输出的结果是true,说明结果是相等的。这时我们就可以通过反射绕过编译期,在运行期将值加进去。
try {  //由于c1和c2是相等的,这里我们用c1还是c2就无所谓了
	Method m = c2.getMethod("add", Object.class); //得到add方法,将值注入进去
	m.invoke(list1, 20);//绕过编译操作也就绕过了泛型
	System.out.println(list1.size());
	System.out.println(list1);
} catch (Exception e) {
	e.printStackTrace();
}
输出结果:
2
[你好, 20]

      从输出结果我们可以看出,list1的长度是2,说明值加进去了,结果也能输出。但是在前面list1还是加了泛型的,为什么能将整型值加进去呢?通过c1==c2结果返回true就说明了编译之后集合的泛型是去泛型化的,就是说泛型只在编译期有效,到了运行期,泛型就失效了!所以我们的整型值是能加进去的。但是还要注意一点,这时候你是不能通过循环输出list1里面的值的,会提示类型转换错误,这是需要注意的。


今天就讲这么多,看到这里,我想大家应该对反射也有了一定的了解!有不懂的可以发邮件来问我或者评论区直接贴出来。
















你可能感兴趣的:(Java进阶)