黑马程序员:jdk1.5新特性3 (反射)

来源:互联网 发布:杭州首展科技公司知乎 编辑:程序博客网 时间:2024/04/29 14:44
 

反射 ( reflect 反射不是JDK 1.5的新特性,是java1.2开始有的。)
 透彻分析反射的基础-Class
  1 java 程序中的各个java类 属于同一类事物,描述这类事物的java类名就是 Class。
  2  Class类 代表java类,它的各个实例对象分别对应什么呢?
     ^对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码....
     ^一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不    同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个空间分别用一个个的对    象来表示,这些对象具有了相同的类型Class.
  3 如何得到各个字节码对应的实例对象(Class类型)
    ^类名.class,例如 System.class
    ^对象。getClass(),例如new Daye().getClass
    ^Class.forName("类名"),例如,Class.forName("java.lang.Date");
     面试题:Class.forName("")的作用是什么?
            答:返回字节码。有两种方式:
                1)如果字节码在java虚拟机中已经存在,则在内存中找到并返回该字节码
                2)如果字节码在java虚拟机中不存在,则类加载器将其加载到内存后 返回该字节码。
   4 九个预定义的Class对象
      八个基本类型 和void (int.class  、 void.class...)
       参看Class.isPrimitive方法的帮助 (是不是基本数据类型)
       Int.class==Integer.TYPE
   5 数组类型的Class实例对象
    Class.isArray()
 总之只要在源程序中出现的类型,都有各自的Class实例对象,例如:int[],void...

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


Constructor类 代表某个类中的一个构造方法
得到某个类的构造方法:
  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();
  该方法内部先得到默认的构造方法(无参的构造方法被缓存了),然后用该构造方法创建实例对象。
  该方法用到了缓存机制来 保存默认的构造方法的实例对象。


Field 类  代表某个类中的一个成员变量
 问题:得到的Field对象是对应到类上的成员变量,还是对应到对象上的成员变量?
 类只有一个,而该类的实例对象有多个,所以Field对象关联的是类上的成员变量。
  所以字段fieldX代表X的定义,而不是具体的X变量。
 
public class ReflectPoint {
  private int x;
  public int y;
  public ReflectPoint(int x, int y) {
 super();
 this.x = x;
 this.y = y;
}}
public class ReflectTest{
   public static void main(String[] args){
    try {
        ReflectPoint flect1=new ReflectPoint(4,6);
 Field fieldY=flect1.getClass().getField("y");
         //fieldY的值是多少?5错:fieldY是类的变量,不是对象的。
         System.out.println(fieldY.get(flect1));
   /*Field fieldX=flect1.getClass().getField("x");
    * x是私有的外部类不能访问,要想获得X只能强制反射
    */
  Field fieldX=flect1.getClass().getDeclaredField("x");
  fieldX.setAccessible(true);
  System.out.println(fieldX.get(flect1));
  } catch (Exception e) {
     // TODO Auto-generated catch block
  e.printStackTrace();} } }  

作业:将任意一个对象中的所有String类型的成员变量所有对应的字符串内容中的“b”改为“a”
import java.lang.reflect.Field;
public class ReflectTest{
    ReflectPoint flect1=new ReflectPoint();
    changeStringValue(flect1);
    System,out.print(flect1);
  public void changeStringValue(Object obj)throws Exception{
     Field[] fields=obj.getClass().getFields();
     for(Field field:fields){
            if(field.getType==String.class){
            String oldString=field.get(obj);
            String newString=oldString.replace("b","a");
            field.set(obj,newString);
                                            }
                             }
                                                           }
                         }       
public class ReflectPoint {
  private int x;
  public int y;
  public String str1="boy";
  public String str2="field";
  public String str3="hello";
  @Override
public String toString(){
   return str1+":"+str2+":"+str3; 
                        }
                          }


Method类 代表某个类中一个成员方法
得到类中某一个方法:
  Method methodCharAt=Class.forName("java.lang.String").getMethod("charAt",int.class);
调用方法:
   通常:System.out.print(str.charAt(1));
   反射:System.out.print(methodCharAt.invoke(str,1));
 如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法
(因为静态方法调用时 对象还没有产生)System.out.print(methodCharAt.invoke(null,1));  
jdk1.4和 jdk1.5的区别:
   jdk1.5: public Object invoke(Object obj,Object ... args)
   jdk1.4: public Object invoke(Object obj,Object[] args)
 按jdk1.4的语法需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法的一个参数,所有 调用charAt方法的代码也可以用jdk1.4改写methodCharAt(str,new Object[]{1});
 

对接收数组参数的成员方法进行反射
 用反射的方式执行某个类中的main方法:
  目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类的main()。那么我们为什么要用反        射的方式调用呢?
  问题:启动java程序的main方法的参数时一个字符串数组,通过反射调用main方法时,如何为invoke         方法传递参数呢?按jdk1.5,整个数组是一个参数;按jdk1.4,数组中的每个元素对应一个参数        jdk1.5肯定要兼容jdk1.4的语法,把数组打散成若干个单独的参数。所以给main方法传递参数时        不能使用代码。

 解决办法:
  1 mainMethod.invoke(null,new Object[]{new String[]{"xxx"}})
    将数组作为另一个数组的元素。
  2 mainMethod.invoke(null,(object)new String[]{"xxx"});
   编译器会作特殊处理,编译时不把参数当做数组看待。
目标程序:(运行这段代码时 我们在运行对话框里 给类传递参数(要运行main方法类的完整名称)这样主          函数才能找到类名进行反射)
import java.lang.reflect.Method;
public class MainMethodRunTest {
    public static void main(String[] args) {
 String mainStaticMethod = args[0];
  try {
    Method mainMethod=
 Class.forName(mainStaticMethod).getMethod("main",String[].class);
    mainMethod.invoke(null,new Object[]{new String[]{"12","43","34"}}); 
  //mainMethod.invoke(null, (Object)new String[]{"12","43","34"});  
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();}}}
  class MainMethodTest {
 public static void main(String[] args) {
    // TODO Auto-generated method stub
          for(String arg:args){
           System.out.println(arg);
          } } }


数组与Object的关系及其反射类型
 1 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象
 2 代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
 3 基本类型的一位数组可以被当做Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当作Object类型也可当作Object[]类型使用。
 4 Arrays.asList()方法处理int[]和String[]时的差异
    jdk1.4中Arrays.asList(Object[] a) 它将String[]的元素转化为List对象,就可以打出了;而
    int[] 属于Object类型 不属于Object[] 只能由jdk1.5处理。jdk1.5中,Arrays.asList(T ... a)
    int[] 被当作一个Object 所以不能打出int[]的各个元素。
  int [] a1=new int[]{1,2,8};
  String [] a2=new String[]{"a","b","d"};
  System.out.println(Arrays.asList(a1)); //结果[[I@C17164]
  System.out.println(Arrays.asList(a2));  //结果[a,b,d]
 5 Array工具类用于完成对数组的反射操作
 例:打印任意对象
 import java.util.Arrays;
 public class AyyayReflectTest{
  public static void main(String [] args){

     int [] a1=new int[]{1,5,8};
     String [] a2=new String[]{"v","fd","h"};
     printObject(a1);
     printObject(a2);
     printObject("xyz");
  }
 private static void printObject(Object obj) {
  // TODO Auto-generated method stub
  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);
  }  }    }

ArrayList、HashSet的比较及Hashcode分析
ArrayList按顺序存储元素,可以重复
HashSet存储的元素不可以重复,当存储元素时先比较如果存在该元素则不存放。
     它们都可以实现Collection接口
**分析Hashcode:
 如果要查找一个集合中是否有某个对象,通常我们会逐一取出每个元素与查找的对象比较,直到找到那个用equals方法比较相等的值,停止查找并返回肯定的信息,如果有一万个元素并且不包含要查找的对象,那么equals方法会被调用一万次。有人发明了哈希算法,计算出每个元素的哈希值,并将集合分为若干区域,这样查找对象时 只要计算出它的哈希值所对应的区域 在对这个区域进行查找 ,大大的提高了程序的运行效率。当然Hashcode只对用哈希算法存储的集合有价值,例如HashSet.

注意(内存泄露):当一个对象被存入HashSet集合以后,就不能修改这个对象中的那些参与计算哈希值    的字段了,否则,对象修改后的哈希值与最初存入的不同,这时即使在contains方法使用该对象当前引   用作为参数去HashSet集合检索对象,也将 返回 找不到对象的结果,这也会导致无法从HashSet集合中   单独删除当前对象,造成内存泄露。

反射的作用——>实现框架功能
  框架与工具类的区别:工具类被用户调用;框架则是调用用户提供的类
  框架要解决的核心问题:写程序时无法知道要被调用的类名,所以在程序中无法直接new某个类的实例对象,而要用反射方式来做
综合案例:采用配置文件夹反射的方式创建ArrayList和HashSet的实例对象。
   工作间workspacethree 类名ReflectPoint.java和ReflectTest2.java  配置文件config.properties  *   配置文件一定要用完整的目录,这个目录不是硬编码,是运算出来的。
 *   在javaWeb中  我们通过getRealPath();来获得绝对路径 将配置文件放在一个目录下,之后让用户 *   配置这个目录
 *   另一个获得资源文件的方式,用类加载器
ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast.day1/config.properties")
ReflectTest2.class.getResourceStream("config.peoperties")


内省-->了解JavaBean
IntroSpector(内省)-->JavaBean-->特殊的java类
JavaBean:是一种特殊的java类主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,           且方法名符合某种命名规则
 如果在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object简称VO),这些信息在类中用私有字段来存储,称为类的属性,
属性是由方法来判断的,去掉get或set后 就是JavaBean的属性,如果第二个字母是小写则第一个字母变成小写getTime-->time、gettime-->time、getCPU-->CPU

 JavaBean类的好处:
   1 在javaEE开发中,经常要使用JavaBean,很多环境要求按JavaBean的方式操作。
   2 JDK中提供了对JavaBean进行操作的一些API,这套API 称为内省,使用内省操作JavaBean比普通类       的方式更方便
写一段代码 读取javaBean属性,然后再设置javaBean属性
   代码1: 用PropertyDescriptor类更简单(workspace2/javaenhance/cn/itcast/day1/IntroSpectTest.java)
  代码2:采用遍历BeanInfo的所有属性方式查找和设置某个ReflectPoint对象的x属性。在程序中把一个类当作javaBean来看,就是调用IntroSpector.getBeanInfo(),得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息

**BeanUtils工具包
  由于javaBean的属性设置和获取使用的非常多,有人开发出了Beanutils工具包。
  将beanutils包的最大jar包commons-beanutils-1.8.3放入工程中(在工程里新建一个文件夹lib),之  后build path;因为beanutils包使用到了commons-logging.jar,所以将这个jar同样的导入工程中。
 
(1)BeanUtils(获得设置javabean属性)
   get()返回的属性结果为字符串,set可接受任意类型对象,通常为字符串。
     BeanUtils.setProperty(Object,propertyName,value)
     BeanUtils.getProperty(object,peopertyName)
例:
     BeanUtils.setProperty(rp, "x", "8");
     BeanUtils.getProperty(rp,"x");

 *操作符合属性,支持属性的级联操作
  BeanUtils.setProperty(rp,"birthday.time" ,"111");
  System.out.println(BeanUtils.getProperty(rp, "birthday.time"));

 *java7新特性:beanutils可以对MAP进行操作。提供了Map与javabean 的转换
Map map={name:"lisi",age:17};
BeanUtils.setProperty(map,"name","wangsan");
 
(2)PropertyUtils(另一个获得设置javabean属性的简单类 )
get()返回的属性为bean本身类型,set只接受bean属性的本身类型。
    PropertyUtils.setProperty(bean,propertyName ,value);
    System.out.println(PropertyUtils.getProperty(bean, propertyName));
 例如:
    PropertyUtils.setProperty(rp, "x", 5);
    System.out.println(PropertyUtils.getProperty(rp, "x"));