反射与内省 --- 应该知道的常见用法

目录

  • 反射 (Reflect)
    • 反射的原理
    • 使用反射操作属性
    • 使用反射操作构造函数
      • 无参数构造方法
      • 有参数构造方法
    • 使用反射操作方法
    • 实例方法
    • 静态方法
    • Modifier解析修饰符
  • 内省 (Introspector)
    • JavaBean 的规范
    • MethodUtils
    • ConstructorUtils
    • PropertyUtils
    • BeanUtils
    • ConvertUtils
  • 反射和内省的区别

反射 (Reflect)

在框架开发中,都是基于配置文件开发的,在配置文件中配置了类,可以通过读取配置文件中的类名,然后通过反射得到类中的所有内容,或是让类中的某个方法来执行。

也就是说,反射是在运行时获取一个类的所有信息,可以获取到 .class 的任何定义的信息(包括成员 变量,成员方法,构造器等)可以操纵类的字段、方法、构造器等部分。

反射的原理

  • 得到 class 文件
    • 把 java 文件保存到本地硬盘,得到 .java 文件
    • 编译 java 文件,得到 .class 文件
    • JVM 把 .class 文件加载到内存中,class 文件在内存中使用 Class 类表示
  • 通过 class 文件得到 Class 类,可以通过以下 3 种方式获得 Class 类
    • 通过成员变量获得: 类名.class
    • 通过具体对象获得: 对象.getClass()
    • 通过 Class 的静态方法获取: Class.forName("classFilePath")
  • 通过 Class 类获取 class 文件中的内容,包括:成员变量,构造方法,普通方法,它们都可以用相应的类表示:
    • 成员方法:Field
    • 构造方法:Constructor
    • 普通方法:Method

使用反射操作属性

public void test3() {
    try {
        Class c2 = Class.forName("cn.itcast.test09.Person");  // 得到Class类
        Person p11 = (Person) c2.newInstance();  // 得到Person类的对象,返回
        Field[] fields = c2.getDeclaredFields();  // 得到所有的属性,返回一个Field数组
        Field f1 = c2.getDeclaredField("name");  // 通过属性名称得到特定属性,参数是属性的名称

        // 如果操作的是私有的属性,不让操作,可以通过setAccessible(true)操作私有属性
        f1.setAccessible(true);
        f1.set(p11, "wangwu"); // 设置name值,相当于p.name = "wangwu";
        System.out.println(f1.get(p11)); // 相当于 p.name
    }catch(Exception e) {
        e.printStackTrace();
    }
}

使用反射操作构造函数(创建类的实例)

无参数构造方法

通过 Class 对象的 newInstance() 方法创建。

public void test1() throws Exception {
    Class c3 = Class.forName("cn.itcast.test09.Person");
    // 无参数的构造方法就是直接使用newInstance()方法
    Person p = (Person) c3.newInstance();
    p.setName("zhangsan");
    System.out.println(p.getName());
}

有参数构造方法

不能再通过 Class 对象的 newInstance() 方法创建了,要先得到要调用的构造函数的 Consturctor 对象,然后通过 Constructor 对象的 newInstance() 方法创建。

public void test2() throws Exception {
    Class c1 = Class.forName("cn.itcast.test09.Person");

    // 获取所有的构造方法
    Constructor[] css = c1.getConstructors();
    // 获取特定的构造方法:传递是有参数的构造方法里面参数类型,类型使用class的形式传递
    Constructor cs = c1.getConstructor(String.class, String.class);

    // 通过有参数的构造方法创建Person实例,而不是通过Class的对象
    Person p1 = (Person) cs.newInstance("lisi","100");
    System.out.println(p1.getId()+" "+p1.getName());
}

使用反射操作方法

实例方法

public void test4() throws Exception {
    Class c4 = Class.forName("cn.itcast.test09.Person");
    Person p4 = (Person) c4.newInstance();

    // 得到所有的普通方法
    Method[] mds = c4.getDeclaredMethods();
    // 得到特定的普通方法,传递两个参数:第一个参数:方法名称;第二个参数:方法里面参数的类型
    Method m1 = c4.getDeclaredMethod("setName", String.class);

    // 使用invoke执行方法,传递两个参数:第一个参数:person实例;第二个参数:设置的值
    // 在这里要传入person对象的原因是:我们需要知道到底是哪一个对象的setName方法执行了
    // 如果要操作的是私有的方法 ,需要 m1.setAccessible(true);
    m1.invoke(p4, "niuqi");
    System.out.println(p4.getName());
}

静态方法

静态方法调用方式是 类名.方法名,不需要类的实例,所以使用反射操作静态方法的时候,也是不需要实例的,在 invoke 方法的第一个参数传入 null 即可: m1.invoke(null, "niuqi");
例如,我们利用反射操作一个主方法 main 方法(在学习《深入理解Java虚拟机》时遇到了这个用法:

Class clazz = ...
try {
    Method method = clazz.getMethod("main", new Class[] { String[].class});
    method.invoke(null, new String[] {null});
} catch (Throwable e){
    e.printStackTrace();
}

Modifier解析修饰符

利用反射我们可以获得类的属性、构造器、方法等,那么如何判断类或变量、方法的修饰符呢?
Java针对类、成员变量、方法,有很多修饰符,例如public、private、static、final、synchronized、abstract等,这些修饰符用来控制访问权限或其他特性。

通过查看源码,我们发现,FieldMethodConstructor 都直接或间接实现了 Member 接口,看下Member接口:Member 表示一个类中的成员,包括成员变量、方法、构造方法三种实现。
Java 文档:

Interface Member
所有已知实现类:
Constructor , Executable , Field , 方法
Member是一个反映关于单个成员(字段或方法)或构造函数的标识信息的接口。

Member接口有个方法:

int getModifiers() 作为整数返回由此 Member所表示的成员或构造方法的 Java语言修饰符。

我们想要得到的应该是一个表示修饰符的字符串,却得到的是一个整数,那么一个整数表示怎样的修饰符或者这个整数怎么转化为修饰符呢?

在这里,需要用到java.lang.reflect.Modifier这个类。Modifier提供了很多静态方法。

static String toString(int mod) 返回描述指定修饰符中的访问修饰符标志的字符串。
static boolean isPrivate(int mod) 如果整数参数包含 private修饰符,则返回 true , false返回false。
static boolean isProtected(int mod) 如果整数参数包含 protected修饰符,则返回 true , false false。
static boolean isPublic(int mod) 如果整数参数包含 public修饰符,则返回 true , false false。
static boolean isStatic(int mod) 如果整数参数包含 static修饰符,则返回 true , false false。

如 public static String toString(int mod) 就可以输出该整数对应的所有的修饰符。
public static boolean isPublic(int mod) 就可以判断该整数对应的是不是包含public修饰符。

我们再来看下 Modifier 源码:

    public static final int PUBLIC           = 0x00000001;
    public static final int PRIVATE          = 0x00000002;
    public static final int PROTECTED        = 0x00000004;
    public static final int STATIC           = 0x00000008;
    public static final int FINAL            = 0x00000010;
    public static final int SYNCHRONIZED     = 0x00000020;
    public static final int VOLATILE         = 0x00000040;
    public static final int TRANSIENT        = 0x00000080;
    public static final int NATIVE           = 0x00000100;
    public static final int INTERFACE        = 0x00000200;
    public static final int ABSTRACT         = 0x00000400;
    public static final int STRICT           = 0x00000800;
    static final int BRIDGE    = 0x00000040;
    static final int VARARGS   = 0x00000080;
    static final int SYNTHETIC = 0x00001000;
    static final int ANNOTATION  = 0x00002000;
    static final int ENUM      = 0x00004000;
    static final int MANDATED  = 0x00008000;

把它们转换成二进制,可以看出,其实 Modifier 使用一个二进制的位来表示是否包含某个修饰符。
而接口 Member 中有个方法,返回的整数就是该成员表示所有修饰符的数字的和的值:

    public int getModifiers();

内省 (Introspector)

内省是基于反射实现的,主要用于操作 JavaBean,相比反射使用起来要方便一些。可以获取 bean 的 getter/setter 方法,也就是说,只要 JavaBean 有 getXxx() 方法,不管这个 Bean 有没有 Xxx 属性,使用内省我们都认为它有。

为了更好的理解,在讲解内省之前,我们先来介绍一下 JavaBean 的规范。

JavaBean 的规范

  • JavaBean是一种特殊的类,主要用于传递数据信息。
  • 必须有一个public的无参数构造器。
  • 属性可以通过get、set、is(可以替代get,用在布尔型属性上)方法或遵循特定命名规范的其他方法访问。
  • 可序列化。
  • 提供 get/set 方法,如果只有 get 方法,那么这个属性是只读属性。
  • 属性:有 get/set 方法的成员,还可以没有成员,只有 get/set 方法。属性名称由 get/set 方法来决定,而不是成员名称。
  • 我的理解:属性是一个逻辑概念,能访问通过符合规则的方法访问到的就是属性!
    实例代码:
/**
 * @author ycw
 */

import java.io.Serializable;

public class UserInfo implements Serializable {     //可序列化

    // 属性名:使用驼峰命名法。
    private String UserId;

    private String userName;

    private boolean isVIP;

    // 有一个public的无参数构造器。
    public UserInfo() {
    }

    public UserInfo(String userId, String userName, boolean isVIP) {
        UserId = userId;
        this.userName = userName;
        this.isVIP = isVIP;
    }

    public UserInfo(String userId, String userName) {
        UserId = userId;
        this.userName = userName;
    }

    public String getUserId() {
        return UserId;
    }

    public void setUserId(String userId) {
        UserId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    // is 可以替代 get,用在布尔型属性上
    public boolean isVIP() {
        return isVIP;
    }

    public void setVIP(boolean VIP) {
        isVIP = VIP;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "UserId='" + UserId + '\'' +
                ", userName='" + userName + '\'' +
                ", isVIP=" + isVIP +
                '}' ;
    }
}

MethodUtils

MethodUtils 通过反射访问对象的方法并且执行方法。

/**
 * @author ycw
 */
public class MethodUtilsTest {
    @Test
    /* MethodUtils通过反射访问对象的方法并且执行方法。*/
    public void test1() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException {
        UserInfo user1 = new UserInfo();
        // 直接通过反射获得类对象,方法,执行
        Class<?> clazz = Class.forName("~.model.UserInfo");
        Method method = clazz.getMethod("setUserName", String.class);
        method.invoke(user1, "令狐冲");
        System.out.println(user1);

        // 通过方法名和参数类型获得可访问方法
        method = MethodUtils.getAccessibleMethod(UserInfo.class, "setUserName", String.class);
        method.invoke(user1, "东方不败");
        System.out.println(user1);

        // 可以直接通过invokeMethod执行方法
        MethodUtils.invokeMethod(user1, "setUserName", "任我行");
        System.out.println(user1);
    }
}

执行结果:

UserInfo{UserId='null', userName='令狐冲', isVIP=false}
UserInfo{UserId='null', userName='东方不败', isVIP=false}
UserInfo{UserId='null', userName='任我行', isVIP=false}

ConstructorUtils

ConstructorUtils 通过反射提供了构造方法相关的便捷操作方法。

/**
 * @author ycw
 */
public class ConstructorUtilsTest {
    @Test
    public void test2() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 直接通过反射获得类对象,构造器,创建实例
        Class<?> clazz = Class.forName("org.simplespring.commonsBeanutilsTest.model.UserInfo");
        Constructor<?> constructor1 = clazz.getConstructor(String.class, String.class, boolean.class);
        Object user1 = constructor1.newInstance("独孤九剑弟子1号", "令狐冲", true);
        System.out.println(user1);

        // ConstructorUtils 通过反射提供了构造方法获取的便捷操作方法。
        Constructor<UserInfo> constructor2 = ConstructorUtils.getAccessibleConstructor(UserInfo.class, new Class[]{String.class, String.class, boolean.class});
        UserInfo user2 = constructor2.newInstance("魔教1号", "东方不拜", true);
        System.out.println(user2);

        // ConstructorUtils 创建实例更简洁的方法
        UserInfo user3 = ConstructorUtils.invokeConstructor(UserInfo.class, new String[]{"魔教老1号", "任我行"});
        System.out.println(user3);
    }
}

执行结果:

UserInfo{UserId='独孤九剑弟子1号', userName='令狐冲', isVIP=true}
UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
UserInfo{UserId='魔教老1号', userName='任我行', isVIP=false}

PropertyUtils

PropertyUtils 通过反射提供了对象属性的便捷操作方法。

/**
 * @author ycw
 */
public class PropertyUtilsTest {
    @Test
    public void test3() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {

        UserInfo user1 = new UserInfo("魔教1号", "东方不拜", true);
        UserInfo user2 = new UserInfo("魔教老1号", "任我行");
        System.out.println(user1);
        System.out.println(user2);

        // 使用 PropertyUtils 将一个 bean 实例的所有属性全部拷贝到另一个实例上,注意属性会完全覆盖,使用时应注意!
        PropertyUtils.copyProperties(user2, user1);
        System.out.println(user2);
        
        // 为一个实例设置特定属性值
        PropertyUtils.setProperty(user1, "userName", "东方必败");
        System.out.println(user1);
    }
}

执行结果:

UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
UserInfo{UserId='魔教老1号', userName='任我行', isVIP=false}
UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
UserInfo{UserId='魔教1号', userName='东方必败', isVIP=true}

BeanUtils

BeanUtils 通过反射提供了Bean对象的便捷操作方法。

/**
 * @author ycw
 */
public class BeanUtilsTest {
    @Test
    public void test4() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        // BeanUtils 是一个更为通用的工具,通过反射提供了Bean对象的便捷操作方法。
        UserInfo user1 = new UserInfo("魔教1号", "东方不拜", true);
        UserInfo user2 = new UserInfo("魔教老1号", "任我行");

        // 可以代替 PropertyUtils 提供一些操作属性的方法
        // 复制属性
        BeanUtils.copyProperties(user2, user1);
        System.out.println(user2);
        // 获取属性
        System.out.println(BeanUtils.getProperty(user2, "userName"));
        // 设置属性
        BeanUtils.setProperty(user2, "userName", "任盈盈");
        System.out.println(user2);

        // 封装 Map 数据到 JavaBean 对象中
        UserInfo user3 = new UserInfo();
        Map<String, String> properties = new HashMap<>();
        properties.put("userId", "华山掌门");
        properties.put("userName", "岳不群");
        BeanUtils.populate(user3, properties);
        System.out.println(user3);
    }
}

执行结果:

UserInfo{UserId='魔教1号', userName='东方不拜', isVIP=true}
东方不拜
UserInfo{UserId='魔教1号', userName='任盈盈', isVIP=true}
UserInfo{UserId='华山掌门', userName='岳不群', isVIP=false}

ConvertUtils

ConvertUtils 提供了数据类型相互转换的方法。
最基本的 convert() 方法将一个字符串转化为特定类型的实例对象。

public static Object convert(String value, Class<?> clazz) {
        return ConvertUtilsBean.getInstance().convert(value, clazz);
    }

示例:

/**
 * @author ycw
 */
public class ConvertUtilsTest {
    @Test
    public void test5() {
        // 将一个字符串转化为特定类型的实例对象。
        String numStr = "123";
        int num = (int) ConvertUtils.convert(numStr, Integer.class);
        System.out.println(num);
    }
}

执行结果:

123

反射和内省的区别

反射就像给类照镜子,这个的所有信息会毫无保留的反射到镜子中,将这个类的所有信息照出来,能照出来就是有,照不出来就是没有,得到的东西都是客观真实存在的。

而内省的目的是找出 bean 的 getter 和 setter 以便操作这个 bean,所以只要看到有 getter 或者 setter 就认为这个类有那么一个字段,比如看到 getName() 内省就会认为这个类中有 name 字段,但事实上并不一定会有 name。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容