• Java 反射学习笔记


    要学反射,先要了解Class这个类,Class是所有Java类的一个总称,Class的实例中存储的是一个类的字节码,获取Class的实例有三种方式:

    1. System.class
    2. new Date().getClass()
    3. Class.forName(“java.lang.String”);

    Java不允许使用Class cla = new Class()这种方式获得一个Class的新实例,因为Class的构造方法是私有的,看一段源代码:

    这里写图片描述

    这段源码里说的很明白,只有JVM才可以创建一个Class对象。

    那么这三种获取Class实例的方式有什么区别呢?

        @Test
        public void test1() throws ClassNotFoundException{
            String str = "abc";
            Class cla1 = str.getClass();
            Class cla2 = String.class;
            Class cla3 = Class.forName("java.lang.String");
            System.out.println(cla1==cla2);
            System.out.println(cla2==cla3);
        }

    输出结果为:
    这里写图片描述

    根据这结果我们可以推论出,用这三种方式获得的Class实例是一模一样的,但是在实际的开发中我们更多的是使用第三种方式来获得一个Class实例,比如spring框架,我们先在配置文件中写好类名,然后在程序运行的过程中动态加载,获得该类的实例,再执行方法(spring的工作原理基本就这样)。

    System.out.println(String.class.isPrimitive());//false
    System.out.println(int.class.isPrimitive());//true,判断是否为基本类型
    System.out.println(int.class==Integer.class);//false,Integer是类,而int是基本类型
    System.out.println(int[].class.isArray());//true

    那么反射是什么?这是别人总结的“反射就是把Java类中的各种成分映射成相应的Java类(比如 属性–>Field、方法–>Method、构造方法–>Contructor、包–>Package)”。拿到这些相应的Java类之后该怎么用?这是反射学习的重点。

    1.Constructor类
    1.1 如果想调用一个类的默认无参构造方法,有以下两种方式:
    方式一,直接实例化一个Class(这种方式只能调用无参构造方法):

        @Test
        public void test2(){
            try {
                Class c = Class.forName("lenve.test.Utils");
                c.newInstance();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    输出结果:
    这里写图片描述

    Utils.java

    package lenve.test;
    
    public class Utils {
    
        private String username;
        private String password;
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public int add(int a, int b) {
            return a + b;
        }
    
        public Utils(String str) {
            System.out.println(str);
        }
    
        public Utils(int a, int b) {
            System.out.println(a + b);
        }
    
        public Utils() {
            System.out.println("this is default constructor!");
        }
    
    }

    方式二,先获得一个Constructor类,再根据这个类调用无参构造方法。

        @Test
        public void test3(){
            try {
            /**
            Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数;
            Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。 
    
                */
                Class c = Class.forName("lenve.test.Utils");
                Constructor constructor = c.getConstructor(null);
                Utils util = (Utils) constructor.newInstance();
                System.out.println(util.add(3, 4));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }

    输出结果:

    这里写图片描述

    1.2 调用一个有参的构造方法

        @Test
        public void test4(){
            try {
                Class c = Class.forName("lenve.test.Utils");
                //根据参数的类型来确定调用的是哪一个构造方法
                Constructor constructor = c.getConstructor(String.class);
                //传入该构造方法需要的参数
                constructor.newInstance("today is a good day!");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }

    输出结果:

    这里写图片描述

    2.获得Field并查看相应的实例对象的值,细节都已经在注释中说明:

        @Test
        public void test5(){
            try {
                Point p1 = new Point(3, 7);
                Field fY = p1.getClass().getField("y");
                //fY是字节码中Field对象的一个实例,并不属于某个具体的实例,因此它的值不是7
                //这样才是得到p1中y的值
                System.out.println(fY.get(p1));
                //因为x是私有的,所以不能通过下面的方式获得
    //          Field fX = p1.getClass().getField("x");
                //正确的获得方式应该是这样的
                Field fX = p1.getClass().getDeclaredField("x");
                //上面的方式拿到x后并不能获得其值,还需要做如下处理
                fX.setAccessible(true);
                System.out.println(fX.get(p1));
            } catch (NoSuchFieldException | SecurityException
                    | IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    3.获取一个类中的所有String类型的属性,如果该字段的值中有’a’,则全部替换为’b’:

        @Test
        public void test6(){
            Point p = new Point(3, 4);
            changeValue(p);
            System.out.println(p);
        }
    
        private void changeValue(Object obj) {
            try {
                Field[] fields = obj.getClass().getFields();
                for(Field field:fields){
                    //因为对一个Java类来说,它只有一个字节码,是单例的,所以用==比较就可以了
                    if(field.getType()==String.class){
                        String oldStr = (String) field.get(obj);
                        String newStr = oldStr.replace('a', 'b');
                        field.set(obj, newStr);
                    }
                }
            } catch (SecurityException | IllegalArgumentException
                    | IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    Point.java

    public class Point {
    
        private int x;
        public int y;
        public String username = "zhangsan";
        public String password = "lisi";
        public Point(int x,int y) {
            this.x = x;
            this.y = y;
        }
        @Override
        public String toString() {
            return "Point [username=" + username + ", password=" + password + "]";
        }
    }

    4.通过反射调用方法

        @Test
        public void test7() {
            try {
                Class c = Class.forName("lenve.test.Utils");
                //通过方法名以及参数类型、参数个数来确定具体是得到那个方法
                Method m = c.getMethod("add", int.class, int.class);
                /**
                 * 第一个参数表示方法是在哪个实例中调用,后面的表示该方法的参数,
                 * 如果该方法是静态方法,则第一个参数可以为null
                 */
                System.out.println(m.invoke(c.newInstance(), 3, 4));
            } catch (ClassNotFoundException | NoSuchMethodException
                    | SecurityException | IllegalAccessException
                    | IllegalArgumentException | InvocationTargetException
                    | InstantiationException e) {
                e.printStackTrace();
            }
        }

    5.通过反射调用一个方法的main(String[] args)方法:

        @Test
        public void test8(){
            try {
                Method m = Class.forName("lenve.test.ReflectMain").getMethod("main", String[].class);
                /**
                 * 由于编译器会将传入的参数拆包,因此要设法使参数以一个数据类型出现
                 * 下面一共有两种调用方式
                 * 第一种:告诉编译器不要拆包
                 * 第二种:再包一层,这样即使拆包后还是一个参数
                 */
                m.invoke(null, (Object)new String[]{"111","222","333"});
                System.out.println("---------------------");
                m.invoke(null, new Object[]{new String[]{"111","222","333"}});
            } catch (NoSuchMethodException | SecurityException
                    | ClassNotFoundException | IllegalAccessException
                    | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }

    ReflectMain.java

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

    6.使用反射类打印一个普通对象或者数组对象:

        @Test
        public void test10(){
            int[] a1 = new int[]{1,2,3};
            String[] a2 = new String[]{"a","b","c"};
            Object obj = null;
            printObject(a1);
            printObject(a2);
            printObject("xyz");
        }
    
        private void printObject(Object obj) {
            Class c = obj.getClass();
            if(c.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);
            }
    
        }

    7.利用反射实现一个间的spring框架

    方式一:

        //配置文件为config.properties
        @Test
        public void test1(){
            try {
                InputStream is = new FileInputStream(new File("config.properties"));
                Properties prop = new Properties();
                prop.load(is);
                is.close();
                String className = prop.getProperty("className");
                Class c = Class.forName(className);
                Method m = c.getMethod("add", int.class,int.class);
                System.out.println(m.invoke(c.newInstance(), 3,4));
            } catch (ClassNotFoundException | NoSuchMethodException
                    | SecurityException | IllegalAccessException
                    | IllegalArgumentException | InvocationTargetException
                    | InstantiationException | IOException e) {
                e.printStackTrace();
            }
        }

    config.properties

    className=lenve.test.Utils

    方式二:

        //配置文件为config.xml
        @Test
        public void test2(){
                try {
                    SAXReader reader = new SAXReader();
                    Document document = reader.read(new File("config.xml"));
                    Element root = document.getRootElement();
                    List<Element> elementList = root.elements();
                    for(Element es:elementList){
                        String className = es.element("name").getText();
                        Class c = Class.forName(className);
                        System.out.println(c.newInstance());
                    }
                } catch (ClassNotFoundException | InstantiationException
                        | IllegalAccessException | DocumentException e) {
                    e.printStackTrace();
                }
        }

    config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans>
        <bean>
            <name>lenve.test.Utils</name>
        </bean>
        <bean>
            <name>java.util.Date</name>
        </bean>
    </beans>

    8.使用classLoader加载一个配置文件:

        //配置文件为config.properties
        @Test
        public void test1(){
            try {
                //把config.properties文件放入工程目录下
    //          InputStream is = new FileInputStream(new File("config.properties"));
                //使用classLoader获得配置文件,注意文件路径
    //          InputStream is = ReflectTest2.class.getClassLoader().getResourceAsStream("lenve/test/config.properties");
                //也可以直接使用class提供的方法获得,这里的路径地址则是相对地址(这里我们把配置文件和java文件放入同一个包中)
    //          InputStream is = ReflectTest2.class.getResourceAsStream("config.properties");
                //如果把配置文件放入lenve.test.resources包中(注意ReflectTest2.java在lenve.test包中)
    //          InputStream is = ReflectTest2.class.getResourceAsStream("resources/config.properties");
                //还可以通过绝对路径来访问
                InputStream is = ReflectTest2.class.getResourceAsStream("/lenve/test/resources/config.properties");
                Properties prop = new Properties();
                prop.load(is);
                is.close();
                String className = prop.getProperty("className");
                Class c = Class.forName(className);
                Method m = c.getMethod("add", int.class,int.class);
                System.out.println(m.invoke(c.newInstance(), 3,4));
            } catch (ClassNotFoundException | NoSuchMethodException
                    | SecurityException | IllegalAccessException
                    | IllegalArgumentException | InvocationTargetException
                    | InstantiationException | IOException e) {
                e.printStackTrace();
            }
        }
  • 相关阅读:
    Linux网卡驱动程序对ethtool的支持和实现
    Linux下samba编译与安装(Ubuntu和嵌入式linux)
    [DM8168]Linux下SPI驱动测试
    Sublime Text 2 中文乱码
    Linux线程优先级
    Linux再谈互斥锁与条件变量
    Makefile编写记录
    Linux大小端模式转换函数
    电脑显卡4种接口类型:VGA、DVI、HDMI、DP
    python __enter__ 与 __exit__的作用,以及与 with 语句的关系
  • 原文地址:https://www.cnblogs.com/lenve/p/4518009.html
Copyright © 2020-2023  润新知