再谈Java反射

2018-02-13  本文已影响26人  sugaryaruan

在日常开发中尽量不要用反射,如果需要,先考虑通过复制原始类的形式来避免反射,还不行再考虑通过反射。

反射能帮我们做什么?

  1. 反射构建出无法直接访问的类
  2. set或get到无法访问的类变量
  3. 调用不可访问的方法

每个包装器原始数据类型类具有名为 TYPE 的静态字段
int.class 和 Integer.TYPE 指的是同一个类对象。

上一篇我在Java反射札记里说了如何反射一个类,代参实例对象,反射属性和方法。这次我们来看一个开源类库

jOOR 传送门 (https://github.com/jOOQ/jOOR)

我们一起学习下这个库的代码

这个开源库适配了Java6,Java8 和2017年11月份已经发布的Java9,作者在开发这个类库使,借鉴了已有的项目,做了调研和测试。

github上给出的Demo代码段:

String world = on("java.lang.String")  // Like Class.forName()
                .create("Hello World") // Call most specific matching constructor
                .call("substring", 6)  // Call most specific matching substring() method
                .call("toString")      // Call toString()
                .get();  

上述代码采用链式调用,on方法 create方法,call方法内部都是调用的on的重载方法,返回Reflection类,最后的get()内部采用了泛型返回,如下:

/**
 * Get the wrapped object
 *
 * @param <T> A convenience generic parameter for automatic unsafe casting
 */
@SuppressWarnings("unchecked")
public <T> T get() {
    return (T) object;
}

关于泛型的使用,可以查阅之前的两篇:

  1. 重识Java泛型 上
  2. 重识Java泛型 下

在Android项目中,findViewById经过这样封装后,再也不用强转类型了。

类加载机制

JVM里java的类的加载流程图

这部分内容已在:Java反射札记 这篇讲解了,这里不重复了

Loading -> Linking -> Initialization

类一定会初始化的五大情况

  1. 使用new字节码指令创建类的实例,或者使用getstatic、putstatic读取或设置一个静态字段的值(放入常量池中的常量除外),或者调用一个静态方法的时候,对应类必须进行过初始化。

  2. 通过java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则要首先进行初始化。

  3. 当初始化一个类的时候,如果发现其父类没有进行过初始化,则首先触发父类初始化。

  4. 当虚拟机启动时,用户需要指定一个主类(包含main()方法的类),虚拟机会首先初始化这个类。

  5. 使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发其初始化。

关于第五点,没有接触过

现在通过代码实践来试试看吧

我定义了SuperClass类和SubClass类

SuperClass类

public class SuperClass {

    public static String sValue = "666";

    public static final String HELLO_WORLD = "hello world!";

    static {
        Out.println("SuperClass init");
    }

    public String getContent(){
        return "This is String result";
    }

    public static void setsValue(String value){
        sValue = value;
    }
}

SubClass

public class SubClass extends SuperClass {

    public static String sTemp = "";

    static {
        sTemp = "sub class temp";
        Out.println("Subclass init");
    }

    public void instanceTest(){
        Out.println("instanceTest");
    }
}

测试下第一点,new的时候

/**
 * 类初始化
 */
private static void testVersion1_2() {
    SubClass subClass = new SubClass();
}

输出:

SuperClass init
Subclass init

子类SubClass和SuperClass都经历了类初始化阶段

测试下第一点里提到的引用静态字段时

/**
 * 类初始化3
 */
private static void testVersion1_3() {
    Out.println(SubClass.sTemp);
}

输出:

SuperClass init
Subclass init
sub class temp

再看下这段代码

/**
 * 类初始化1 主动引用/被动引用
 */
private static void testVersion1_1() {
    Out.println(SubClass.sValue);
}

你觉得SubClass类会调用static静态代码块么?

输出:

SuperClass init
666

答案是不会,神奇的一幕,SubClass没有经过类初始化

Java“相等”判定相关方法

我们最熟悉的是instanceof方法,这个是实例对象的方法,判断当前对象是否是某个类的实例。

如果我要比较class类与类之间是否子类关系呢?要比较class对象和一个实例对象呢?

Java反射为我们提供了解决方法

/**
 * 相等判断 除了 instanceof
 */
private static void testVersion2_1() {
    Out.println(SuperClass.HELLO_WORLD);
}

private static void testVersion3_1() {
    boolean isAssignable = SuperClass.class.isAssignableFrom(SuperClass.class);
    Out.println("isAssignable = " + isAssignable);
}

private static void testVersion3_2() {
    SubClass subClass = new SubClass();
    boolean isInstance = SuperClass.class.isInstance(subClass);
    Out.println("isInstance = " + isInstance);
}

说明:使用isInstance,isAssignableFrom是需要注意判断的逻辑关系。比如拿testVersion3_1方法举例,boolean isAssignable = SuperClass.class.isAssignableFrom(SuperClass.class);说的是SuperClass是否是SuperClass的子类啊?isAssignable的布尔值就是答案

参考资料

欢迎关注CodeThings
上一篇下一篇

猜你喜欢

热点阅读