Java反射:如何正确理解,不如手写一个(反射包分析、楼主亲测) - Go语言中文社区

Java反射:如何正确理解,不如手写一个(反射包分析、楼主亲测)


Java反射机制、动态代理是基于什么原理?

这个问题可谓是老生常谈的一个热门问题了,如果没有深入的思考还真的是很难回到上来。那么今天我们一起来看看,如何正确清晰的认识这个热门却又说简单又不简单说复杂又比较复杂的问题。

一、什么是反射

反射机制是Java语言提供的一种基础功能

这个功能能够赋予程序在运行时进行自省的能力。

通过反射我们可以直接操作类或者对象,比如:获取某个对象的类的定义、获取类声明的属性和方法、调用方法或者构造对象,甚至可以运行时修改类的定义。

二、认识反射的包

1、反射包

Java为我们准备一个专门的反射包java.lang.reflect

这里写图片描述

在这个包中中有3个类,FieldMethodConstructor分别作用于类的域(成员变量)、方法和构造器。

2、AccessibleObject

关于反射有一点需要特意注意一下,就是反射包提供的AccessibleObject类中的setAccessible方法。

这里写图片描述

他的子类很多也重写了这个方法,这里的所谓的setAccessible顾名思义,就是指的是成员变量前面的用于修饰的publicprotectedprivate,这个方法也就意味着我们可以在运行时通过反射去修改类的成员变量的访问限制。

setAccessible的应用场景非常广泛,各种框架:开发、测试、依赖注入。例如在数据库O/R Mapping框架中,我们在加载或者持久化数据的时候,框架通常会利用反射做这个事情,而不需要开发者自己去实现。

还有个典型的应用场景,就是绕过API的访问控制。我们在开发的过程中,有时候,可能需要调用内部的API去做一些事情,比如,自定义的高性能的NIO框架需要显示的释放DirectBuffer,使用反射绕开限制是一种常见的办法。

那么具体怎么实现呢?在后面的代码示例中会有说明。

3、Modifier

反射包中还有一个重要的类Modifier,该类是静态类,其中的方法也是静态方法。
Modifier.Class类中getModifiers()函数返回一个用于描述类,构造器,方法和域的修饰符的整形数值。、
调用Modifier.toString()方法将整型数值转变成字符串,也是就我们熟悉的public,private,static,final等修饰符。

三、反射包的详解与代码实践

package com.newframe.controllers.api;

import com.newframe.entity.test.TestUser;

import java.lang.reflect.*;

/**
 * @author:wangdong
 * @description:反射
 */

/**
 * 创建一个user类
 * 可不在与main方法同一个类中创建
 */
class User{

    //定一个成员变量:姓名
    private String name;

    //无参构造
    public User(){}
    //有参构造
    public User(String name) {
        this.name = name;
    }
    //get方法
    public String getName() {
        return name;
    }
    //set方法
    public void setName(String name) {
        this.name = name;
    }
}

/**
 * 测试Class类的函数和反射库中的函数
 */
public class TestReflectController {

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

        //获得User类的Class对象
        Class<?> cc = User.class;

        //下面这个是通过new出来的对象,并通过set给成员变量赋值
        User user1 = new User();
        user1.setName("hello");
        //下面这个是通过构造器的方式

        //1.有参构造器
        //先得到有参构造器的信息,再根据构造器的信息,由newInstance()函数创建一个User对象
        Constructor<?> constructor = cc.getConstructor(String.class);
        //这个constructor.newInstance方法就是反射包下java.lang.reflect.Constructor类下的newInstance方法
        User user2 = (User) constructor.newInstance("world");
        System.out.println(user1.getName());//hello
        System.out.println(user2.getName());//world

        //2.无参构造器
        Constructor<?> constructor1 = cc.getConstructor();
        //无参构造不能够像1一样赋值
        //User user3 = (User) constructor1.newInstance("熊本");
        User user3 = (User) constructor1.newInstance();
        user3.setName("熊本");
        System.out.println(user3.getName());//熊本

        //由无参构造器创建对象时,可不必获得构造器,直接由Class对象调用newInstance()方法。
        Class<?> cc2 = User.class;
        User user4 = (User) cc2.newInstance();
        user4.setName("同学");
        System.out.println(user4.getName());//同学

        //3.下面2个输出语句可看出cc保存类信息,输出的是“class + 类名”。cc.newInstance()是具体类的对象。
        System.out.println(cc);//class com.newframe.controllers.api.User
        System.out.println(cc.newInstance());//com.newframe.controllers.api.User@3a03464


        //4.AccessibleObject、Field类
        //首先得到有参构造函数的信息,然后根据构造函数实例化一个对象。
        //由getDeclaredField()函数得到类里面的私有成员变量,访问私有成员变量要用setAccessible()函数设置访问权限。
        //Field类对象得到成员变量后还可以设置该变量的值,使用set()方法。
        Constructor<?> constructor2 = cc.getConstructor(String.class);
        User user5 = (User) constructor2.newInstance("改变前:100");
        Field field = cc.getDeclaredField("name");
        field.setAccessible(true);
        field.set(user5,"改变后:50");
        System.out.println(user5.getName());//改变后:50

        //5.Method.invoke
        //首先根据获得的构造函数信息实例化一个对象
        //然后由函数名获得类中的公有函数,getMethod("函数名")
        //invoke()方法执行由getMethod()获得的函数,这里获得的函数是getter()
        //对于获得的有参函数,invoke(对象)里只添加对象名。
        Constructor<?> constructor3 = cc.getConstructor(String.class);
        User user6 = (User) constructor3.newInstance("你好世界");
        //getName是User实体类中的方法
        System.out.println(cc.getMethod("getName").invoke(user6));//你好世界

        //对于获得到的无参函数,在调用getMethod()函数时,要在getMethod()中指定被获得函数的"函数名"和"参数类型"
        //并且在执行该函数(即调用invoke()函数时),要指定对象和参数类型的具体实例。
        User user7 = (User) cc.newInstance();
        Method method = cc.getMethod("setName", String.class);
        method.invoke(user7,"你好哇,这个世界");
        System.out.println(user7.getName());//你好哇,这个世界

        //6.Modifier类
        //Class类中getModifiers()函数返回一个用于描述类,构造器,方法和域的修饰符的整形数值。、
        //调用Modifier.toString()方法将整型数值转变成字符串,也是就我们熟悉的public,private,static,final等修饰符。
        System.out.println(Modifier.toString(cc.getModifiers()));//无
        System.out.println(Modifier.toString(constructor.getModifiers()));//public
        System.out.println(Modifier.toString(field.getModifiers()));//private
        System.out.println(Modifier.toString(method.getModifiers()));//public

        //同时,Modifier类还有一些判断修饰符是不是某一类型的方法。
        System.out.println(Modifier.isPublic(cc.getModifiers()));
        System.out.println(Modifier.isPublic(constructor.getModifiers()));

    }
}

四、通过反射调用其他类的方法

下面以在另一个类的main中调用其他类的main方法。

首先要有一个被调用的类:TestPrint
package com.newframe.controllers.api;

/**
 * @author:wangdong
 * @description:反射测试的打印类
 */
public class TestPrint {

    public static void main(String[] args) {
        for (String arg: args) {
            System.out.println(arg);
        }
    }
}
需要有一个调用类去调用TestPrint类中的主方法
package com.newframe.controllers.api;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author:wangdong
 * @description:测试反射调用其他类的方法
 */
public class TestReflectInvoke {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //传统的方式调用
        TestPrint.main(new String[]{"hello","world"});//输出hello world

        //反射调用,路径为TestPrint的包路径
        Class<?> cc = Class.forName("com.newframe.controllers.api.TestPrint");
        Method method = cc.getMethod("main", String[].class);
        //这里需要用Object强转,实例化字符串数组对象时要在前面加(Object),不然会报参数个数不匹配的错误。
        method.invoke(null,(Object) new String[]{"你好","世界"});
    }
}

五、数组的反射

因为Array数组属于反射包java.lang.reflect.Array;

package com.newframe.controllers.api;

import java.lang.reflect.Array;

/**
 * @author:wangdong
 * @description:测试数组的反射
 */
public class TestReflectArray {

    public static void main(String[] args) throws ClassNotFoundException {
        //新建4个不同的数组
        //一维数组
        int[] a = new int[3];
        int[] b = new int[4];
        //二维数组
        int[][] c = new int[3][4];
        //String类型的数组
        String[] d = new String[3];

        //可以对比各个数组所属的类是否相同
        System.out.println(a.getClass() == b.getClass());//true
        //System.out.println(a.getClass() == c.getClass());不同类型的数组不同比较
        System.out.println(a.length == c.length);//但是可以比较长度,true
        System.out.println(a.getClass().getName());//[I
        System.out.println(d.getClass().getName());//[Ljava.lang.String;
        System.out.println(a.getClass().getSuperclass().getName());//java.lang.Object

        //利用反射生成一个数组
        int[] e = (int[]) Array.newInstance(int.class,3);
        //0为数组索引下标,1为数字值
        Array.set(e,0,1);
        Array.set(e,1,2);
        Array.set(e,2,3);
        System.out.println(Array.get(e,0));
        System.out.println(Array.get(e, 1));
        System.out.println(Array.get(e, 2));

        //利用反射获取数组类型常用的一些方式
        Class<?> cc = String[].class;
        Class<?> cc1 = Class.forName("[I");
        Class<?> cc2 = Class.forName("[Ljava.lang.String;");

        System.out.println(cc);
        System.out.println(cc1);
        System.out.println(cc2);

        //通过已有的对象获取数组的类型
        Class<?> cc3 = e.getClass();
        System.out.println(cc3);
        //根据类型信息获取数组内成员的类型
        Class<?> cc4 = cc3.getComponentType();
        System.out.println(cc4);//int
    }
}

六、友情索引

在知乎上有关于反射的讨论,可以参考一下:知乎反射

做技术,最重要的还是动手啊,很多概念,类似于反射啊、动态代理啊。
你不动手,你光看,肯定是不会深入理解的。
你动手敲一边代码,看看源码,立刻理解完全就不一样了啊。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/wd2014610/article/details/82592448
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-02-13 14:53:04
  • 阅读 ( 1372 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢