• 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基础增强

  • 相关阅读:
    OCP-1Z0-051-V9.02-162题
    OCP-1Z0-051-V9.02-161题
    OCP-1Z0-051-V9.02-160题
    Matlab中矩阵的分解
    OCP-1Z0-051-V9.02-158题
    OCP-1Z0-051-V9.02-157题
    Matlab中特殊的矩阵函数
    求Matlab中矩阵的秩和迹
    Matlab中的条件数
    在android里使用boost c++
  • 原文地址:https://www.cnblogs.com/pony1223/p/7659210.html
Copyright © 2020-2023  润新知