Java高新技术2(反射)
1.Class类概述:
/* Java程序中的各个Java类属于同一类事物,描述这类事物的Java 类名就是Class 对比提问,众多的人用一个什么类表示?众多的Java类用一个什么类表示? 人->Person .class->Class Class类代表Java类,它的各个实例对象又分别对应什么呢? 1.对应各个类在内存中的字节码,例如:Person类的字节码,ArrayList类的字节码等 2.一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码 不同的类,字节码是不同的,这一个个的空间可以用一个个的对象来表示,这些对象显然具有相同的类型 这个类型就是Class */
2.获取字节码对象:
/* 如何得到各个字节码对应的实例对象(Class类型) 1.类名.class,例如:System.class 2.对象.getClass,例如new Date().getClass(); 返回:表示此 对象运行时 的Class对象 3.Class.forName("java.lang.String"): 运行时,可以接收字符串变量 作用: 返回字节码,如果JVM已加载该字节码,直接返回 如果没有,则用类加载器将硬盘中的字节码加载到虚拟机中 基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double) 和关键字 void 也表示为 Class 对象(Class cls=void.class)。 */package com.itheima.day1; public class ReflectTest { /** * @param args */ public static void main(String[] args)throws Exception { // TODO 自动生成的方法存根 /*验证通过三种方式获取到的字节码对象是否是同一个*/ Class cls1=String.class; Class cls2="abc".getClass(); Class cls3=Class.forName("java.lang.String");//此方法上声明有异常 System.out.println("①"+(cls1==cls2));//true System.out.println("②"+(cls2==cls3));//true /* 以上结果说明JVM中只会保存一份同样的字节码 以后再用到还是该字节码 */ // isPrimitive(): // 判定指定的 Class 对象是否表示一个基本类型。 // 有九种预定义的 Class 对象,表示八个基本类型和 void。 // 这些类对象由 Java 虚拟机创建,与其表示的基本类型同名 // System.out.println("③"+cls1.isPrimitive());//false System.out.println("④"+int.class.isPrimitive());//ture System.out.println("⑤"+Integer.TYPE.isPrimitive());//ture System.out.println("⑥"+Integer.class.isPrimitive());//false //public boolean isArray() //判定此 Class 对象是否表示一个数组类。 System.out.println("⑦"+int[].class.isArray());//true /* 总之,只要是在源程序中出现的类型,都有各自的Class实例对象:例如:int[],void.. */ } }
3.构造方法反射:
package com.itheima.day1; /* 反射就是把 Java类中的各种成分 映射成 相应的Java类. Java类中有什么?(类所属的包,类中的字段,类中的方法) Package getPackage() 获取此类的包。 Method getMethod(String name, Class<?>... parameterTypes) 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 Field getField(String name) 返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。 每个返回值都是一个对象,都映射成相应的类(相当于对java中所有类中的成分向上抽取描述),对上面那句话的理解 学习反射的目的: 一个类中的 每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些 实例对象后,如何用?怎么用?这才是学习和应用反射的关键 */ import java.lang.reflect.*; public class ReflectConstructor { /** * @param args */ public static void main(String[] args)throws Exception { // TODO 自动生成的方法存根 /* public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 parameterTypes 参数是 Class 对象的一个数组, 这些 Class 对象按声明顺序标识构造方法的形参类型。 */ //new String(new StringBuffer("abc")); Constructor strCons=String.class.getConstructor(StringBuffer.class);//对应String类中形参类型为StringBuffer的构造器对象 Constructor strBufCon=StringBuffer.class.getConstructor(String.class);//对应StringBuffered类中形参类型为String的构造器对象 /* public T newInstance(Object... initargs)//如果传入基本类型->自动装箱 throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException 使用此 Constructor 对象表示的构造方法来 创建该构造方法的声明类 的新实例, 并用指定的初始化参数初始化该实例。个别参数会自动解包,以匹配基本形参, 必要时,基本参数和引用参数都要进行方法调用转换。 */ String str=(String)strCons.newInstance(strBufCon.newInstance("abc"));//这里没有使用泛型,编译时期不能确定返回类型,返回Object //如果传入类型非StirngBuffer类型->IllegalArgumentException System.out.println(str+" "+str.length()); System.out.println(String.class.newInstance());//使用缓存机制来保存默认构造方法创建的实例对象//Class类中的newInstance相当于调用该字节码对象所表示的类的无参构造器. //该语句相当于new String(); } }
4.成员字段反射:
获取该类中的字段:
package com.itheima.day1; //测试反射用 public class ReflectPoint { private int x; int y; public int z; String str; public ReflectPoint(int x, int y, int z) { super(); this.x = x; this.y = y; this.z = z; } public ReflectPoint(String str) { super(); this.str = str; } @Override public String toString(){ return str; } }package com.itheima.day1; import java.lang.reflect.Field; public class ReflectField { /** * @param args */ public static void main(String[] args)throws Exception{ // TODO 自动生成的方法存根 ReflectPoint rp=new ReflectPoint(4,5,6); /* public Field getField(String name) throws NoSuchFieldException, SecurityException返回一个 Field 对象, 它反映此 Class 对象所表示的类或接口的指定公共成员字段.(default访问权限也不能,必须public) name 参数是一个 String,用于指定所需字段的简称。 返回: 由 name 指定的该类的 Field 对象 */ //获取公共字段的值 Field fieldZ=rp.getClass().getField("z");//ReflectPoint类中的字段z被封装成Field对象 //注意Field对象中z并没有值 //仅仅把ReflectPoint的字段z封装了,而字段的值需要对象去初始化的 //一个类可以有成千上万个对象,每个对象的中同一个字段的值都可能不同 //因此下面要获取z的值,指出ReflectPoint哪个对象 System.out.println("z="+fieldZ.get(rp));//6 //获取私有/默认字段的值 /* public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。 name 参数是一个 String,它指定所需字段的简称。注意,此方法不反映数组类的 length 字段。 */ Field fieldX=rp.getClass().getDeclaredField("x");//注意该方法只要是类中声明过的一定能拿到 //只要你有,管你暴露不暴露,藏起来我也要弄出来 /* 即使拿到了该fieldX对象,但是你获取不到字段值,依然是权限问题,这时需要用到一个类: AccessibleObject AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。 它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。 对于公共成员、默认(打包)访问成员、受保护成员和私有成员, 在分别使用 Field、Method 或 Constructor 对象来设置或获取字段、调用方法, 或者创建和初始化类的新实例的时候,会执行访问检查。 public void setAccessible(boolean flag) throws SecurityException 将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。 值为 false 则指示反射的对象应该实施 Java 语言访问检查。 */ fieldX.setAccessible(true);//暴力反射,抢~~ - -! System.out.println("x="+fieldX.get(rp));//4 Field fieldY=rp.getClass().getDeclaredField("y"); //fieldY.setAccessible(true);//经测试,default权限可以不用进行标志设置也可以获取到,因为default权限的字段类外也可访问 System.out.println("y="+fieldY.get(rp)+"\n");//5 getField(rp); } //尝试使用数组的方式获取 public static void getField(ReflectPoint rp)throws Exception{ //Field[] fieldArr=rp.getClass().getFields();//该数组只存入public字段 Field[] fieldArr=rp.getClass().getDeclaredFields(); for(Field f : fieldArr){ System.out.println(f); f.setAccessible(true); System.out.println(f.get(rp)); } } } /* 注意几个异常: 1.如果对private/default字段使用getField(String)-->Exception in thread "main" java.lang.NoSuchFieldException: x/y 2.如果未设置此Field对象的accessible(可存取)标志为true,而调用get -->Exception in thread "main" java.lang.IllegalAccessException: Class ReflectField can not access a member of class ReflectPoint with modifiers "private" */字段反射练习:
package com.itheima.day1; import java.lang.reflect.Field; //将任意一个对象中的所有String类型的成员变量所对应的字符串内容中含有"b"改成"a" public class ReflectFieldTest { public static void main(String[] args)throws Exception { // TODO Auto-generated method stub ReflectPoint rf=new ReflectPoint("bbca"); setStrValue(rf); System.out.println(rf);//aaca } public static void setStrValue(Object obj)throws Exception{ Field[] fieldArr=obj.getClass().getDeclaredFields(); for(Field f : fieldArr){ Class type=f.getType();if(type==String.class){//判断下类型是否为String类型f.setAccessible(true);//为了修改所有满足条件的字段 String value=(String)f.get(obj); if(value.contains("b")) f.set(obj, value.replaceAll("b","a"));//会将该字符串中所有是"b"的子串替换为"a" } } } }
5.成员方法的反射:
package com.itheima.day1; import java.lang.reflect.Method; public class ReflectMethod { public static void main(String[] args)throws Exception{ // TODO 自动生成的方法存根 //实现通过String对象调用charAt(1); /*public Method getMethod(String name,Class<?>... parameterTypes) throws NoSuchMethodException SecurityException返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 */ Method strMethod=String.class.getMethod("charAt", int.class); /* public Object invoke(Object obj,Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 对带有 指定参数 的 指定对象 调用由此 Method 对象表示的底层方法。 个别参数被自动解包,以便与基本形参相匹配,基本参数和引用参数都随需服从方法调用转换。 如果底层方法是静态的,那么可以忽略指定的 obj 参数,该参数可以为 null。 返回: 使用参数 args 在 obj 上指派该对象所表示方法的结果 */ System.out.println(strMethod.invoke("abc", 1));//该方法被哪个对象调用,以及传入相应实参 System.out.println(strMethod.invoke("abc", new Object[]{1}));//在JDK 1.5之前,传入一个数组对象 //之所以为Object,因为数组中元素类型是不确定的 //可以new Object[]{"abc",1} /* 这里非常别扭:invoke方法是通过Method对象调用 通俗例子: 列车司机把列车刹车:列车司机给列车发信号(刹车动作),哥们停车吧,真正让列车停车的是列车自己(内部一系列动作) 画圆方法定义在圆的内部(Circl.draw(圆心,半径)),关门(Door.close()) */ } }
练习:通过反射调用某个类的main方法:
package com.itheima.day1; import java.lang.reflect.Method; import java.util.Arrays; //需求:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法,由于不知道是哪个类,通过反射解决.//该例子的难点:给main方法传入的实参! //例如调用ReflectMethod中的main方法 public class ReflectMethodTest { public static void main(String[] args)throws Exception{ invokeMainMethod("com.itheima.day1.TestInvoke");//不要忘了包名(完整类名) } public static void invokeMainMethod(String className)throws Exception{ Class cls=Class.forName(className); Method method=cls.getMethod("main", String[].class); //第一种解决方案 method.invoke(null,new Object[]{new String[]{"123","456"}});//以打包/拆包理解 //main方法的形参类型为String[] //这里如果传入new String[]{"123"}等价于"123"->类型为String //这里需要传入一个数组对象:new String[]{"123"},需要再封装为数组 //new Object[]{new String[]{"123"}},Object[0]指向new String[]{"123"}-->相当于传入了一个实参->new String[]{"123"} //说明当传入new String[]{"123"},可变参数并不会再次封装成一个数组,而是把它当成String数组对待->兼容JDK 1.5以前版本//第二种解决方案 method.invoke(null,(Object)new String[]{"123","abc"});//(Object)强转动作告诉编译器->这是一个对象(字符串数组对象),别拆开 } } //调用该类的main方法 class TestInvoke{ public static void main(String[] args){ System.out.println(Arrays.toString(args)); } }
6.数组,Object,Object[]之间关系探讨:
package com.itheima.day1; import java.util.Arrays; //数组反射 /*每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。*/ public class ReflectArray { public static void main(String[] args)throws Exception { // TODO 自动生成的方法存根 int[] arr1=new int[3]; int[] arr2=new int[4]; int[][] arr3=new int[2][3]; String[] str=new String[2]; System.out.println(arr1.getClass());//class [I System.out.println(arr3.getClass());//class [[I System.out.println(str.getClass());//class [java.lang.String System.out.println(arr1==arr2);//false System.out.println((arr1.getClass()==arr2.getClass())+"\n");//同一个字节码对象:class [I //通过forName获取字节码对象 System.out.println(Class.forName("[[I")+"\n"); //查看数组字节码文件对象所表示的类的 超类的字节码对象 System.out.println(arr1.getClass().getSuperclass()); System.out.println(arr3.getClass().getSuperclass()); System.out.println(str.getClass().getSuperclass()+"\n");//均为class java.lang.Object //Object,Object[],数组之间的转化 convert(arr1,arr3,str); } public static void convert(int[] arr1,int[][] arr3,String[] str){ Object obj=null; Object[] objArr=null; //一维数组 obj=arr1;//obj和arr1均指向new int[3] System.out.println("arr1->"+arr1+" obj->"+obj);//但是你不能使用obj[0]取出元素->因为obj类型为Object(指向一个对象)//objArr=arr1;//int[]与Object[] 类型不匹配(arr1数组中元素为int而不是Object或其子类) //数组的数组 obj=arr3;//obj和arr3均指向new int[2][3]; System.out.println("obj->"+obj+" arr3->"+arr3); objArr=arr3;//Object[] objArr=new int[2][3];-->objArr数组中每个元素指向了一个一维数组对象 System.out.println("objArr->"+objArr+" arr3->"+arr3+ " arr3[0]->"+arr3[0]+" objArr[0]->"+objArr[0]+ " arr3[1]->"+arr3[1]+" objArr[1]->"+objArr[1]); //元素的类型为String obj=str; System.out.println("obj->"+obj+" str->"+str); objArr=str;//区分上面数组元素为基本类型的objArr=arr1,Object[] objArr=new String[2]; //objArr中的每个元素指向一个String对象 System.out.println("objArr->"+objArr+" str->"+str+ " str[0]->"+str[0]+" objArr[0]->"+objArr[0]+ " str[1]->"+str[1]+" objArr[0]->"+objArr[1]);//str[0]/objArr[0]获取到数组的String类型元素的值(默认值null) str[0]="abc"; System.out.println(str[0].length()+" "+((String)objArr[0]).charAt(1)+"\n");//Object[] objArr=new String[2],元素类型为Object //最后关于Arrays.asList方法: /* JDK 1.4: public static List asList(Object[] a)//不能接受收基本类型一维数组(int[]) JDK 1.5: public public static <T> List<T> asList(T... a) */ arr1[0]=1; arr1[1]=2; System.out.println(Arrays.asList(arr1)+" "+Arrays.asList(str));//第一个使用:asList(T... a)->传入了一个参数(一个 一维数组对象)->存入集合//第二个使用:asList(Object[] a)->传入new String[2],将a数组中每一个元素(字符串对象)存入集合 } }
7.数组反射:
package com.itheima.day1; import static java.lang.reflect.Array.getLength; import static java.lang.reflect.Array.get; import java.util.Arrays; //数组反射获取数组属性length,数组中元素值 /* public boolean isArray() :判定此 Class 对象是否表示一个数组类。 public static int getLength(Object array) throws IllegalArgumentException 以 int 形式返回指定数组对象的长度。 public static Object get(Object array, int index) throws IllegalArgumentException ArrayIndexOutOfBoundsException 返回指定数组对象中索引组件的值。 如果该值是一个基本类型值,则自动将其包装在一个对象中。 */ public class ReflectArray2 { public static void main(String[] args){ // TODO 自动生成的方法存根 Object obj=null; obj=new int[]{3,5}; //System.out.println(Arrays.toString(obj.getClass().getDeclaredMethods())); printObj(obj); obj="cdef"; printObj(obj); } //一.根据传入对象不同采取不同方式打印 //传入数组对象->打印数组中的元素,传入非数组对象->打印该对象 private static void printObj(Object obj) { // TODO 自动生成的方法存根 Class cls=obj.getClass(); if(cls.isArray()){//true->实参为一个数组对象 for(int i=0;i<getLength(obj);++i) System.out.println(get(obj,i)); } else System.out.println(obj); } } /* 思考:如何得到数组元素的类型? 回到是:这是不可能的 int[] arr=new int[3];//这样的数组还可能想办法获取数组元素类型 Object[] obj=new Object[]{"abc",1,1.3};//数组元素的类型是什么? 不同统一而论 obj[0].getClass().getName();//也就是说只能获取到某个元素的具体类型 */