黑马程序员——自学总结(六)反射
2015-07-28 00:04
603 查看
<a href="http://www.itheima.com"
target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流!
一、反射的基石Class类
类用于描述一类事物的共性,描述该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。而Class类就是用来描述类本身,它抽出所有类的共性,比如某个Class类的对象对应某个类,和其他对应某个类的Class对象一样,它们有成员,有方法,这些就是它们的共性,可以调用方法获得成员信息和方法信息,Class类不能实例化,分别代表各种字节码。
获取Class对象的方法有三种:1、Class cls1=Data.class;2、Person p1=new Person(); Class cls2=p1.getClass();3、Class cls3=
Class.forname("java.lang.String")。如果内存里,JVM里已存在该Class对象,则直接指向,如果不存在,则由类加载器加载,返回新加载的字节码。
有九个预定义的Class对象,分别是八个基本数据类型对应的Class对象和void对应的Class对象。
基本数据类型对应的包装器类内部封装了基本数据类型的Class对象,比如Integer.TYPE就等于int.class。
isPrimitive()用来判断是否是基本类型对应的Class对象,int[].class.isPrimitive()返回false,int[].isArray()返回true,总之,只要是在源程序中出现的类型,都有各自的Class对象,例如:int[],void。
二、反射
反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等也是一个个的类,表示Java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field,Method,Contructor,Package等。
一个类中的每个成员都可以用相应的反射类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。
1、 构造方法的反射应用
Constructor类代表某个类中的一个构造方法,得到某个类所有的构造方法Constructor[] constructors=Class.forName(类名字符串).getConstructors(),得到某一个构造方法,例如getConstructor(StringBuffer.class)该构造方法参数为StringBuffer,得到Constructor对象后,可以用newInstance(对应的参数)方法创建对象,该方法编译期返回Object(JDK1.5版本以后可用泛型代替)。
Class对象也有newInstance()方法,调用无参构造函数,提供了便利,查看该方法源代码,发现其找到无参构造方法,找到后存储备用,每次调用重复取这个方法,比较高效,而反射每次都要重新查找,导致程序性能下降。
2、Field类
Field类代表某个类中的一个成员变量 ,Field field=对象.getClass().getField(字段名) ,field.get(对象)取得对象该成员变量值,field不是对象身上的变量,而是类上,要用它去取某个对象上对应的值。私有成员无法用getField(字段名)获得,但可用getDeclardField(字段名) 获得,但此时再调用方法field.get(对象)则编译不通过。
可以用field.setAccessible(true),使得私有成员变得可以访问,再调用field.get(对象)获得对象私有成员的值,这种方式称为暴力反射。
成员变量修改示例:将某对象上所有String成员变量值改变,将字符串中的'b'全部改成'a'。
代码如下:
程序运行结果:
程序应该在内部处理异常,为了编写方便,直接在主函数中抛出。Class对象的getFileds()方法返回Field对象数组,field对象.getType()==String.class;指向同一个地址,用==判断,不用equals()判断,因为类字节码在内存中只有一份。field对象.set(obj,newValue)可对对象的成员值进行设置。
3、Method类
Method类用于成员方法的反射,Method对象代表某个类中的一个成员方法 。对象.getClass().getMethod(方法名,参数类型.class)获取指定方法对应的Method类对象。必须要指明参数类型,以确定是哪一个重载形式。Method对象.invoke(调用方法的对象,参数)可以在某个对象上调用反射来的该方法,如果调用静态方法,第一个参数用null,意味着method对应着一个静态方法,该方法调用不需要对象,第二个参数可以封装成数组,这是1.4版本的调用方式,1.5版本改成可变参数列表形式。
4、接受数组参数的方法的反射
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
main接受的参数中含有要执行的类名,调用该类的main方法: Class.forName(" 类名").getMethod("main",String[ ].class).invoke(null,"");
代码如下:
运行结果如下:
分析Class.forName(args[0]).getMethod("main",String[].class).invoke(null,new Object[]{new String[]{"aa","bb","cc"}});
在调用invoke()方法时,需要传入方法所需的参数。main()方法所需参数类型为String[],即字符串数组。如果写成这样的形式invoke(null,"aa","bb","cc")JDK1.5按照所定义的形式Object invoke(Object obj, Object... args) 将参数解释为可变参数类型,将传给test.main()方法三个字符串参数,与所需的字符串数组类型不匹配,如果转向低版本JDK1.4去解释,JDK1.4是将invoke()传递的参数打包成Object[]类型,即对象数组类型,也不匹配。
如果写成这样的形式invoke(null,new String[ ]{"aa","bb","cc"})JDK1.5同样认为是打包好的可变参数形式,不匹配,转向JDK1.4解释,而JDK需要Object数组类型,String[]是字符串数组类型,两者不匹配。
只能写成这样的形式invoke(null,new Object[]{new String[ ]{"aa","bb","cc"}}),首先JDK1.5解包,发现数组元素只有一个String[],匹配。
或者写成这样的形式invoke(null,(Object)new String[ ]{"aa","bb","cc"}),JDK1.5将解释成一个Object变量,传递给所需的String[ ],类型转型成功,匹配。
5、数组的反射
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象,代表数组的Class实例对象的getSuperclass()方法返回的是父类Object类对应的Class,int[ ] i=new int[5];i.getName()方法返回[I ,表示类型是int类型的数组。
直接打印数组对象,只能打印数组对象的类型和哈希值,Arrays.asList(Object[])可以将数组转化成容器打印,可以打印数组中每个元素的内容,JDK1.5版本中将参数变成了可变参数,<T> List<T> asList(T... a)。
Array工具类用于完成对数组的反射操作。不确定某个对象是具体对象,还是数组对象的方式如下:
printObject(Object obj){
Class class=obj.getClass();
if(class.isArray(){
int len=Array.getLength(obj);
for(int i=0;i<len;i++)
Array.get(obj,i);
else
直接打印obj;
}
目前还找不到比较好的方法得到数组中元素的类型。就是说我们拿Object接收一个数组对象,可以求得某个元素的具体类型,但无法取得该数组的类型。
三、HashSet中元素的比较
HashSet存储的元素不能重复,判断两个元素是否是同一个元素是比较两者的HashCode,每个对象都有自己的HashCode。在程序中可以覆盖对象的int hashCode()方法。
上面的代码中,将Person类的hasCode()方法覆盖,用age值作为对象的HashCode值,在往hs中添加对象时,因为每个Person对象的age都不一样,HashCode也不一样,所以都能添加进来,hs.size()=4。当p4修改了字段age值,它的HashCode值也随之改变,当调用hs.remove(p4)时,将比较hs中每个元素的HashCode值,结果没有HashCode值是25的元素,删除失败,p4仍然在容器中。如果程序中反复修改对象字段值,并添加到容器中,容器比较HashCode值后都会接收,造成内存泄露。
四、反射的作用—实现框架功能。
框架要解决的核心问题:举例说,我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具有区别,工具类被用户的类调用,而框架则是调用用户提供的类。我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢,我写的框架程序怎样能调用到你以后写的类(门窗)呢?因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射方式来做。以后客户端的编写的类全部存储在配置文件里,服务器端读取文件中数据,通过反射来完成功能,这样即使客户端类还没有编写出来,或者以后有什么改动,都不会修改服务端的代码。
target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流!
一、反射的基石Class类
类用于描述一类事物的共性,描述该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。而Class类就是用来描述类本身,它抽出所有类的共性,比如某个Class类的对象对应某个类,和其他对应某个类的Class对象一样,它们有成员,有方法,这些就是它们的共性,可以调用方法获得成员信息和方法信息,Class类不能实例化,分别代表各种字节码。
获取Class对象的方法有三种:1、Class cls1=Data.class;2、Person p1=new Person(); Class cls2=p1.getClass();3、Class cls3=
Class.forname("java.lang.String")。如果内存里,JVM里已存在该Class对象,则直接指向,如果不存在,则由类加载器加载,返回新加载的字节码。
有九个预定义的Class对象,分别是八个基本数据类型对应的Class对象和void对应的Class对象。
基本数据类型对应的包装器类内部封装了基本数据类型的Class对象,比如Integer.TYPE就等于int.class。
isPrimitive()用来判断是否是基本类型对应的Class对象,int[].class.isPrimitive()返回false,int[].isArray()返回true,总之,只要是在源程序中出现的类型,都有各自的Class对象,例如:int[],void。
二、反射
反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等也是一个个的类,表示Java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field,Method,Contructor,Package等。
一个类中的每个成员都可以用相应的反射类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象。
1、 构造方法的反射应用
Constructor类代表某个类中的一个构造方法,得到某个类所有的构造方法Constructor[] constructors=Class.forName(类名字符串).getConstructors(),得到某一个构造方法,例如getConstructor(StringBuffer.class)该构造方法参数为StringBuffer,得到Constructor对象后,可以用newInstance(对应的参数)方法创建对象,该方法编译期返回Object(JDK1.5版本以后可用泛型代替)。
Class对象也有newInstance()方法,调用无参构造函数,提供了便利,查看该方法源代码,发现其找到无参构造方法,找到后存储备用,每次调用重复取这个方法,比较高效,而反射每次都要重新查找,导致程序性能下降。
2、Field类
Field类代表某个类中的一个成员变量 ,Field field=对象.getClass().getField(字段名) ,field.get(对象)取得对象该成员变量值,field不是对象身上的变量,而是类上,要用它去取某个对象上对应的值。私有成员无法用getField(字段名)获得,但可用getDeclardField(字段名) 获得,但此时再调用方法field.get(对象)则编译不通过。
可以用field.setAccessible(true),使得私有成员变得可以访问,再调用field.get(对象)获得对象私有成员的值,这种方式称为暴力反射。
成员变量修改示例:将某对象上所有String成员变量值改变,将字符串中的'b'全部改成'a'。
代码如下:
class Person{ private String name; private String address; public Person(String name,String address){ this.name=name; this.address=address; } public String toString(){ return "name:"+name+"...address:"+address; } }
import java.lang.reflect.*; class myreflect{ public static void main(String[] args) throws Exception{ Person p1=new Person("bobo","beijing"),p2=new Person("xiaobai","bengbu"); System.out.println(p1); System.out.println(p2); Field[] fields1=p1.getClass().getDeclaredFields(); for(int i=0;i<fields1.length;i++){ fields1[i].setAccessible(true); if(fields1[i].getType()==String.class){ String oldvalue=(String)fields1[i].get(p1); String newvalue=oldvalue.replace('b','a'); fields1[i].set(p1,newvalue); } } Field[] fields2=p2.getClass().getDeclaredFields(); for(int i=0;i<fields2.length;i++){ fields2[i].setAccessible(true); if(fields2[i].getType()==String.class){ String oldvalue=(String)fields2[i].get(p2); String newvalue=oldvalue.replace('b','a'); fields2[i].set(p2,newvalue); } } System.out.println(p1); System.out.println(p2); } }
程序运行结果:
程序应该在内部处理异常,为了编写方便,直接在主函数中抛出。Class对象的getFileds()方法返回Field对象数组,field对象.getType()==String.class;指向同一个地址,用==判断,不用equals()判断,因为类字节码在内存中只有一份。field对象.set(obj,newValue)可对对象的成员值进行设置。
3、Method类
Method类用于成员方法的反射,Method对象代表某个类中的一个成员方法 。对象.getClass().getMethod(方法名,参数类型.class)获取指定方法对应的Method类对象。必须要指明参数类型,以确定是哪一个重载形式。Method对象.invoke(调用方法的对象,参数)可以在某个对象上调用反射来的该方法,如果调用静态方法,第一个参数用null,意味着method对应着一个静态方法,该方法调用不需要对象,第二个参数可以封装成数组,这是1.4版本的调用方式,1.5版本改成可变参数列表形式。
4、接受数组参数的方法的反射
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
main接受的参数中含有要执行的类名,调用该类的main方法: Class.forName(" 类名").getMethod("main",String[ ].class).invoke(null,"");
代码如下:
class test{ public static void main(String[] args){ System.out.println("test.main()"); for(int i=0;i<args.length;i++) System.out.println(args[i]); } }
import java.lang.reflect.*; class callmain{ public static void main(String[] args){ try{ Class.forName(args[0]).getMethod("main",String[].class).invoke(null,new Object[]{new String[]{"aa","bb","cc"}}); }catch(ClassNotFoundException e){ throw new RuntimeException(e); }catch(NoSuchMethodException e){ throw new RuntimeException(e); }catch(IllegalAccessException e){ throw new RuntimeException(e); }catch(InvocationTargetException e){ throw new RuntimeException(e); } } }
运行结果如下:
分析Class.forName(args[0]).getMethod("main",String[].class).invoke(null,new Object[]{new String[]{"aa","bb","cc"}});
在调用invoke()方法时,需要传入方法所需的参数。main()方法所需参数类型为String[],即字符串数组。如果写成这样的形式invoke(null,"aa","bb","cc")JDK1.5按照所定义的形式Object invoke(Object obj, Object... args) 将参数解释为可变参数类型,将传给test.main()方法三个字符串参数,与所需的字符串数组类型不匹配,如果转向低版本JDK1.4去解释,JDK1.4是将invoke()传递的参数打包成Object[]类型,即对象数组类型,也不匹配。
如果写成这样的形式invoke(null,new String[ ]{"aa","bb","cc"})JDK1.5同样认为是打包好的可变参数形式,不匹配,转向JDK1.4解释,而JDK需要Object数组类型,String[]是字符串数组类型,两者不匹配。
只能写成这样的形式invoke(null,new Object[]{new String[ ]{"aa","bb","cc"}}),首先JDK1.5解包,发现数组元素只有一个String[],匹配。
或者写成这样的形式invoke(null,(Object)new String[ ]{"aa","bb","cc"}),JDK1.5将解释成一个Object变量,传递给所需的String[ ],类型转型成功,匹配。
5、数组的反射
具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象,代表数组的Class实例对象的getSuperclass()方法返回的是父类Object类对应的Class,int[ ] i=new int[5];i.getName()方法返回[I ,表示类型是int类型的数组。
直接打印数组对象,只能打印数组对象的类型和哈希值,Arrays.asList(Object[])可以将数组转化成容器打印,可以打印数组中每个元素的内容,JDK1.5版本中将参数变成了可变参数,<T> List<T> asList(T... a)。
Array工具类用于完成对数组的反射操作。不确定某个对象是具体对象,还是数组对象的方式如下:
printObject(Object obj){
Class class=obj.getClass();
if(class.isArray(){
int len=Array.getLength(obj);
for(int i=0;i<len;i++)
Array.get(obj,i);
else
直接打印obj;
}
目前还找不到比较好的方法得到数组中元素的类型。就是说我们拿Object接收一个数组对象,可以求得某个元素的具体类型,但无法取得该数组的类型。
三、HashSet中元素的比较
HashSet存储的元素不能重复,判断两个元素是否是同一个元素是比较两者的HashCode,每个对象都有自己的HashCode。在程序中可以覆盖对象的int hashCode()方法。
class Person{ public String name; public int age; public Person(String name,int age){ this.name=name; this.age=age; } public int hashCode(){ return age; } } class reflectdemo{ public static void main(String[] args){ Person p1=new Person("p1",21); Person p2=new Person("p2",22); Person p3=new Person("p3",23); Person p4=new Person("p4",24); HashSet<Person> hs=new HashSet<Person>(); hs.add(p1); hs.add(p2); hs.add(p3); hs.add(p4); System.out.println(hs.size()); p4.age=25; System.out.println(hs.remove(p4)); System.out.println(hs.size()); } }
上面的代码中,将Person类的hasCode()方法覆盖,用age值作为对象的HashCode值,在往hs中添加对象时,因为每个Person对象的age都不一样,HashCode也不一样,所以都能添加进来,hs.size()=4。当p4修改了字段age值,它的HashCode值也随之改变,当调用hs.remove(p4)时,将比较hs中每个元素的HashCode值,结果没有HashCode值是25的元素,删除失败,p4仍然在容器中。如果程序中反复修改对象字段值,并添加到容器中,容器比较HashCode值后都会接收,造成内存泄露。
四、反射的作用—实现框架功能。
框架要解决的核心问题:举例说,我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门窗插入进我提供的框架中。框架与工具有区别,工具类被用户的类调用,而框架则是调用用户提供的类。我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢,我写的框架程序怎样能调用到你以后写的类(门窗)呢?因为在写程序时无法知道要被调用的类名,所以,在程序中无法直接new某个类的实例对象了,而要用反射方式来做。以后客户端的编写的类全部存储在配置文件里,服务器端读取文件中数据,通过反射来完成功能,这样即使客户端类还没有编写出来,或者以后有什么改动,都不会修改服务端的代码。
相关文章推荐
- 乐观的程序员们
- 程序员是如何一步步走向平庸的?
- 为什么程序员也能成为伟大的CEO
- 程序员修炼指南——引导你成为真正的编程高手
- android面试题总结02 在android中本地写入一个xml文件
- [黑马程序员](第50天)一些平时刷的题(3)
- [黑马程序员](第45天)一些平时刷的题(2)
- [黑马程序员](第40天)一些平时刷的题
- [黑马程序员](第17)异常
- [黑马程序员](第37天)今晚敲的一些代码题
- [黑马程序员](第25天)高新技术之正则表达式
- 【剑指Offer面试题】 九度OJ1390:矩形覆盖
- 程序员的能力拓展模型
- [黑马程序员](第1-5天)基础部分
- 黑马程序员--JAVA<反射>
- [黑马程序员](第35天)交通管理系统练习题
- 【剑指Offer面试题】 九度OJ1389:变态跳台阶
- [黑马程序员](第31天)高新技术之反射
- [黑马程序员](第31天)高新技术之反射---易错点
- [黑马程序员](第33天)今晚做的7k面试题(1)