黑马程序员----反射

来源:互联网 发布:正航软件 编辑:程序博客网 时间:2024/05/16 07:42
---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IO开发S</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------


               java_高新_反射

 反射:


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


反射的基石->Class类

对比提问: Person类代表人,它的实例对象就是张三,李四这样一个个具体的人, Java程序中的各个Java类属于同一类事物,
描述这类事物的Java类名就是Class。对比提问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?

 

 人Person
 Java类Class


Class类代表Java类,它的各个实例对象又分别对应什么呢?


对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的
,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?


如何得到各个字节码对应的实例对象( Class类型)

 

 类名.class,例如,System.class


 对象.getClass(),例如,new Date().getClass()


 Class.forName("类名"),例如,Class.forName("java.util.Date");


九个预定义Class实例对象:


 参看Class.isPrimitive方法的帮助
 Int.class == Integer.TYPE


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


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


演示:
  

[java] view plaincopyprint?
  1. String str1="abc";  
  2.     
  3.   //获取str1的字节码文件  
  4.   Class cls1=str1.getClass();  
  5.     
  6.   //获取String的字节码文件  
  7.   Class cls2=String.class;  
  8.     
  9.   //返回与带有给定字符串名的类或接口相关联的 Class 对象  
  10.   Class cls3=Class.forName("java.lang.String");  
  11.   System.out.println(cls1==cls2);//true  
  12.   System.out.println(cls1==cls3);//true  
  13.     
  14.   //isPrimitive()  cls1是不是基本类型(字符串不是基本类型)  
  15.   System.out.println(cls1.isPrimitive());  
  16.     
  17.   //判断int是不是基本类型.true  
  18.   System.out.println(int.class.isPrimitive());  
  19.     
  20.   //判断int和Integer两个类的字节码是否相同false  
  21.   System.out.println(int.class == Integer.class);  
  22.     
  23.   //TYPE表示基本类型 int 的 Class 实例。 true  
  24.   System.out.println(int.class == Integer.TYPE);  
  25.     
  26.   //数组不是基本类型false  
  27.   System.out.println(int[].class.isPrimitive());  
  28.     
  29.   //判断是否是一个数组类.isArray();true  
  30.   System.out.println(int[].class.isArray());  


 

//-----------------------------------------------------------------------------
Constructor类:

 

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


得到某个类所有的构造方法:


例子:
Constructor [] constructors= Class.forName("java.lang.String").getConstructors();


得到某一个构造方法:


例子:
 Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
 //获得方法时要用到类型


创建实例对象:


通常方式:String str = new String(new StringBuffer("abc"));


反射方式:String str = (String)constructor.newInstance(new StringBuffer("abc"));
 //调用获得的方法时要用到上面相同类型的实例对象


Class.newInstance()方法:


例子:
String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。

演示:

  

[java] view plaincopyprint?
  1. //用字节码获取获取Stirng的StringBuffer构造方法.返回Constructor类型  
  2.   Constructor constructor1 = String.class.getConstructor(StringBuffer.class);  
  3.     
  4.   //再用Constructor类中的newInstance方法给构造方法实例化.  
  5.   String str2=(String)constructor1.newInstance(new StringBuffer("abc"));  
  6.     
  7.   //打印返回的字符串角标位置上的字符  
  8.   System.out.println(str2.charAt(2));  


 

//-----------------------------------------------------------------------------
Field类:

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


演示用eclipse自动生成Java类的构造方法


问题:

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

演示:
  

[java] view plaincopyprint?
  1. //成员变量的放射  
  2.   ReflectPoint pt1 = new ReflectPoint(36);  
  3.   //成员变量时共有的可以正常反射  
  4.   Field filedY = pt1.getClass().getField("y");   
  5.   System.out.println(filedY.get(pt1));  
  6.   
  7.     
  8.   //如果成员变量是私有的要强行反射getDeclaredField  
  9.   Field fieldX = pt1.getClass().getDeclaredField("x");  
  10.   //调用逆转方法setAccessible(true);  
  11.   fieldX.setAccessible(true);  
  12.   //获取  
  13.   System.out.println(fieldX.get(pt1));  


 

  一个问题,我把自己的变量定义成private,就是不想让人家访问,可是,现在人家用
  暴力反射还是能够访问我,这说不通啊,能不能让人家用暴力反射也访问不了我。首先,private主
  要是给javac编译器看的,希望在写程序的时候,在源代码中不要访问我,是帮组程序员实现高内聚、低
  耦合的一种策略。你这个程序员不领情,非要去访问,那我拦不住你,由你去吧。同样的道理,泛型集
  合在编译时可以帮助我们限定元素的内容,这是人家提供的好处,而你非不想要这个好处,怎么办?绕
  过编译器,就可以往集合中存入另外类型了。

 

替换成员变量的示例:

[java] view plaincopyprint?
  1. //替换成员变量的参数  
  2.  //pt1传进函数内  
  3.   
  4.  ReflectPoint pt1 = new ReflectPoint(36);  
  5.   
  6.  //原始的  
  7.  System.out.println(pt1);  
  8.  //修改中  
  9.  changeStringValue(pt1);  
  10.  //修改后  
  11.  System.out.println(pt1);  
  12.   
  13.  public static void changeStringValue(Object obj)throws Exception{  
  14.   //获取穿进去的pt1(obj)字节码,调用字节码的getFields方法.获取pt1里面所有的成员变量,返回类型是Field类型的数组  
  15.   Field[] fields = obj.getClass().getFields();  
  16.   //用高级for循环遍历数组.  
  17.   for(Field field : fields){  
  18.    //遍历的过程中判断成员变量是否是String类型的字节码文件.  
  19.    if(field.getType() == String.class){  
  20.     //如果是.用Field对象的get方法,获取该对象的字符串,并强转成String.  
  21.     String oldValue = (String)field.get(obj);  
  22.     //用字符串的replace方法.将字符串的出现的所以的b,替换成a.  
  23.     String newValue = oldValue.replace('b''a');  
  24.     //使用Field的set方法设置穿进去pt1对象Obj代表穿进去的对象,newValue表替换的值,也就是新值  
  25.     field.set(obj, newValue);  
  26.    }  
  27.   }  
  28.  }  
  29.   
  30. package cn.itcast.day01;  
  31.   
  32. public class ReflectPoint {  
  33.  private int x;  
  34.  public int y;  
  35.  public String str1="ball";  
  36.  public String str2="basketball";  
  37.  public String str3="itcast";  
  38.  public ReflectPoint(int x, int y) {  
  39.   super();  
  40.   this.x = x;  
  41.   this.y = y;  
  42.  }  
  43.  @Override  
  44.  public String toString(){  
  45.   return str1+":"+str2+":"+str3;  
  46.  }  
  47.    
  48. }  


 

//-----------------------------------------------------------------------------
Method类:

 

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


得到类中的某一个方法:


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

 

调用方法:


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

 

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

 

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})形式。

 

演示:

  

[java] view plaincopyprint?
  1. //获取String的字节码调用字节码的getmethod方法.获取String的charAt方法.int.class是1.5之后的  
  2.   //新特性.可变参数.  
  3.   Method methodCharAt = String.class.getMethod("charAt"int.class);  
  4.   //调用Method的invoke方法.获取Str1,的第二个角标位置  
  5.   System.out.println(methodCharAt.invoke(str1, 2));  


 

//-----------------------------------------------------------------------------
用反射方式执行某个类中的main方法:


目标:

写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普
通方式调完后,大家要明白为什么要用反射方式去调啊?


问题:


启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射
方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4
的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种
语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的
参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它
当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。

 

解决办法:


mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不
会数组打散成若干个参数了
  

[java] view plaincopyprint?
  1. //调用程序的main方法.  
  2.   //原始方法  
  3.   //TestArguments.main(new String[]{});  
  4.     
  5.   //args[0],是确定要调用哪一个类.需要在编译的时候在名字后面加上运行类的全名  
  6.   //在运行类的名字上按F2获取类的全名.然后点右键选择Run As --> Open Run Dialod.. -->单击.在Argument,把类名粘贴上去  
  7.   String startCalssName = args[0];  
  8.   
  9.   //用Class的forName方法获取类的名字.然后用类的名字调用getMethod方法.确定要调用的是main方法,里面传递才参数是String[]类型的  
  10.   Method maiMethod = Class.forName(startCalssName).getMethod("main",String[].class);  
  11.   
  12.   //嗲用Method的Invoke方法给方法实例化.并传递进去字符数组"111","222","333",这里需要二维数组,因为传递进去的时候会拆包一次.  
  13.   System.out.println(maiMethod.invoke(null,new Object[]{new String[]{"111","222","333"}}));  


 

//-----------------------------------------------------------------------------
数组的反射:

 

具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象(此处比较与值无关)。

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

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

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

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

示例:
  

[java] view plaincopyprint?
  1. //  
  2.   int[] a1=new int[]{1,2,3};  
  3.   
  4.   int[] a2=new int[4];  
  5.   
  6.   int[][] a3=new int[3][4];  
  7.   
  8.   String[] a4=new String[]{"aaa","bbb","ccc"};  
  9.   
  10.   System.out.println(a1.getClass()==a2.getClass());//true  
  11.   
  12.   System.out.println(a1.getClass()==a3.getClass());//false  
  13.   
  14.   System.out.println(a1.getClass()==a4.getClass());//false  
  15.      
  16.   printObject(a1);  
  17.   
  18.   private static void printObject(Object obj) {  
  19.   //判断穿进去的类型是否是数组  
  20.   if(obj.getClass().isArray()){  
  21.   //如果是调用Array的静态方法getLength获取Obj的长度  
  22.   int len = Array.getLength(obj);  
  23.   //然后遍历obj,并打印数组中的元素  
  24.   for(int i=0;i<len;i++) {  
  25.   
  26.   System.out.println(Array.get(obj, i));  
  27.   
  28.   }  
  29.   } else {  
  30.   //如果不是数组直接打印  
  31.   System.out.println(obj);  
  32.   }  
  33.  }  


 

//-----------------------------------------------------------------------------
反射的作用实现框架功能:

 

什么是框架,例如,我们要写程序扫描.java文件中的注解,要解决哪些问题:读取每一样,在每一个
中查找@,找到的@再去查询一个列表,如果@后的内容出现在了列表中,就说明这是一个我能处理和想
处理的注解,否则,就说明它不是一个注解或者说至少不是一个我感兴趣和能处理的注解。接着就编
写处理这个注解的相关代码。现在sun提供了一个apt框架,它会完成所有前期工作,只需要我们提供能够
处理的注解列表,以及处理这些注解的代码。Apt框找到我们感兴趣的注解后通知或调用我们的处理代码去处理。
你做的门调用锁,锁是工具,你做的门被房子调用,房子是框架,房子和锁都是别人提供的。


程序中不处理异常,而是main方法声明抛出异常,便于大家可以集中看主要的关键代码。
Class类也提供getResourceAsStream方法的比喻:如果你每次都找我给你商店买可乐,那我还不如直接向你买
可乐,即直接提供一个买可乐的方法给你。


框架与框架要解决的核心问题
我做房子卖给用户住,由用户自己安装门窗和空调,我做的房子就是框架,用户需要使用我的框架,把门
窗插入进我提供的框架中。框架与工具类有区别,工具类被用户的类调用,而框架则是调用用户提供的类。


框架要解决的核心问题
我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?
因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。

综合案例
先直接用new  语句创建ArrayList和HashSet的实例对象,演示用eclipse自动生成 ReflectPoint类的equals和hashcode方法,比较两个集合的运行结果差异。
然后改为采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果差异。


示例:



 

[java] view plaincopyprint?
  1. package cn.itcast.day01;  
  2.   
  3. import java.io.FileInputStream;  
  4. import java.io.InputStream;  
  5. import java.util.ArrayList;  
  6. import java.util.Collection;  
  7. import java.util.HashSet;  
  8. import java.util.Properties;  
  9. public class ReflectTest2 {  
  10.   
  11.  /** 
  12.   * @param args 
  13.   */  
  14.  public static void main(String[] args) throws Exception{  
  15.   // TODO Auto-generated method stub  
  16.   
  17.   //先演示相对路径的问题  
  18.   //InputStream is=new FileInputStream("config.properties");  
  19.     
  20.   //一个类加载器能加载.class文件,那它当然也能加载classpath环境下的其他文件,既然它  
  21.   //有如此能力,它没有理由不顺带提供这样一个方法。它也只能加载classpath环境下的那些文件。注意:直接使用类加载器时,不能以/打头。  
  22.   //InputStream is=ReflectTest2.class.getClassLoader().getResourceAsStream("cn\\itcast\\day01\\config.properties");  
  23.     
  24.   //Class提供了一个便利方法,用加载当前类的那个类加载器去加载相同包目录下的文件  
  25.   InputStream is = ReflectTest2.class.getResourceAsStream("config.properties");  
  26.   
  27.   Properties prop=new Properties();  
  28.     
  29.   prop.load(is);  
  30.   is.close();  
  31.     
  32.   String className = prop.getProperty("className");  
  33.     
  34.     
  35.   Collection pt1=(Collection)Class.forName(className).newInstance();  
  36.   //Collection pt1=new HashSet();  
  37.   ConstrucPoint cons1=new ConstrucPoint(3,3);  
  38.   ConstrucPoint cons2=new ConstrucPoint(5,5);  
  39.   ConstrucPoint cons3=new ConstrucPoint(3,3);  
  40.     
  41.   pt1.add(cons1);  
  42.   pt1.add(cons2);  
  43.   pt1.add(cons3);  
  44.   pt1.add(cons1);  
  45.   System.err.println(pt1.size());  
  46.  }  
  47.   
  48. }  


**********************************************************************
总结:


反射就是一句话.对象-->字节码-->获取方法用.

 




---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IO开发S</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------