JAVA提高三:反射总结

为前期学习过反射,再这里再次复习总结下:【转载请说明来源:http://www.cnblogs.com/pony1223/p/7659210.html 】

一、透彻分析反射的基础_Class类

Class是一个类,他代表一类事物,它代表一类什么样的事物呢? Java 程序中的各个java类属于同一类事物,描述这类事物的java类名就是Class.

思考:
众多的人可以用一个什么类表示? Person
众多的类用一个什么类来表示?    答案是Class

我们知道Person代表一个人,一个人有他的身高,体重等属性,有叫,喊等方法,人还有他的父类,它的构造方法.接口等.
那么Class类呢?他是所有类的类.那么他可以干什么呢?他可以获取某一个类的属性,方法,父类,构造方法,接口等....

(1)Class的本质是什么?

我们现在定义两个对象:
Person p1 = new Person();
Person p2 = new Person();
那如何获取这两个对象的Class类呢?用Class cls1 = new Class()可以么?不可以,因为Class跟们就没有这个构造方法.
Class类在内存中代表的就是一段字节码,那什么是字节码呢?当我们在源程序用到Person这个类的时候,首先把这个类编译成class文件以后,存放到硬盘上,这些class文件其实就是一堆二进制代码,要把这一堆二进制代码加载到内存中来,然后才可以去创建一个个对象.当我在程序中用好多java类,有Person类,Math类,Date类,我又到这三个类,那么在内容中会有几个字节码呢?有三份字节码,每一份字节码就是Class的一个实例对象.

Class cls1 = Date.class;//代表Date类的字节码
Class cls2 = Pserson.class;//代表Person类的字节码

(2)那如何来得到这个Class呢?

第一种,就是类.class;
第二种得到字节码的方式是,用这个类的实例p1.getClass(),也可以返回这个类对应的字节码
第三种方式:Class.forName("java.lang.String"),里面需要制定类的完整的名字,这个方法的作用也是得到java.lang.String类的字节码.
Class.forName()的作用:
返回指定类的字节码.返回的方式有两种.第一种.这个字节码曾经被加载过,已经保存在java虚拟机中了,直接返回.另一种是,java虚拟机中还没有这个字节码,则用类加载器去加载,把加载进来的字节码就会存在java虚拟机里面了.以后要在等到这份字节码就不用再加载了.
将类转变成字节码有以上三种形式,而在反射中使用最多的是第三种方法,Class.forName(name),因为写源程序的时候,还不知道类的名字,可以作为变量传递.
九个预定义Class实例对象:
java中有八个基本类型(int ,byte,long,float,double,short,char,boolean,)和void,他们也对应有Class对象

总结:一个类的Class字节码只存在一份.

Class.isPrimitive()方法.判断一个类型是否是基本的数据类型.

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

 二、理解反射的概念

第一种官方理解:

反射是指在运行状态中,对于任意一个类,都可以获取到这个类的所有属性和方法;对于任意一个对象,都能够调用这个对象的任意方法和属性;这种动态获取信息及动态调用对象的方法,称为JAVA语言的反射机制。

第二种个人理解:

反射,就是把Java类中的各个成分映射成相应的Java类.我们想想,一个类身上有什么,有包,有方法,有属性,有构造方法,有接口.而这些方法相应的又返回一个类.看下面这个api,通过Class的方法可以得到什么.

 

 

三、构造方法的反射应用

package study.javaenhance;

import java.lang.reflect.Constructor;

public class ReflectTest
{
    public static void main(String[] args) throws Exception 
    {
        String str = "abc";
        Class clz1 = String.class;
        Class clz2 = str.getClass();
        Class clz3 = Class.forName("java.lang.String");
        System.out.println(clz1 ==  clz2);
        System.out.println(clz1 ==  clz3);
        getConstructor();
    }
    
    /**
     * 目前String 类型中有很多构造方法,我想要找到获取构造方法为StringBuffer类型的一个
     * @throws Exception 
     * @throws SecurityException 
     */
    public static void getConstructor() throws SecurityException, Exception
    {
        String.class.getConstructor(StringBuffer.class);
        //那么上面的获取到构造方法有什么用呢?
        /**
         * 通常,我们创建String 对象为 String abc = new String(new StringBuffer("abc")) 方式,
         * 下面我们通过反射来实现:
         *  1.加载类
         *  2.解析类
         *  
         */
        Constructor constructor = String.class.getConstructor(StringBuffer.class);
        String str =  (String) constructor.newInstance(new StringBuffer("abc"));
        System.out.println(str);
    }

}

 

具体的原理,如何使用可以参考:http://www.cnblogs.com/pony1223/p/7445950.html

 四、成员变量的反射

成员变量的反射,在这里我们先定义一个类,用于讲解成员变量的反射。

 

package study.javaenhance.util;import java.util.Date;

public class ReflectPoint {
    private Date birthday = new Date();
    
    private int x;
    public int y;
    public String str1 = "ball";
    public String str2 = "basketball";
    public String str3 = "itcast";
    
    public ReflectPoint(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }
    
    
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        result = prime * result + y;
        return result;
    }


    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final ReflectPoint other = (ReflectPoint) obj;
        if (x != other.x)
            return false;
        if (y != other.y)
            return false;
        return true;
    }


    @Override
    public String toString(){
        return str1 + ":" + str2 + ":" + str3;
    }


    public int getX() {
        return x;
    }


    public void setX(int x) {
        this.x = x;
    }


    public int getY() {
        return y;
    }


    public void setY(int y) {
        this.y = y;
    }


    public Date getBirthday() {
        return birthday;
    }


    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    
}

接下来,我们要采用反射的方式来获取其中的x 成员和 y成员

//成员变量的反射
        //1.首先我们先初始化一下ReflectPoint类的xy值,以便反射的时候获取其值
        ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后续加载类
        Field xField = rp.getClass().getField("x");
        //3.解析类
        System.out.println(xField.get(rp));

上述会出现一个错误:

Exception in thread "main" java.lang.NoSuchFieldException: x
at java.lang.Class.getField(Class.java:1520)
at study.javaenhance.ReflectTest.main(ReflectTest.java:25)

 也就是说找不到这个,为什么呢?很明显,因为是私有的,因此需要采用另外一个方法来获取,修改如下:

ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后续加载类
        Field xField = rp.getClass().getDeclaredField("x");
        //3.解析类
        System.out.println(xField.get(rp));

Exception in thread "main" java.lang.IllegalAccessException: Class study.javaenhance.ReflectTest can not access a member of class study.javaenhance.util.ReflectPoint with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.reflect.Field.doSecurityCheck(Field.java:960)
at java.lang.reflect.Field.getFieldAccessor(Field.java:896)
at java.lang.reflect.Field.get(Field.java:358)
at study.javaenhance.ReflectTest.main(ReflectTest.java:27)

现在是看到,却没法拿到,需要修改访问的权限打开,修改为如下:

ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后续加载类
        Field xField = rp.getClass().getDeclaredField("x");
        xField.setAccessible(true);
        //3.解析类
        System.out.println(xField.get(rp));

对于非私有的成员变量,没有这么麻烦,如下即可:

//成员变量的反射
        //1.首先我们先初始化一下ReflectPoint类的xy值,以便反射的时候获取其值
        ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后续加载类
        Field xField = rp.getClass().getDeclaredField("x");
        xField.setAccessible(true);
        //3.解析类
        System.out.println(xField.get(rp));
        Field yField = ReflectPoint.class.getField("y");
        System.out.println(yField.get(rp));

下面做一个小例子,我们看到ReflectPoint类中有很多成员变量,我们现在要做的是将String类型的成员变量的值中含b的字母替换称为a,如:ball 替换为aall

代码如下:

private static void changeStringValue(Object rp) throws Exception
    {
        //思路:1.反射获取要所有的成员变量  2.获取成员变量的类型判断是否为String类型 3.如果是则获取其值 4.替换 5.回填
        Field[] fields = rp.getClass().getFields();
        for (Field field : fields) {
            if(field.getType() == String.class)
            {
                String oldValue = (String) field.get(rp);
                String newValue = oldValue.replace("b", "a");
                field.set(rp, newValue);
            }
        }
    }

客户端调用代码:

 

ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后续加载类
        Field xField = rp.getClass().getDeclaredField("x");
        xField.setAccessible(true);
        //3.解析类
        System.out.println(xField.get(rp));
        Field yField = ReflectPoint.class.getField("y");
        System.out.println(yField.get(rp));
        
        //案例:changeStringValue
        System.out.println(rp);
        changeStringValue(rp);
        System.out.println(rp);

结果为:

2
4
ball:basketball:itcast
aall:aasketaall:itcast

五、成员方法的反射

//方法的反射
        Method methodCharAt = String.class.getMethod("charAt", int.class);
        System.out.println(methodCharAt.invoke(str, 1));
        //兼容JDK5.0之前的写法
        System.out.println(methodCharAt.invoke(str, new Object[]{2}));

可以看出还是比较简单的和成员的方式差不多,但是下面需要注意的是,如果有一个类,我们需要通过反射的方式去调用main方法,该如何去做呢?

先定义一个类:

class TestArguments{
    public static void main(String[] args){
        for(String arg : args){
            System.out.println(arg);
        }
    }
}

最先想到的代码如下:

//调用main方法,不采用TestArguments.main(new String[]{"1","2","3"}); 这种方式,因为有些场景我们的类是传入的是你运行期才知道的
        Class clazz = Class.forName("study.javaenhance.TestArguments");
        Method method =  clazz.getMethod("main",String[].class);
        //因为main方法是静态方法,所以第一个参数传入的为null
        method.invoke(null,new String[]{"1","2","3"});

结果如下:

为什么会这样呢?我们本身的使用其实是正确的,只是因为SUN公司的JDK5.0的可变参数出现后,为了兼容以前的数组方式所以出现了上面的错误。

在JDK5.0前:public Object invoke(Object obj, Object... args)   是采用的为 public Object invoke(Object obj, Object[] obj)

采用的数组方式进行的接受,那么这个时候你传入的是String[]{"1","2","3"} 那么它正好和Object[] obj匹配,因为在1.5前就会开始拆分,那么就变成了传入了3个String类的参数到了main方法上面,因此这个时候就参数类型个数无法匹配了,所以就出现了上面的错误了,如何修改匹配呢?方式如下:

 

//调用main方法,不采用TestArguments.main(new String[]{"1","2","3"}); 这种方式,因为有些场景我们的类是传入的是你运行期才知道的
        Class clazz = Class.forName("study.javaenhance.TestArguments");
        Method method =  clazz.getMethod("main",String[].class);
        //因为main方法是静态方法,所以第一个参数传入的为null
        //method.invoke(null,new String[]{"1","2","3"});
        //这样拆开后就是main方法想要的参数了
        method.invoke(null,new Object[]{new String[]{"1","2","3"}});
        //这样就是一个对象传入了,不需要拆了
        method.invoke(null,(Object)new String[]{"1","2","3"});

六、数组与Object的关系

先看下面的例子:

//数组与Object的关系
        int [] a1 = new int[]{1,2,3};
        int [] a2 = new int[4];
        int[][] a3 = new int[2][3];
        String [] a4 = new String[]{"a","b","c"};
        
        System.out.println(a1.getClass() == a2.getClass());//true
        //System.out.println(a1.getClass() == a4.getClass());//false
        //System.out.println(a1.getClass() == a3.getClass());//false
        System.out.println(a1.getClass().getName());//[I
        System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
        System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object

从上面的例子说明:

具有相同的维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象

代表数组的Class实例对象的个头Superclass方法返回的父类为Object类对应的class

基本类型的一维数组可以被当做Object类型使用个,不能当做Object[]类型使用;非基本类型的一维数组,既可以当做Object类使用,又可以当做Object[]类型使用 --- 这句话需要理解一下,参考下面的代码:

 

//数组与Object的关系
        int [] a1 = new int[]{1,2,3};
        int [] a2 = new int[4];
        int[][] a3 = new int[2][3];
        String [] a4 = new String[]{"a","b","c"};
        
        System.out.println(a1.getClass() == a2.getClass());//true
        //System.out.println(a1.getClass() == a4.getClass());//false
        //System.out.println(a1.getClass() == a3.getClass());//false
        System.out.println(a1.getClass().getName());//[I
        System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
        System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
        
        Object aObj1 = a1; //因为a1 是一个int[] 类型的数组所以是object类型
        Object aObj2 = a4;//因为a4 是一个String[]类型的数组所以是Object类型
        //Object[] aObj3 = a1;// 报错,因为a1 是一个int[]数组里面的元素都是int型,不是Object类型所以不可以如果改成包装类就可以
        Object[] aObj4 = a3; //a3 是一个二维数组,那么里面的一维数组就是一个Object类型,所以可以匹配Object[]代表里面每个元素类型为Object
        Object[] aObj5 = a4; //String[] 里面的每一个元素都是String类型,也是Object类型 所以可以匹配.
        
        System.out.println(a1);
        System.out.println(a4);

 

我们发现最后a1 和 a4 的输出是:

[I@87816d
[Ljava.lang.String;@422ede

这个是什么东西呢?我们找下JDK文档看下:

在Class 的getName 方法中说明如下:

 

即代表的是int类型的数组和String类型的数组,但我们肯定不希望输出这个样子,我们希望的看到的是里面的值,那么肯定有人会说用遍历的方法遍历出来,当然这是一种方法,但没有简单的方法吗?

 七、数组的反射应用

上面提到了遗留的问题,我们本节来解决,解决前我们先学习两个类:Array类 和Arrays类

Array 类提供了动态创建和访问 Java 数组的方法。需要说明下Array 是 数组的反射类,处于反射包下面,所以我们看到引用类型下对于类,我们是用Class  对于数组我们是用Array

Arrays 此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。

我们现在就是要利用Arrays类中的asList方法来完成显示的目的.

 

//数组与Object的关系
        int [] a1 = new int[]{1,2,3};
        int [] a2 = new int[4];
        int[][] a3 = new int[2][3];
        String [] a4 = new String[]{"a","b","c"};
        
        System.out.println(a1.getClass() == a2.getClass());//true
        //System.out.println(a1.getClass() == a4.getClass());//false
        //System.out.println(a1.getClass() == a3.getClass());//false
        System.out.println(a1.getClass().getName());//[I
        System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
        System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
        
        Object aObj1 = a1; //因为a1 是一个int[] 类型的数组所以是object类型
        Object aObj2 = a4;//因为a4 是一个String[]类型的数组所以是Object类型
        //Object[] aObj3 = a1;// 报错,因为a1 是一个int[]数组里面的元素都是int型,不是Object类型所以不可以如果改成包装类就可以
        Object[] aObj4 = a3; //a3 是一个二维数组,那么里面的一维数组就是一个Object类型,所以可以匹配Object[]代表里面每个元素类型为Object
        Object[] aObj5 = a4; //String[] 里面的每一个元素都是String类型,也是Object类型 所以可以匹配.
        
        System.out.println(a1);
        System.out.println(a4);
        
        //Array应用
        System.out.println(Arrays.asList(a1));
        System.out.println(Arrays.asList(a4));

结果为:

[[I@87816d]
[a, b, c]

为什么呢?如果对前面的数组与Object 关系理解的话,这里就清楚了,因为在5.0之前没有可变参数,因此传入的a1 是int[]  这个时候在Object[] 数组接受的时候发现Object[] = a1 这个是不匹配的,因此这个时候它无法处理,于是会走可变参数可变参数的时候,于是会当做一个参数处理,那么就是List集合里面直接添加这个数组了,然后结果显示为[[I@hashCode 了;而String[] 传入的时候,因为Object[] = String[] 是匹配的于是会走5.0前的方式,于是会拆分,那么List集合中就添加了3个元素,所以就显示出了上述的结果。

那么现在有一个需求:

写一个方法,传递一个参数,判断这个参数是一个数组,还是一个普通类型,如果是普通类型,我就直接把他打印出来。如果是数组,我们就遍历后把值打印出来。(好处,就是不用每次都去for循环,只需要调用这个方法即可,公共性)

 

private static void printObject(Object obj)
    {
        Class clazz = obj.getClass();
        if(clazz.isArray())
        {
            //如果是数组,利用数组的反射类Array类来进行相关操作
            int len = Array.getLength(obj);
            for(int i=0;i<len;i++){
                System.out.println(Array.get(obj, i));
            }
        }
        else
        {
            System.out.println(obj);
        }
        
    }

 

即完成了之前的遗留问题.

到此基本的反射应用总结到这里,最后附上全代码。

package study.javaenhance;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

import study.javaenhance.util.ReflectPoint;

public class ReflectTest
{
    public static void main(String[] args) throws Exception 
    {
        String str = "abc";
        Class clz1 = String.class;
        Class clz2 = str.getClass();
        Class clz3 = Class.forName("java.lang.String");
        System.out.println(clz1 ==  clz2);
        System.out.println(clz1 ==  clz3);
        getConstructor();
        
        
        //成员变量的反射
        //1.首先我们先初始化一下ReflectPoint类的xy值,以便反射的时候获取其值
        ReflectPoint rp = new ReflectPoint(2,4);
        //2.要反射,后续加载类
        Field xField = rp.getClass().getDeclaredField("x");
        xField.setAccessible(true);
        //3.解析类
        System.out.println(xField.get(rp));
        Field yField = ReflectPoint.class.getField("y");
        System.out.println(yField.get(rp));
        
        //案例:changeStringValue
        System.out.println(rp);
        changeStringValue(rp);
        System.out.println(rp);
        
        //方法的反射
        Method methodCharAt = String.class.getMethod("charAt", int.class);
        System.out.println(methodCharAt.invoke(str, 1));
        //兼容JDK5.0之前的写法
        System.out.println(methodCharAt.invoke(str, new Object[]{2}));
        
        //调用main方法,不采用TestArguments.main(new String[]{"1","2","3"}); 这种方式,因为有些场景我们的类是传入的是你运行期才知道的
        Class clazz = Class.forName("study.javaenhance.TestArguments");
        Method method =  clazz.getMethod("main",String[].class);
        //因为main方法是静态方法,所以第一个参数传入的为null
        //method.invoke(null,new String[]{"1","2","3"});
        //这样拆开后就是main方法想要的参数了
        method.invoke(null,new Object[]{new String[]{"1","2","3"}});
        //这样就是一个对象传入了,不需要拆了
        method.invoke(null,(Object)new String[]{"1","2","3"});
        
        //数组与Object的关系
        int [] a1 = new int[]{1,2,3};
        int [] a2 = new int[4];
        int[][] a3 = new int[2][3];
        String [] a4 = new String[]{"a","b","c"};
        
        System.out.println(a1.getClass() == a2.getClass());//true
        //System.out.println(a1.getClass() == a4.getClass());//false
        //System.out.println(a1.getClass() == a3.getClass());//false
        System.out.println(a1.getClass().getName());//[I
        System.out.println(a1.getClass().getSuperclass().getName());//java.lang.Object
        System.out.println(a4.getClass().getSuperclass().getName());//java.lang.Object
        
        Object aObj1 = a1; //因为a1 是一个int[] 类型的数组所以是object类型
        Object aObj2 = a4;//因为a4 是一个String[]类型的数组所以是Object类型
        //Object[] aObj3 = a1;// 报错,因为a1 是一个int[]数组里面的元素都是int型,不是Object类型所以不可以如果改成包装类就可以
        Object[] aObj4 = a3; //a3 是一个二维数组,那么里面的一维数组就是一个Object类型,所以可以匹配Object[]代表里面每个元素类型为Object
        Object[] aObj5 = a4; //String[] 里面的每一个元素都是String类型,也是Object类型 所以可以匹配.
        
        System.out.println(a1);
        System.out.println(a4);
        
        //Array应用
        System.out.println(Arrays.asList(a1));
        System.out.println(Arrays.asList(a4));    
        
        //写一个方法,传递一个参数,判断这个参数是一个数组,还是一个普通类型,如果是普通类型,我就直接把他打印出来。如果是数组,我们就遍历后把值打印出来
        printObject(a1);
        
        printObject(a4);
        
        printObject("xyz");
        
    }

    
    private static void printObject(Object obj)
    {
        Class clazz = obj.getClass();
        if(clazz.isArray())
        {
            //如果是数组,利用数组的反射类Array类来进行相关操作
            int len = Array.getLength(obj);
            for(int i=0;i<len;i++){
                System.out.println(Array.get(obj, i));
            }
        }
        else
        {
            System.out.println(obj);
        }
        
    }


    private static void changeStringValue(Object rp) throws Exception
    {
        //思路:1.反射获取要所有的成员变量  2.获取成员变量的类型判断是否为String类型 3.如果是则获取其值 4.替换 5.回填
        Field[] fields = rp.getClass().getFields();
        for (Field field : fields) {
            if(field.getType() == String.class)
            {
                String oldValue = (String) field.get(rp);
                String newValue = oldValue.replace("b", "a");
                field.set(rp, newValue);
            }
        }
    }

    /**
     * 目前String 类型中有很多构造方法,我想要找到获取构造方法为StringBuffer类型的一个
     * @throws Exception 
     * @throws SecurityException 
     */
    public static void getConstructor() throws SecurityException, Exception
    {
        String.class.getConstructor(StringBuffer.class);
        //那么上面的获取到构造方法有什么用呢?
        /**
         * 通常,我们创建String 对象为 String abc = new String(new StringBuffer("abc")) 方式,
         * 下面我们通过反射来实现:
         *  1.加载类
         *  2.解析类
         *  
         */
        Constructor constructor = String.class.getConstructor(StringBuffer.class);
        String str =  (String) constructor.newInstance(new StringBuffer("abc"));
        System.out.println(str);
    }

}


class TestArguments{
    public static void main(String[] args){
        for(String arg : args){
            System.out.println(arg);
        }
    }
}

 

 

参考资料:

张孝祥Java基础增强

posted on 2017-10-13 22:02  pony1223  阅读(1729)  评论(0编辑  收藏  举报

导航