您的位置:首页 > 职场人生

黑马程序员——自学总结(六)反射

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 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某个类的实例对象了,而要用反射方式来做。以后客户端的编写的类全部存储在配置文件里,服务器端读取文件中数据,通过反射来完成功能,这样即使客户端类还没有编写出来,或者以后有什么改动,都不会修改服务端的代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: