反射
一、反射的基石
反射的基石是Class类。Java程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class.
1.1 Class类的分析
在程序运行时调用类的时候,首先将这个类在硬盘上的二进制代码加载到内存中,才可以用这个类创建对象,也就是说先将类的字节码加载到内存,再用这个字节码创建对象。每个类的字节码就是一个Class类的实例对象。
1.2得到字节码对应的实例对象(Class类型)有三种方式:
1. 类名.class 例:PersonDemo.class
2. 对象.getClass() 例:new Person().getClass();
3. Class.forName(“完整类名”),
例:Class.forName(“java.util.Data”);
Class.forName方法的作用是返回字节码,返回方式有两种:
第一种,如果字节码已经被加载进内存,虚拟机缓存了,那么就可以直接用全类名的方式得到字节码Class.forName(String className)。
第二种,如果class文件没有被载加过,也就是说虚拟机里还没有该字节码,就要指定文件名、加载器,来得到字节码(用加载器加载,把那份字节码缓存起来,通过该方法返回字节码)Class.forName(String name, boolean initialize, ClassLoader loader)
1.3九个预定义的Class实例对象
任何类型都是Class的实例对象。
八个基本数据类型都是Class类的实例对象,void类也是Class类的实例对象,Class cls = void.class;
方法isPrimitive()用于判断指定的Class对象是否表示一个基本类型(原始类型),有八个基本类型和关键字void。基本类型和void的Class实例对象都可用对应的包装类型的常量值TYPE来获取。
例:Integer.TYPE表示所包装的基本类型int的Class实例对象。
Class类中有个方法isArray()用于判断Class实例是不是数组类型。
总之,只要是在源程序中出现的类型,都有各自的Class实例对象。
二、反射的概念
反射就是把java类中的各种成分映射成相应的java类,例如:一个java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。
表示java类的Class类显要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示。它们是Field、Method、Contructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。
三、 Constructor类
Constructor类代表某个类中的一个构造方法。
import java.lang.reflect.*;
public class ConstructorTest {
public static void main(String[] args)throws Exception {
// 返回类里面的所有构造方法
Constructor[] constructors = String.class.getConstructors();
//用参数类型来指定某一个构造方法,从而返回该构造方法对象
Constructor con = String.class.getConstructor(StringBuilder.class);
//用得到的有参构造方法进行new对象,用的什么参数类型获取的构造方法,
//在new对象的时候,就一定要用相应的实际参数类型传值。
String str = (String)con.newInstance(new StringBuilder("abc"));//该方法返回的是Object所以一定要强转
System.out.println(str.charAt(2));
//这个方法也是用于建对象,只是通过无参构造方法建的。
String.class.newInstance();
}
}
Class类里面的newInstance()方法就是在用无参的构造方法创建一个对象。
四、Field类
Field类代表某个类中的一个成员变量,也就是字节码里的一个变量,不代表一个对象上的变量。所以用get()方法获取变量值时要指定是哪个对象。
4.1成员变量的反射
当成员变量私有化时,通过Class对象调用getDeclaredField方法传入指定的成员变量字符串,返回Field对象,并通过Field对象调用setAccessible(true)方法设为可访问性,就可用get方法获取到私有的成员变量,也称暴力反射。
public class ReflectPoint {
public int x;
private int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
import java.lang.reflect.*;
public class ReflectDemo
{
public static void main(String[] rags)throws Exception{
ReflectPoint rp = new ReflectPoint(5,9);
Field fieldX = rp.getClass().getField("x");
System.out.println(fieldX.get(rp));
Field fieldY = rp.getClass().getDeclaredField("y");
fieldY.setAccessible(true);
System.out.println(fieldY.get(rp));
}
}
4.2获取一个类中所有String类型的成员变量,并替换指定字符
import java.lang.reflect.*;
public class ReflexDemo {
public static void main(String[] args) throws Exception {
Student s = new Student("zhangsan","beijing",23);
refField(s);
System.out.println(s.name+":"+s.add+":"+s.age);
}
static void refField(Object o) throws Exception{
Field[] field = o.getClass().getFields();
for(Field f : field){
if(f.getType() == String.class){
String oldValue = (String)f.get(o);
String newValue = oldValue.replaceAll("zhangsan", "lisi");
f.set(o, newValue);
}
}
}
}
class Student{
public String name,add;
public int age;
public Student(String name,String add,int age){
this.name = name;
this.add = add;
this.age = age;
}
}
五、Method类
Method类代表某个类中的一个成员方法。
5.1成员方法的反射
1、得到成员方法对象:Class对象.getMethod(“方法名”,参数类型.class);
方法有几个参数类型就要写几个对应的.class。
2、用反射方式调用方法:Method对象.invoke(对象,实际参数);
如果传递给Method对象的invoke()方法的第一个参数为null,该Method对象对应的是一个静态方法,因为静态方法不需要用对象调用,是跟着类走的。
3、JDK1.4和JDK1.5的invoke方法的区别:
JDK1.5:public Object invoke(Object obj,Object。。。args)支持可变参数
JDK1.4:public Object invoke(Object obj,Object[] args)需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,用JDK1.4写为charAt.invoke(“str”,new Object[]{1})
例:
import java.lang.reflect.*;
public class ReflectDemo{
public static void main(String[] rags)throws Exception{
String str = new String("abcde");
Method method = String.class.getMethod("charAt", int.class);//得到method对象
System.out.println(method.invoke(str, 2));
}
}
5.2用反射方式执行某个类中的main方法
需求:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
分析:启动java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,怎样给invoke方法传递参数呢?按JDK1.5的语法,整个数组是一个参数,而按1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会按1.4的语法处理(因为新版本要兼容老版本),即把数组打散成为若干个单独的参数,所以给main方法传递参数时,不能写mainMethod.invoke(null,new String[]{“abc”,”def”});
解决办法有两种:
1、mainMethod.invoke(null,new Object[]{new String[]{“xxx”}});这种方式相当于Object数组里存了一个String数组类型的元素,这样编译器就会认为是一个参数。
2、mainMethod.invoke(null.(Object)new String[]{“xxx”});这种方式编译时,相当于是一个Object对象,编译器就不会把参数当作数组看待,也就不会把数组里的元素打散成若干个参数。
具体代码如下:
import java.lang.reflect.*;
public class ReflectDemo{
public static void main(String[] args)throws Exception{
String startClassName = args[0];
Method mainMethod = Class.forName(startClassName).getMethod("main", String[].class);
mainMethod.invoke(null, (Object)new String[]{"123"});
}
}
class TestArguments{
public static void main(String[] args){
for(String str:args){
System.out.println(str);
}
}
}
注:要在运行ReflectTest的时候给主函数传值,打开Run Configurations窗口,并在Arguments选项卡的Program arguments栏里输入cn.itcast.day1.TestArguments 表示TestArguments字节码传给ReflectTestr 的main函数,那么在运行ReflectTest的时候就会同时加载得到TestArguments字节码文件,就相当于。ReflectTest主函数的args[0],那么就可通过Class.forName方法得到传递进来的字节码文件对象。
六、数组的反射
6.1数组与Object的关系及其反射类型
1、只要数组类型相同,维度相同,就是同一份字节码对象。
2、只要是引用数据类型(比如数组类型)的超类都是Object,所以可以通过getSuperclass方法获取到超类的Class对象。
3、如果数组是一维的,数组里的元素类型又是基本数据类型,那么就不能写成Object[] obj = a1;只能写成Object obj = a1;
例:
int[] a1 = new int[3];
int[] a2 = new int[5];
int[] [] a3 = new int[3];
Stirng[] a4 = new String[3];
System.out.println(a1.getClass()==a2.getClass());//结果为true
System.out.println(a1.getClass().getSuperclass().getName());//结果为Object
Object a1Obj = a1;//正确
Object[] a1Obj = a1;//错误
Object[] a3Obj = a3;//正确,一维数组是数组类型,属于Object子类,Object[]相当于有一个Object数组,里面存的是Object
Objcet[] a4Obj = a4;//正确,因为String是类类型,属于Object子类
4、Arrays.asList()方法处理int[]和String[]时的差异:
从反射的角度理解:虚拟机运行时会先根据JDK1.4的方法处理Arrays.asList(Object[] a),接收的是Object类型的数组,当传入的是String[],就代表String数组里的每个元素都是一个参数并存到集合里,所以可以全部打印出元素。这时int[]传进来它代表的一个Object而不是Object[],不符合1.4的参数类型就处理不了,就会按1.5的方法处理,Arrays.asList(T…a)是接收的Object对象,就把int[]当作一个整体处理了,就相当于是一个参数,所以打印的是地址值。
个人理解:Arrays.asList()返回的是一个list集合,而集合里只能存对象的引用,不能存基本数据类型int,所以打印list就是一个地址值,而String[]里存的是String类型的元素,每个元素都所属Object子类,打印list就可看到每个元素的值。
6.2、数组的反射应用
Array类是用于对数组反射的类。
例:用反射方式做:只要传进来的是数组就打印里面的元素,不是数组就直接打印对象。
public class ReflectArray{
public static void main(String[] args)throws Exception{
String[] str = new String[]{"ab","cde","w"};
Object obj = null;
printObject(str);
printObject("sss");
}
public static void printObject(Object obj){
if(obj.getClass().isArray()){
int len = Array.getLength(obj);
for(int x=0;x<len;x++){
System.out.println(Array.get(obj, x));
}
}
else{
System.out.println(obj);
}
}
}