高新技术_反射
一,反射的基石(Class类)
1.1Class概述
1,java程序中的各个java类属于同一类事物,java提供了一个类来用于描述这类事物,这个类就是Class。
2,Class类代表java类,它的各个实例对象又分别对应什么呢?
答:对应各个类在内存中的字节码
3,一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,
所以他们再内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象具有相同的类型,就是Class类型。
1.2获得Class对象
1,使用Class类的forName(String className)静态方法。传入的字符串参数值是某个类的权限定类名(必须添加完整包名)
(该方法可能抛出ClassNotFoundException异常)
2,调用某个类的class属性来获取该类对应的Class对象。
3,调用某个对象的getClass()方法,该方法是java.lang.Object类中的一个方法,所有的对象都可以调用这个方法。
会返回该对象所属类对应的Class对象。
String str1 = "abc"; //三种得到字节码的方式 Class cls1 = str1.getClass(); Class cls2 = String.class; Class cls3 = Class.forName("java.lang.String");//抛出异常。
以上三种得到的String字节码都是同一份。
System.out.println(cls1 == cls2);//true System.out.println(cls1 == cls3);//true
关于字节码的其他相关判断。
System.out.println(cls1.isPrimitive());// 判定指定的 <code>Class</code> 对象是否表示一个基本类型。、false、 System.out.println(int.class.isPrimitive());//true System.out.println(int.class == Integer.class);//false System.out.println(int.class == Integer.TYPE);//true System.out.println(int[].class.isPrimitive());//false System.out.println(int[].class.isArray());//判断是否是数组
三种方式的区别:第三种在源程序时不用知道要获得的字节码的类名称,而是临时传进来的。
对反射的一个总结:反射就是把java类的各种成分映射成相应的java类。
1.3Class的获取信息的常用方法
1.3.1获取Class对应类所包含的构造器
Constructor<T> getConstructor(Class<?>...parameterTypes):
Constructor<?>[] getConstructors():
Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes); 与访问权限无关
Constructor<?>[] getDeclaredConstructos();
1.3.2 获取Class对应类所包含的方法
Method getMethod(String name,Class<?>...parameterTypes);Method[] getMethods();Method getDeclaredeMethod(String name,Class<?>...parameterTypes); 与访问权限无关Method[] getDeclaredeMethods();
1.3.3访问Class对应类所包含的Field
Field getField(String name);Field[] getFields();Field getDeclaredeField(String name); 与访问权限无关Fidle getDeclaredeFields();
1.3.4获取方法和构造函数的注意要点
//前一个参数指定方法名,后面是个数可变的Class参数指定形参类型列表
String.class.getMethod("String",Stirng.class);
如果需要获取第三个indexOf方法,则使用如下代码:
//前一个参数指定方法名,后面是个数可变的Class参数指定形参类型列表 String.class.getMethod("String",Stirng.class,int.class);
而获取构造器时无须传入构造器名,因为同一个类的所有构造器的名字都是相同的,所以要确定一个构造器只要指定形参列表即可,
Class.forName("java.lang.String").getConstructor(StringBuffer.class);
二,创建对象(Constructor类)
2.1第一种创建方式
2.2第二种创建方式
(1) 获取该类的Class对象(2) 利用Class对象的getConstructor()方法来获取指定的构造器(3) 调用Constructor的newInstance()方法来创建java对象。
Constructor constructor1 = String.class.getConstructor(StringBuffer.class); String str2 = (String)constructor1.newInstance(new StringBuffer("abc")); //编译时不知道constructor是String的构造方法,返回的是Object所以上面要强转
通常没有必要使用反射来创建该对象,毕竟通过反射创建对象时性能要稍低一些,实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,
三,调用方法(Method类:(代表类在内存的字节码的一个成员方法))
1,得到类中的某一个方法
例子:Method methodCharAt = String.class.getMethod("charAt",int.class);
获取String类的charAt(int index);
2,方法的调用
例子:char s = methodCharAt.invoke(str1,1);
等价char s = str1.charAt(1)
注:如果传递给Method对象的invoke()方法的第一个参数为null,说明该method对象对应的是一个静态方法。
例:用反射方法执行某个类的main方法。
class TestArguments{ public static void main(String[] args){ for(String arg : args){ System.out.println(arg); } } } public class ReflectTest { //普通方式调用main方法, //TestArguments.main(new String[]{"111","222","333"}); //为什么要用反射的方式调用mian,不知道要调用那个类的main,等外界通过传递参数,告诉我要调用哪个类。 String startingClassNmae = args[0]; Method mainMethod = Class.forName(startingClassNmae).getMethod("main",String[].class ); //mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}}); mainMethod.invoke(null,(Object)new String[]{"111","222","333"}); }
四,访问属性值(Field类:(代表字节码的某个变量))
1,Field类代表某个类中的一个成员变量
public class ReflectPoint { private int x; public int y; public ReflectPoint(int x, int y) { super(); this.x = x; this.y = y; } }
ReflectPoint pt1 = new ReflectPoint(3,5); //getField()只能得到publlic修饰的变量 Field fieldY = pt1.getClass().getField("y");//对应字节码的变量。没有对应到对象身上。 System.out.println("fieldY"+fieldY); //field的值是public int cn.itcast.day1.ReflectPoint.y // fieldY的值是多少?是5 错!fieldY不是对象的变量,而是类上的变量。要用它取某个对象上的Y的值 System.out.println(fieldY.get(pt1)); //private修饰的变量可以使用getDeclareField();,public也可以 Field fieldX = pt1.getClass().getDeclaredField("x"); fieldX.setAccessible(true);//暴力反射,给你看到钱,就不让你用,那就只能抢。 System.out.println(fieldX.get(pt1));
练习(Field):将任意一个对象的所有String类型的成员变量所对应的字符串内容中的“b”改成“a”;
private static void changeStringValue(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'); //(old,new) field.set(obj, newValue); } } }
五,数组的反射。
1,具有相同维数和元素类型的数组属于同一个类型,即具有相同的class实例对象。
class ArrayReflect{ public static void main(String[] args) { 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"}; //具有相同维数和元素类型的数组属于同一个类型,即具有相同的class实例对象。 System.out.println(a1.getClass() == a2.getClass());//true System.out.println(a1.getClass() == a3.getClass());//false System.out.println(a1.getClass() == a4.getClass());//false System.out.println(a1.getClass().getName());//[I } }
2,代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
//代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
3,基本类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用,非基本类型的一维数组,
既可以当做Object类型使用,又可以当做Objcet[]类型使用。
Object aObj1 = a1; Object aObj2 = a4; //Object[] aObj3 = a1;//基本类型不是Object,int不属于Object。 Object[] 有一个数组,里面装的是Object Object[] aObj4 = a3; Object[] aObj5 = a4;
4,Array.asList()方法处理int[]和String[]时的差异。
//JDK1.4 asList(Object[] a) 当成一个Object[]处理
//JDK1.5 asList(T... a) 当成一个Object处理
System.out.println(a1);//[I@170bea5 System.out.println(a4);//[Ljava.lang.String;@f47396 System.out.println(Arrays.asList(a1));//[[I@170bea5] 为什么?jdk1.4和jdk1.5的接收参数的区别 System.out.println(Arrays.asList(a4));//[a, b, c]
5,对接收数字参数的成员方法进行反射
例:在一个类里调用另外一个类的main()方法
public class Reflect { public static void mian(String[] srgs) { //普方式调用 TestArguments.main(new String[]{"111","222","333"}); /*用反射的方式调用*/ //在运行该类时,传入一个参数作为被调用的类名,用一个变量接收被调用类名 String startingClassNmae = args[0]; //获取被调用类的main()方法 Method mainMethod = Class.forName(startingClassNmae).getMethod("main",String[].class ); //1,/下面传入的方式不对,JDK1.5兼容JDK1.4的处理方式 //因为String数组属于Object数组,按照jdk1.4的处理方式,所以相当于传了3个参数进去,所以会出现参数个数不对的错误。 //mainMethod.invoke(null,new Stirng[]{"111","222","333"}); //2,以下就是创建一个Object数组,将要传的参数作为Object数组的第一个元素,这样就想相当于传进去一个参数 //mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}}); mainMethod.invoke(null,(Object)new String[]{"111","222","333"}); } } class TestArguments{ public static void main(String[] args){ for(String arg : args){ System.out.println(arg); } } }
6,Array工具类用于完成对数组的反射操作。(反射)
实例:打印一个Object对象的内容
private static void printObject(Object obj) { Class clazz = obj.getClass(); if(clazz.isArray()){ int len = Array.getLength(obj); for(int i=0;i<len;i++){ System.out.println(Array.get(obj, i)); } }else{ System.out.println(obj); } }
六,反射的作用(实现框架的功能)
1,对框架的认识
如房地产商造房子用户住,门窗和空调等等内部都是由用户自己安装,房地产商造毛呸房就是框架,用户需使用此框架,安好门窗等放入到房地产商提供的毛呸房(框架)。
框架和工具类的区别:工具类被用户类调用,而框架是调用用户提供的类。
2,框架要解决的核心问题
因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象,而要用反射方式来做
3,实现一个简单框架步骤:
(1)创建一个配置文件config.properties,然后写入配置信息。如键值对:className=java.util.ArrayList。
(2)代码实现,加载此文件:
1将文件读取到读取流中,要写出配置文件的绝对路径。
2用Properties类的load()方法将流中的数据存入集合。
3关闭流:关闭的是读取流,因为流中的数据已经加载进内存。
(3)通过getProperty()方法获取className,即配置的值,也就是某个类名。
(4)用反射的方式,创建对象newInstance()。
(5)执行程序主体功能
实例:
public class ReflectTest2 { public static void main(String[] args) throws Exception { InputStream ips = new FileInputStream("config.properties"); Properties props = new Properties(); props.load(ips); ips.close(); String className = props.getProperty("className"); //通过上面获取的className类名,创建一个ArrayList对象 Collection collections = (Collection)Class.forName(className).newInstance(); ReflectPoint pt1 = new ReflectPoint(3,3); ReflectPoint pt2 = new ReflectPoint(5,5); ReflectPoint pt3 = new ReflectPoint(3,3); collections.add(pt1); collections.add(pt2); collections.add(pt3); collections.add(pt1); System.out.println(collections.size());//4 } }
4,类加载器加载资源文件
4.1,类加载器:
当一个类被使用的时候,会被类加载器加载到内存中,当然,它也可以加载普通文件。
4.2,eclipse编译,加载功能
在eclipse中保存一个.java文件后,eclipse会自动将该文件编译成.class文件,并把它放到classpath指定的目录中,
当然,eclipse也会把源程序目录下的一个非.java文件编译成.class文件,也把它放到classpath指定的目录中。
所以当classPath指定的目录下需要某个文件,那可以将该文件放在源程序目录下,eclipse会帮你复制过去。
4.3,使用类加载器加载配置文件
4.3.1使用类加载器来加载配置文件,需要先通过getClassLoader()获得类加载器,然后使用getResourceAsStream(),获得与配置文件相关的输入流。
利用类加载器来加载配置文件,需把配置文件放置的包名一起写上。这种方式只有读取功能!
InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");
4.3.2使用类提供的简便方法加载的时候,配置文件路径可以相对也可以是绝对。
InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/config.properties");
实例:
public class ReflectTest2 { public static void main(String[] args) throws Exception { //InputStream ips = new FileInputStream("config.properties"); //类加载器,没有OutputStream //一定要记住,用完整的路径,但完整的路径不是硬编码,而是运算出来的 //在classpath根目录下面去逐一找这个文件,cn/itcast/day1 在cn前面不要加斜杠 //InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties"); //InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties"); InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/config.properties"); Properties props = new Properties(); props.load(ips); ips.close(); String className = props.getProperty("className"); Collection collections = (Collection)Class.forName(className).newInstance(); //Collection collections = new HashSet(); ReflectPoint pt1 = new ReflectPoint(3,3); ReflectPoint pt2 = new ReflectPoint(5,5); ReflectPoint pt3 = new ReflectPoint(3,3); collections.add(pt1); collections.add(pt2); collections.add(pt3); collections.add(pt1); System.out.println(collections.size()); } }