反射Reflect

来源:互联网 发布:兔先森质量 知乎 编辑:程序博客网 时间:2024/06/06 02:52

反射Reflect

反射的定义:反射就是把Java类中的各种成分映射成相应的java类。

例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。

一、Class.forName()

Class.forName() 的作用是用来返回字节码。

1)返回字节码的方式有两种:

1、这份字节码曾经被加载过,已经存在于 java 虚拟机中。

2、虚拟机中还没有字节码,需要使用类加载器加载,把加载的字节码放在 java 虚拟机中。

2)获取字节码对应的实例对象( Class类型)获取字节码的三种方式

1、类名.class,例如,System.class虚拟中已经存在该类的字节码,只有用类名.class 就可以得到

2、对象.getClass()例如,new Date().getClass()用这个方式去获取一个类的字节码

3、Class.forName("类名")例如,Class.forName("java.util.Date");主要用于反射。

3)一个奇怪的问题:加载了字节码,并调用了其getMethods之类的方法,但是没有看到类的静态代码块被执行,只有在第一个实例对象被创建时,这个静态代码才会被行。准确的说,静态代码块不是在类加载时被调用的,而是第一个实例对象被创建时才执行的。

4)isPrimitive()判断是否是基本数据类型的字节码。不是,返回 false是 ,返回 true

5)字节码的使用注意:

1、int 的字节码不等于 Integer 的字节码 ,返回 false

如:System.out.println(int.class == Integer.class);

2、int是 Integer 的包装类型中的,所以是同一份字节码,返回 true

如:System.out.println(int.class == Integer.TYPE);

3、数组类型的Class 实例对象Class.isArray();

如:System.out.println(int[].class.isArray());

总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void

二、Constructor类:构造器

Constructor代表某个类中的一个构造方法

1、得到某个类的所有构造方法有如下两种写法,他们的作用是一样的。

Constructor[] cons1 = String.class.getConstructors();Cosntructor[] cons2 = Class.forName("java.lang.String").getConstructors();

2、得到某一个构造方法:

Constructor cons3 = String.class.getConstructor(StringBuilder.class);Constructor cons4 = Class.forName("java.lang.String").getConstructor(StringBuffer.class);

3、创建实例对象

常用方式:cons3对应的初始化的 Constructor 对象。

String str1 = new String(new StringBuffer("abc"));

反射的方式:

String str2 = (String) cons4.newInstance(new StringBuffer("abc"));

4、Class.newInstance() 方法

String obj = (String) Class.forName("java.lang.String").newInstance();

三、Field类

Field 类代表某个类中的一个成员变量

问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。

得到字节码,再通过getField得到对应的变量名,如果成员变量被私有化,或者未定义,那么久会报错,java.lang.NoSuchFieldException: x

注意:

fieldy不是对象身上的变量,是类上的,要用它去取某个对象上对应的值。

想要访问私有成员变量,就得使用getDeclaredField 和 setAccessible。这就是暴力反射,不管是否私有化,都能通过该方法获取。

Code:

package study.part3.day018.reflection;import java.lang.reflect.Field;import java.lang.reflect.Method;public class ReflectDemo4Field {public static void main(String[] args) throws Exception {ReflectPoint rp = new ReflectPoint(3,5);//得到字节码,再通过getField得到对应的变量名,如果成员变量被私有化,或者未定义,//那么久会报错,java.lang.NoSuchFieldException: xField fieldy = rp.getClass().getField("y");//那 fieldy 的值是多少呢? 5?错//fieldy不是对象身上的变量,是类上的,要用它去取某个对象上对应的值System.out.println(fieldy.get(rp));//想要访问私有成员变量,就得使用getDeclaredField 和 setAccessibleField fieldx = rp.getClass().getDeclaredField("x");//暴力反射,不管是否私有化,都能通过该方法获取fieldx.setAccessible(true);System.out.println(fieldx.get(rp));changeStringValue(rp);System.out.println("rp-->"+rp);}/** *  * @param obj 传入所得的字节码参数 * @throws Exception */public static void changeStringValue(Object obj) throws Exception{Field[] fields = obj.getClass().getFields();for(Field field : fields){//if(field.getType().equals(String.class)){}//字节码用 == 比较,不要用 equals 比较System.out.println(field.getType() == String.class);if(field.getType() == String.class){String oldVal = (String) field.get(obj);//System.out.println("old:"+oldVal);String newVal = oldVal.replace('a', 'x');field.set(obj, newVal);}}}}class ReflectPoint{private int x;public int y;public String stra = "ball";public String strb = "basketball";public String strc = "itcast";public ReflectPoint(int x, int y) {super();this.x = x;this.y = y;}//覆盖重写,toString 方法@Overridepublic String toString() {return "ReflectPoint [x=" + x + ", y=" + y + ", stra=" + stra+ ", strb=" + strb + ", strc=" + strc + "]";}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + x;result = prime * result + y;return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;ReflectPoint other = (ReflectPoint) obj;if (x != other.x)return false;if (y != other.y)return false;return true;}}<span style="font-family: Arial, Helvetica, sans-serif;"> </span>

四、Method类

Method 类代表某个类中的一个成员方法

1、得到类中的某一个方法:

Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);

2、调用方法:

通常方式:System.out.println(str.charAt(1));

反射方式: System.out.println(charAt.invoke(str, 1)); 

3、jdk1.4和jdk1.5的invoke()方法的区别:

Jdk1.5:public Object invoke(Object obj,Object... args)

Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。

4、使用注意:

如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法。

Class.getMethod(name,Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。

5、Method Code Demo:

package study.part3.day018.reflection;import java.lang.reflect.Method;public class ReflectDemo5Method {public static void main(String[] args) throws Exception {Method getChatAtFunction = String.class.getMethod("charAt", int.class);System.out.println(getChatAtFunction);//getChatAtFunction}}class TestMain{public static void main(String[] args) {//处理高级 for 循环for(String arg : args){System.out.println("-->"+arg);}}}

五、ArrayReflect数组的反射

1、具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。

2、基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

3、Arrays.asList()方法处理int[]和String[]时的差异。

4、Array工具类用于完成对数组的反射操作。

5、CodeDemo:

package study.part3.day018.reflection;import java.lang.reflect.Array;public class ReflectDemo7Array {public static void main(String[] args) {int[] a1 = new int[3];int[] a2 = new int[4];int[][] a3 = new int[3][4];String[] s1 = new String[3];System.out.println(a1==a2);//相同数据类型的数组,当维数相同时,他们的字节码是相同的System.out.println(a1.getClass()==a2.getClass());//System.out.println(a1.getClass()==a3.getClass());System.out.println(a1.getClass().getName()+"=="+a2.getClass().getName());System.out.println(a1.getClass().getSuperclass().getName());System.out.println(a3.getClass().getSuperclass().getName());Object o1 = a1;Object o2 = a2;Object o3 = a3;Object o4 = s1;Object[] o5 = a3;Object[] o6 = s1;System.out.println(o3);System.out.println("-----------------------------");printObject(a1);//Object 数组的反射Object[] oo = new Object[]{"a",1,'a'};String claoo = oo[1].getClass().getName();System.out.println(claoo);}public static void printObject(Object obj){Class clazz = obj.getClass();if(clazz.isArray()){//获取array 的长度int len = Array.getLength(obj);//for 循环for(int i = 0;i<len;i++){System.out.println((i+1)+":"+Array.get(obj, i));}}else{System.out.println("OBJ:"+obj);}}}

六、CollectionReflect:集合反射

1、一句话总结:配置文件的加载往往是用类加载器的方式加载。以后学到的框架都是这么加的。

2、类加载器:用类加载器加载配置文件

-->类.class.getClassLoader().getResourceAsStream

说明:就是在classPath 下逐一的去查找你要加载的那个文件

3、扩展知识: eclipse 会把源目录(src)下的所有的 .java 文件,自动编译成 .class 你一保存它就编译了,然后存放到 classPath 指定的目录下去。同时,它也把所有的非 .java 文件,原封不动的再保存一份到 classPath 下,那么你就会发现,config.properties存在与两个不同的文件目录下。但是当你真正在获取配置文件的时候,它其实是在classPath下找的。

4、使用注意:创建一个文件读取流,读取配置文件中的字符流数据。一定要记住用完整路径,但是完整路径不是硬编码,而是运算出来的。

5、CodeDemo:

package study.part3.day018.reflection;import java.io.IOException;import java.io.InputStream;import java.util.Collection;import java.util.Properties;public class ReflectDemo8Collection {public static void main(String[] args) throws IOException, Exception, IllegalAccessException, ClassNotFoundException{//创建一个文件读取流,读取配置文件中的字符流数据//那么,读取properties 配置文件的路径怎么取呢?//一定要记住用完整路径,但是完整路径不是硬编码,而是运算出来的。//InputStream ips = new FileInputStream("config.properties");//在 classPath 根目录下逐一的找 config.properties 文件//那么,如果把配置文件放在包内,那么就要加上。。//InputStream ips = ReflectDemo8Collection.class.getClassLoader().getResourceAsStream("study/day018/part3/reflection/config.properties");System.out.println("-------");//直接获取配置文件资源,并未使用类加载器,这种方法:只需要写上配置文件名,不需要写上目录名//InputStream ips = ReflectDemo8Collection.class.getResourceAsStream("config.properties");//当把配置文件整合到一个 resources 资源包下,那么只需要在获取的路径上加上当前的包名(下级目录)InputStream ips = ReflectDemo8Collection.class.getResourceAsStream("resources/config.properties");//初始化一个配置文件对象properties 。Properties prop = new Properties();//加载文件读取流prop.load(ips);//马上关闭文件读取流,这是一个习惯。如果没有及时关闭,那么会造成内存泄漏//并不是因为 ips 对象不被释放,而是这个对象关联的系统资源没有被释放。这是两个概念//总结:释放资源ips.close();String className = prop.getProperty("className");System.out.println("className的值是:"+className);//newInstance():不带参数的构造方法Collection cols = (Collection) Class.forName(className).newInstance();//Collection cols = new HashSet();//得到的集合长度是3//Collection cols = new ArrayList();//得到的集合长度是4ReflectPoint pt1 = new ReflectPoint(3, 4);ReflectPoint pt2 = new ReflectPoint(2, 5);ReflectPoint pt3 = new ReflectPoint(3, 4);//给集合添加了两次的pt1。Cols.add(pt1);Cols.add(pt1);Cols.add(pt1);//如果重新给pt1的变量 y 赋值,那么当 remove 了pt1后,就会造成 hashCode 的内存泄露的现象//pt1.y = 7;//cols.remove(pt1);System.out.println(cols.size());//某个对象,我不要了,但是却一直在用,没有被释放掉。这就是内存泄漏。}}

 

0 0
原创粉丝点击