java 反射
要想知道反射,就需要先了解一下类Class,这是一个java类,而不是关键字。
个人观点,类通俗的说就是描述同一类事物的抽象。比如说人这个类就表述了人。汽车这个类描述了所有汽车。
那么在java程序中java类也是同一类事物,那么用什么类描述java类,这里使用Class这个类。
Person p1 = new Person();
Person p2 = new Person();
Class cls1 = Date.class//字节码1;
Class cls2 = Person.class//字节码2;
当我们在源程序中用到Person这个类的时候,首先要把这个类的二进制代码加载到内存中,加载到内存中的这些二进制代码就叫做字节码,然后才可以用这个字节码复制出一个一个的对象。
p1 就是那个字节码文件创建的对象,那么这个对象就可以使用方法getClass()得到创建自己的字节码。
还可以用Class.forName("java.lang.String");得到类的字节码。
得到类的字节码有两种情况,第一种字节码文件已经加载到内存中了,那么这里就不用加载字节码文件了,直接找到返回就可以了。另外一种要得到一个字节码,那么就需要使用类加载器去加载,在内存中缓存起来,然后再用类加载器返回字节码文件。
返回的方式有两种,一种是这份字节码已经被加载过,直接返回。另一种JVM中没有字节码文件,那么就使用
类加载器加载字节码,然后放在缓存中缓存起来,以后就不用在加载了。
得到字节码的方式有三种:
第一:在源程序中直接写上类的名字:Date.class
第二:使用一个对象调用getClass()方法,因为一个对象是有一个类创建的,那么就可以用这个对象得到创
建自己的类。Date d1 = new Date(); Class clazz = d1.getClass();
第三:使用Class的静态方法Class.forName("java.util.Date");这样就得到了Date这个类的字节码文件。
在用反射的时候经常用到第三种,因为程序中用到的类还不知道,所以里面的类名可以是一个变量,用到什么就传递什么。
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
Class clazz = void.getClass(); 这个也是一个字节码。
数组类型不是一个基本类型,它是数组类型,判断一个对象是否是数组的实例对象使用isArray()方法。
Class.isArray();
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void...
反射就是把Java类中的各种成分映射成相应的java类。
比如说: 一个类有包,那么就会有一个getPackage()的方法返回一个Package的对象。还有各种方法,返
回的方法getMethod()是Method的对象。 一个类里面有很多的成分,把每一个成分解析成一个相应的类,这个就叫反射。
类里面的所有成分都是相应的类的对象来表示。
例如:
System里面有很多方法
System.exit();
System.getProperties();
这些方法对应的Method类里面的不同的对象,methodObj1,methodObj1。这两个对象都是同一个类型。得到一个Method的对象就得到了一个类里面的方法。
Field类
类身上的字段,也就是成员变量,也是用一个类表示的,Field。
要获得一个类身上的成员变量可以使用如下方法:
首先写一个专门用来反射的类ReflectPoint 。
public class ReflectPoint {
private int x ;
public int y ;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
public String str1 = "ball" ;
public String str2 = "basketball" ;
public String str3 = "itcast" ;
@Override
public String toString(){
return str1+" : "+str2 +" : "+str3;
}
}
ReflectPoint pt1 = new ReflectPoint(3, 5) ;
Field fieldY = pt1.getClass().getField("y") ;//要得到某一个字段,那么就要先得到这个类的字节码文件,然后再用方法得到那个字节码文件成员变量。
Constructor构造方法类
这个是类的对象代表一个字节码里面的一个构造方法。
要想得到一个类身上的所有的构造方法,使用Constructor [] constructor = Class.forName("java.lang.String).getConstructors();
得到单独一个构造方法Constructor constructor = Class.forName("java.lang.String).getConstructor();
要想得到一个明确的构造方法,使用Class.getConstructor(Class<?>...) 这里的的参数可以多个,也可以是一个,这正是因为JDK1.5的新特性,可变参数带来的方便。这里通过参数的类型可以明确的得到一个你需要的构造方法。
下面看一些具体的例子:
//new String(new StringBuffer("abc"));
Constructor constructor1 =String.class.getConstructor(StringBuffer.class) ;//这里也可以写很多的参数,这是因为JDK1.5的可变参数的新特新
//上面的StringBuffer的作用是用得到参数是StringBuffer的构造方法
String str2 = (String)constructor1.newInstance(new StringBuffer("abc")) ;//这里的StringBuffer表示用这个构造方法的时候还需要穿一个StringBuffer的对象进去。
//上面的代码在编译的时候这个构造放还不知道是谁的构造方法,所以需要明确转换为String类型的构造方法。
//使用constructor1.newInstance()方法得到一个构造方法,然后用这个构造方法去创建一个实例对象。
System.out.println(str2.charAt(2));
//如果把程序改成下面的这样,看看会出什么错误
//String str3 = (String)constructor1.newInstance("abc") ;//在这里我们的constructor1这个构造方法是参数为StringBuffer的构造方法,但是这里传入的参数是String类型,所以会报错参数类型不匹配。
//argument type mismatch
//在这里主义的问题有两个,得到某个构造方法的时候需要参数的类型,调用获得的方法时要用到上面相同的实例对象。
class-->constructor-->new object,这是我们创建一个对象的流程。
也可以直接用Class.newInstance();这个方法是用不带参数的构造方法穿件一个对象。如果要用很多次这个构造方法,那么就把这个可以使用这个方法,因为这个方法会缓存在内存中。但是如果只调用一次就很麻烦,而且消耗大。所以是用很多次的时候才用这个。
反射会导致程序的性能下降。
//fieldy不是对象身上的变量值,只代表这个字节码文件里面的成员变量,不代表某一个具体对象的值。要用它去取某个对象上对应的y值。这里的fieldy只是一个类里面的成员变量,但是还不是一个对象上面的具体的类,要想获得某个具体对象的值,需要fieldY.get(pt1);调用此方法,确切的指明是那个对象的值。
获得私有成员变量的值
Field fieldX = pt1.getClass().getDeclaredField("x") ;//加入这里的x是一个私有变量,因为x这个变量
是一个私有的,外部不可见,所有要用这个方法可以获得到你申明的所有的成员变量,这里虽然获得这个x,但是不能访问这个私有成员变量。
fieldX.setAccessible(true);//这里是设置私有成员变量可以被访问,不管你的成员变量是私有的还是共有的,都可以被访问。
System.out.println(fieldX.get(pt1));
下面再看一个难一点例子
现在有很多的String类型的成员变量,把里面所有的成员变量的a改变成b;private static void
changedStringValue(Object obj) throws Exception{
Field [] fields = obj.getClass().getFields() ;
for (Field field : fields) {
if((field.getType()) == String.class){//这里的比较一定要用==,因为这里的字节码文件只有一份。
String oldValue = (String)field.get(obj) ;
String newValue = oldValue.replace('b', 'a') ;
//System.out.println(newValue);虽然这里已经换了,但是那个对象身上的值还没有改变,所以还需要把现在新值赋值进去。 field.set(obj, newValue);
}
}
}
通过上面的方法之后,就把ReflectPoint 这个类里面原来string类型的变量的值 给改变了。
通过一个对象,改变这个类里面的其他的成员的值或者动作。
Method方法类
通过一个对象或者类得到一个类的字节码文件,通过一个Method的实例获得一个类里面的方法,这个这个方法不是某一个对象的上的方法,方法是属于类的,要想调用这个方法,必须要通过某个对象调用方法。
如何得到一个类String身上的charAt()方法。
Method methodCharAt = String.class.getMethod("charAt", int.class) ;//使用反射的方法得到String这个类的字节码文件的里面的方法,这里获得方法是charAt
System.out.println(methodCharAt.invoke(str1, 1)) ;//然后把得到方法作用到某一个具体的对象。 调用str1上的charAt方法。invoke这个方法是Method这个类里面的对象。
//首先获的Method的对象获得String这个类里面的方法,然后让这个方法作用到某个对象上,怎么用呢,这里让invoke这个方法通知str1对象来调用这个charAt方法。
//methodCharAt.invoke(null, 1)//如果代码写成这样,那么这个是调用类里面的什么方法呢,首先来看,这里的要调用的方法没有接受一个参数,说明这个方法不需要对象就可以调用,那么这个方法就是一个静态方法
//因为静态方法可以直接用类来调用。
System.out.println(methodCharAt.invoke(str1, new Object[]{2})); //这里是什么意思呢,这里使用的是JDK1.4的语法,因为在JDK1.4里面没有可变承参数
//这里的new Object[]{2} 代表的是这里的数组里面放的内容是2.
//new int[]{2} ,相信大家这个都可以看懂,因为这里代表这个int数组里面的内容是2,那么int也属于Object,所以这里直接换成Object,那么有人就会问了,这里是一个Object,并不是一个整型数据,这里的JDK1.5有自动装箱功能,他会把这个2自动封装成integer。然后这个数组里面的内容就是2.所以相当于这里传入了一个整数。
自己写程序调用人家的main方法
为什么要用反射的方式调用其他类里面的主方法,因为你在运行程序的过程中是不知道到底你在运行哪一个类,那么你就可以通过反射的方式来调用某一个类的main方法,把你要调用的那个类当做一个参数传递进去。看如下例子:
String startingName = args[0] ;//这里通过获得完整的一个类名
Method mainMethod = Class.forName(startingName).getMethod("main", String[].class) ; //在这里通过Method获得类里面的主方法,这个主方法不是属于某一个类。
//mainMethod.invoke(null, new String[]{"abc","xyz","123"}) ;//在原来的主方法中接受的是一个参数,是一个字符串数组类型,现在的这个数组相当于是一个Object数组吗,
//main方法接受到这个数组是,他会把这个数组打开,结果就是三个参数了,而不是一个数组参数,就会认为是收到三个参数,所以这里需要改变参数。
mainMethod.invoke(null, new Object[]{ new String[]{"abc","xyz","123"}}) ;//这里把这个String数组封装成一个object数组,在main方法的参数接收到这个Object数组是打开数组,然后里面是一个数组,
//这就满足了参数的要求。
mainMethod.invoke(null, (Object)new String[]{"qwe","wew","123"}) ;//在这里的转换时直接把这个数组转换成了Object,这个是为什么呢,因为数组是一个Object,
//这里的意思是传过来的是一个Object对象,而不是一个数组。是一个数,这个数是由一个String数组组成的。
数组的反射
数组是具有相同的元素类型,也有相同的维度的数据。
int [] a1 = new int[]{1,2,3} ; //当给数组具体赋值了,就不能在制定个数了
int [] a2 = new int [4] ;
int [][] a3 = new int [2][3] ;
String [] a4 = new String []{"a","b","c"} ;//有一个数组,数组里面装的是int数组。
System.out.println(a1.getClass() == a2.getClass());//同样数据类型和维度数组是同一份字节码文件。
//System.out.println(a1.getClass() == a4.getClass());在这里我的eclipse是4.3的版本他在这里就检查不能通过了,所以不能编译。因为这两个字节码文件
//System.out.println(a1.getClass() == a3.getClass());
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a4.getClass().getSuperclass().getName());
Object aObj1 = a1 ; //int数组是一个Object对象。但是不是一个Object的数组的。
Object aObj2 = a4 ; //
//Object [] aObj3 = a1 ;//这里会报错,为什么,因为基本类型的数组不是Object类型数组,所以不能转换。
Object [] aObj4 = a3 ; //这里的a3是一个二维的int数组,int数组是一个object对象,这个a3里面装了一个int类型的数组,所以是对的。
Object [] aObj5 = a4 ;//这里的a4是一个String数组,String数组是Object类型的数组。
System.out.println(a1);//打印结果[I@156b6b9,这表示是一个int类型的数组,数组的hashcode值是
156b6b9
System.out.println(a4);//[Ljava.lang.String;@1f66cff表示是一个String类型数组,数组的hashcode值是1f66cff
//适应Arrays这个类里面的方法,把一个数组转换成一个list
System.out.println(Arrays.asList(a1));//整数的不能被转换,虽然也转换成了一个数组,但是这个数组里面的内容只有一个值,[I@1f66cff;
//为什么会这样呢,因为在这里的参数接受的是一个Object的类型的数组,但是这你你传进来的是一个int类型的数组,这时Object不管这个参数。如果传递一个string
// 类型的数组,就可以被转换成数组,因为String类型的数组是一个Object类型的数组。
System.out.println(Arrays.asList(a4)); //对于字符串的数组可以直接转换陈数组打印出来对数组进行反射
private static void printObject(Object obj) {
// TODO Auto-generated method stub
Class clazz = obj.getClass() ;//首先得到这个对象的字节码文件
if(clazz.isArray()){//判断这个字节码文件是否是一个数组,如果是一个数组那么就打印出这个数组里面的内容
int len = Array.getLength(obj) ;//使用Array.getLength()方法得到数组的长度
for(int i = 0 ;i < len ; i++){
System.out.println(Array.get(obj, i));//Array.get(obj,i);这个方法是得到指定类型的指定位置的数组的元素的值。
}
}else{
System.err.println(obj);
}
}