Java高新技术2(反射)

 

1.Class类概述:

/*
Java程序中的各个Java类属于同一类事物,描述这类事物的Java
类名就是Class
 对比提问,众多的人用一个什么类表示?众多的Java类用一个什么类表示?
   人->Person
   .class->Class
Class类代表Java类,它的各个实例对象又分别对应什么呢?
 1.对应各个类在内存中的字节码,例如:Person类的字节码,ArrayList类的字节码等
 2.一个类被加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码
   不同的类,字节码是不同的,这一个个的空间可以用一个个的对象来表示,这些对象显然具有相同的类型
   这个类型就是Class
 */

2.获取字节码对象:

/*
 如何得到各个字节码对应的实例对象(Class类型)
   1.类名.class,例如:System.class
   2.对象.getClass,例如new Date().getClass();
      返回:表示此 对象运行时 的Class对象
   3.Class.forName("java.lang.String"):
     运行时,可以接收字符串变量
     作用:
     返回字节码,如果JVM已加载该字节码,直接返回
     如果没有,则用类加载器将硬盘中的字节码加载到虚拟机中

基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)
和关键字 void 也表示为 Class 对象(Class cls=void.class)。 
*/
package com.itheima.day1;

public class ReflectTest {

    /**
     * @param args
     */
    public static void main(String[] args)throws Exception {
        // TODO 自动生成的方法存根
    /*验证通过三种方式获取到的字节码对象是否是同一个*/ 
    Class cls1=String.class;
    Class cls2="abc".getClass();
    Class cls3=Class.forName("java.lang.String");//此方法上声明有异常
    System.out.println("①"+(cls1==cls2));//true
    System.out.println("②"+(cls2==cls3));//true
    /*
     以上结果说明JVM中只会保存一份同样的字节码
     以后再用到还是该字节码
     */
//     isPrimitive():
//        判定指定的 Class 对象是否表示一个基本类型。 
//    有九种预定义的 Class 对象,表示八个基本类型和 void。
//   这些类对象由 Java 虚拟机创建,与其表示的基本类型同名
//   
    System.out.println("③"+cls1.isPrimitive());//false
    System.out.println("④"+int.class.isPrimitive());//ture
    System.out.println("⑤"+Integer.TYPE.isPrimitive());//ture
    System.out.println("⑥"+Integer.class.isPrimitive());//false
    
    //public boolean isArray()
   //判定此 Class 对象是否表示一个数组类。
    System.out.println("⑦"+int[].class.isArray());//true
    /*
     总之,只要是在源程序中出现的类型,都有各自的Class实例对象:例如:int[],void..
     */
   
  }

}

3.构造方法反射:

package com.itheima.day1;
/*
 反射就是把 Java类中的各种成分 映射成  相应的Java类.
 Java类中有什么?(类所属的包,类中的字段,类中的方法)
  Package getPackage() 
          获取此类的包。 
  Method getMethod(String name, Class<?>... parameterTypes) 
          返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 
  Field getField(String name) 
          返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
  每个返回值都是一个对象,都映射成相应的类(相当于对java中所有类中的成分向上抽取描述),对上面那句话的理解 
 学习反射的目的:
   一个类中的 每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些
   实例对象后,如何用?怎么用?这才是学习和应用反射的关键
 */
import java.lang.reflect.*;
public class ReflectConstructor {

    /**
     * @param args
     */
    public static void main(String[] args)throws Exception {
        // TODO 自动生成的方法存根
     /*
      public Constructor<T> getConstructor(Class<?>... parameterTypes)
                              throws NoSuchMethodException,
                                     SecurityException
                                     返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
                parameterTypes 参数是 Class 对象的一个数组,
                                     这些 Class 对象按声明顺序标识构造方法的形参类型。 
      */
      //new String(new StringBuffer("abc"));
      Constructor strCons=String.class.getConstructor(StringBuffer.class);//对应String类中形参类型为StringBuffer的构造器对象
      Constructor strBufCon=StringBuffer.class.getConstructor(String.class);//对应StringBuffered类中形参类型为String的构造器对象
      /*
       public T newInstance(Object... initargs)//如果传入基本类型->自动装箱
              throws InstantiationException,
                     IllegalAccessException,
                     IllegalArgumentException,
                     InvocationTargetException
                     使用此 Constructor 对象表示的构造方法来   创建该构造方法的声明类 的新实例,
                     并用指定的初始化参数初始化该实例。个别参数会自动解包,以匹配基本形参,
                     必要时,基本参数和引用参数都要进行方法调用转换。 
       */
      String str=(String)strCons.newInstance(strBufCon.newInstance("abc"));//这里没有使用泛型,编译时期不能确定返回类型,返回Object                                                      //如果传入类型非StirngBuffer类型->IllegalArgumentException
     
      System.out.println(str+" "+str.length());
      System.out.println(String.class.newInstance());//
使用缓存机制来保存默认构造方法创建的实例对象
                                                  //Class类中的newInstance相当于调用该字节码对象所表示的类的无参构造器.
                                                     //该语句相当于new String();
    }

}

ReflectConstructor

4.成员字段反射:

获取该类中的字段:

package com.itheima.day1;
//测试反射用
public class ReflectPoint {
  private int x;
  int y;
  public int z;
  String str;
    public ReflectPoint(int x, int y, int z) {
        super();
        this.x = x;
        this.y = y;
        this.z = z;
    }
    public ReflectPoint(String str) {
        super();
        this.str = str;
    }
    @Override
    public String toString(){
        return str;
    }
}
package com.itheima.day1;
import java.lang.reflect.Field;
public class ReflectField {

    /**
     * @param args
     */
    public static void main(String[] args)throws Exception{
        // TODO 自动生成的方法存根
      ReflectPoint rp=new ReflectPoint(4,5,6);
     
      
      
      
      /*
       public Field getField(String name)
               throws NoSuchFieldException,
                      SecurityException返回一个 Field 对象,
                   它反映此 Class 对象所表示的类或接口的指定公共成员字段.(default访问权限也不能,必须public)
        name 参数是一个 String,用于指定所需字段的简称。
                  返回: 由 name 指定的该类的 Field 对象 
       */
      //获取公共字段的值
      Field fieldZ=rp.getClass().getField("z");//ReflectPoint类中的字段z被封装成Field对象
                                                //注意Field对象中z并没有值
                                              //仅仅把ReflectPoint的字段z封装了,而字段的值需要对象去初始化的
                                              //一个类可以有成千上万个对象,每个对象的中同一个字段的值都可能不同
                                               //因此下面要获取z的值,指出ReflectPoint哪个对象
      System.out.println("z="+fieldZ.get(rp));//6
      
      
      
      //获取私有/默认字段的值
      /*
      public Field getDeclaredField(String name)
                       throws NoSuchFieldException,
                              SecurityException
                返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
      name 参数是一个 String,它指定所需字段的简称。注意,此方法不反映数组类的 length 字段。 
      */
      Field fieldX=rp.getClass().getDeclaredField("x");//注意该方法只要是类中声明过的一定能拿到
                                                       //只要你有,管你暴露不暴露,藏起来我也要弄出来
     
      /*
       即使拿到了该fieldX对象,但是你获取不到字段值,依然是权限问题,这时需要用到一个类: AccessibleObject
     AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。
     它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。
     对于公共成员、默认(打包)访问成员、受保护成员和私有成员,
     
     在分别使用 Field、Method 或 Constructor 对象来设置或获取字段、调用方法,
     或者创建和初始化类的新实例的时候,会执行访问检查。   
     
     public void setAccessible(boolean flag)
                   throws SecurityException
              将此对象的 accessible 标志设置为指示的布尔值。值为 true 
              则指示反射的对象在使用时应该取消 Java 语言访问检查。
              值为 false 则指示反射的对象应该实施 Java 语言访问检查。   
     */
      fieldX.setAccessible(true);//暴力反射,抢~~ - -!
      System.out.println("x="+fieldX.get(rp));//4
      
      Field fieldY=rp.getClass().getDeclaredField("y");
      //fieldY.setAccessible(true);//经测试,default权限可以不用进行标志设置也可以获取到,因为default权限的字段类外也可访问
      System.out.println("y="+fieldY.get(rp)+"\n");//5
      getField(rp);
      
    }
    
    
    //尝试使用数组的方式获取
    public static void getField(ReflectPoint rp)throws Exception{
         
         //Field[] fieldArr=rp.getClass().getFields();//该数组只存入public字段
         Field[] fieldArr=rp.getClass().getDeclaredFields();
         for(Field f : fieldArr){
             System.out.println(f);
             f.setAccessible(true);
             System.out.println(f.get(rp));
         }
        
    } 

}
/*
 注意几个异常:
  1.如果对private/default字段使用getField(String)-->Exception in thread "main" java.lang.NoSuchFieldException: x/y
  2.如果未设置此Field对象的accessible(可存取)标志为true,而调用get
    -->Exception in thread "main" java.lang.IllegalAccessException: Class ReflectField can not access a member of class ReflectPoint with modifiers "private"
 */

ReflectField

字段反射练习:

package com.itheima.day1;
import java.lang.reflect.Field;
//将任意一个对象中的所有String类型的成员变量所对应的字符串内容中含有"b"改成"a"
public class ReflectFieldTest {

    public static void main(String[] args)throws Exception {
        // TODO Auto-generated method stub
        ReflectPoint rf=new ReflectPoint("bbca");
        setStrValue(rf);
        System.out.println(rf);//aaca
    }
    public static void setStrValue(Object obj)throws Exception{
         
    Field[] fieldArr=obj.getClass().getDeclaredFields();
    
    for(Field f : fieldArr){
      Class type=f.getType();
       
if(type==String.class){//判断下类型是否为String类型
           f.setAccessible(true);//为了修改所有满足条件的字段
           String value=(String)f.get(obj);
           if(value.contains("b"))
               f.set(obj, value.replaceAll("b","a"));//会将该字符串中所有是"b"的子串替换为"a"
        }
    }
  }
}

5.成员方法的反射:

 

package com.itheima.day1;
import java.lang.reflect.Method;
public class ReflectMethod {

    public static void main(String[] args)throws Exception{
        // TODO 自动生成的方法存根
//实现通过String对象调用charAt(1); 
    
    /*public Method getMethod(String name,Class<?>... parameterTypes) throws NoSuchMethodException
       SecurityException返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法*/
       Method strMethod=String.class.getMethod("charAt", int.class);
       
       /*
        public Object invoke(Object obj,Object... args)
     throws IllegalAccessException,
            IllegalArgumentException,
            InvocationTargetException
       对带有 指定参数 的 指定对象 调用由此 Method 对象表示的底层方法。
       个别参数被自动解包,以便与基本形参相匹配,基本参数和引用参数都随需服从方法调用转换。 
         如果底层方法是静态的,那么可以忽略指定的 obj 参数,该参数可以为 null。   
        返回: 使用参数 args 在 obj 上指派该对象所表示方法的结果          
        
     */
    System.out.println(strMethod.invoke("abc", 1));//该方法被哪个对象调用,以及传入相应实参
    System.out.println(strMethod.invoke("abc", new Object[]{1}));//在JDK 1.5之前,传入一个数组对象
                                                                  //所以为Object,因为数组中元素类型是不确定的
                                                                  //可以new Object[]{"abc",1}
    /*                                                             
     这里非常别扭:invoke方法是通过Method对象调用
     通俗例子:
       列车司机把列车刹车:列车司机给列车发信号(刹车动作),哥们停车吧,真正让列车停车的是列车自己(内部一系列动作)
       画圆方法定义在圆的内部(Circl.draw(圆心,半径)),关门(Door.close())
     */
  }

}

ReflectMethod

 

练习:通过反射调用某个类的main方法:

package com.itheima.day1;
import java.lang.reflect.Method;
import java.util.Arrays;
//需求:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法,由于不知道是哪个类,通过反射解决.
//该例子的难点:给main方法传入的实参!
//例如调用ReflectMethod中的main方法

public class ReflectMethodTest {
  public static void main(String[] args)throws Exception{
   invokeMainMethod("com.itheima.day1.TestInvoke");//不要忘了包名(完整类名) 
 }
  public static void invokeMainMethod(String className)throws Exception{
      Class  cls=Class.forName(className);
      Method method=cls.getMethod("main", String[].class);
      
      //第一种解决方案
      method.invoke(null,new Object[]{new String[]{"123","456"}});//以打包/拆包理解
                                                              //main方法的形参类型为String[]
                                                     //这里如果传入new String[]{"123"}等价于"123"->类型为String
                                                     //这里需要传入一个数组对象:new String[]{"123"},需要再封装为数组
          //new Object[]{new String[]{"123"}},Object[0]指向new String[]{"123"}-->相当于传入了一个实参->new String[]{"123"}
                //说明当传入new String[]{"123"},可变参数并不会再次封装成一个数组,而是把它当成String数组对待->兼容JDK 1.5以前版本
     
//第二种解决方案
      method.invoke(null,(Object)new String[]{"123","abc"});//(Object)强转动作告诉编译器->这是一个对象(字符串数组对象),别拆开
  }
}


//调用该类的main方法
class TestInvoke{
    public static void main(String[] args){
        System.out.println(Arrays.toString(args));
    }
    
}

ReflectMain

6.数组,Object,Object[]之间关系探讨:

package com.itheima.day1;

import java.util.Arrays;

//数组反射
/*每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。*/
public class ReflectArray {

    public static void main(String[] args)throws Exception {
        // TODO 自动生成的方法存根
     int[] arr1=new int[3];
     int[] arr2=new int[4];
     int[][] arr3=new int[2][3];
     String[] str=new String[2];
    
     System.out.println(arr1.getClass());//class [I
     System.out.println(arr3.getClass());//class [[I
     System.out.println(str.getClass());//class [java.lang.String
     System.out.println(arr1==arr2);//false
     System.out.println((arr1.getClass()==arr2.getClass())+"\n");//同一个字节码对象:class [I
     
     //通过forName获取字节码对象
     System.out.println(Class.forName("[[I")+"\n"); 
     
     //查看数组字节码文件对象所表示的类的 超类的字节码对象
     System.out.println(arr1.getClass().getSuperclass());
     System.out.println(arr3.getClass().getSuperclass()); 
     System.out.println(str.getClass().getSuperclass()+"\n");//均为class java.lang.Object
    
     //Object,Object[],数组之间的转化
     convert(arr1,arr3,str);
    
    }
    
    public static void convert(int[] arr1,int[][] arr3,String[] str){
         Object obj=null;
         Object[] objArr=null;
         
         //一维数组
         obj=arr1;//obj和arr1均指向new int[3]
         System.out.println("arr1->"+arr1+"  obj->"+obj);//但是你不能使用obj[0]取出元素->因为obj类型为Object(指向一个对象)
        
//objArr=arr1;//
int[]与Object[] 类型不匹配(arr1数组中元素为int而不是Object或其子类)
         
         //数组的数组
         obj=arr3;//obj和arr3均指向new int[2][3];
         System.out.println("obj->"+obj+"  arr3->"+arr3);
         objArr=arr3;//Object[] objArr=new int[2][3];-->objArr数组中每个元素指向了一个一维数组对象
         System.out.println("objArr->"+objArr+"  arr3->"+arr3+
                             "  arr3[0]->"+arr3[0]+"  objArr[0]->"+objArr[0]+
                             "  arr3[1]->"+arr3[1]+"  objArr[1]->"+objArr[1]);
        
         //元素的类型为String
         obj=str;
         System.out.println("obj->"+obj+"  str->"+str);
         objArr=str;//区分上面数组元素为基本类型的objArr=arr1,Object[] objArr=new String[2];
                     //objArr中的每个元素指向一个String对象
         System.out.println("objArr->"+objArr+"  str->"+str+
                            "  str[0]->"+str[0]+"  objArr[0]->"+objArr[0]+
                            "  str[1]->"+str[1]+"  objArr[0]->"+objArr[1]);//str[0]/objArr[0]获取到数组的String类型元素的值(默认值null)
         str[0]="abc";
         System.out.println(str[0].length()+" "+((String)objArr[0]).charAt(1)+"\n");//Object[] objArr=new String[2],元素类型为Object
        
        
         //最后关于Arrays.asList方法:
         /*
          JDK 1.4: public static List asList(Object[] a)//不能接受收基本类型一维数组(int[])
          JDK 1.5: public public static <T> List<T> asList(T... a)
         */
         arr1[0]=1;
         arr1[1]=2;
         System.out.println(Arrays.asList(arr1)+" "+Arrays.asList(str));//第一个使用:asList(T... a)->传入了一个参数(一个 一维数组对象)->存入集合 
//第二个使用:asList(Object[] a)->传入new String[2],将a数组中每一个元素(字符串对象)存入集合
                                                                         
    }

}

数组与Object

 

7.数组反射:

package com.itheima.day1;
import static java.lang.reflect.Array.getLength;
import static java.lang.reflect.Array.get;

import java.util.Arrays;
//数组反射获取数组属性length,数组中元素值
/*
 public boolean isArray() :判定此 Class 对象是否表示一个数组类。 
 
 public static int getLength(Object array)
                     throws IllegalArgumentException
    以 int 形式返回指定数组对象的长度。 
 
 public static Object get(Object array,
                         int index)
  throws IllegalArgumentException
   ArrayIndexOutOfBoundsException
 返回指定数组对象中索引组件的值。
 如果该值是一个基本类型值,则自动将其包装在一个对象中。  
 */
public class ReflectArray2 {
   
    public static void main(String[] args){
        // TODO 自动生成的方法存根
        Object obj=null;
        obj=new int[]{3,5};
        //System.out.println(Arrays.toString(obj.getClass().getDeclaredMethods()));
    
        printObj(obj);
        obj="cdef";
        printObj(obj);
        

}
   //一.根据传入对象不同采取不同方式打印
   //传入数组对象->打印数组中的元素,传入非数组对象->打印该对象
    private static void printObj(Object obj) {
        // TODO 自动生成的方法存根
        Class cls=obj.getClass();
        if(cls.isArray()){//true->实参为一个数组对象
          for(int i=0;i<getLength(obj);++i)
              System.out.println(get(obj,i));
        }
        else
         System.out.println(obj);
    }
  
}
/*
 思考:如何得到数组元素的类型?
 回到是:这是不可能的
 int[] arr=new int[3];//这样的数组还可能想办法获取数组元素类型
 Object[] obj=new Object[]{"abc",1,1.3};//数组元素的类型是什么? 不同统一而论
 obj[0].getClass().getName();//也就是说只能获取到某个元素的具体类型
 */

ReflectArr

posted @ 2013-07-19 21:43  伊秋  阅读(320)  评论(0编辑  收藏  举报