关于java:Java-类型信息详解和反射机制

55次阅读

共计 5654 个字符,预计需要花费 15 分钟才能阅读完成。

本文局部摘自 On Java 8

RTTI

RTTI(RunTime Type Information)运行时类型信息,可能在程序运行时发现和应用类型信息,把咱们从只能在编译期通晓类型信息并操作的局限中解脱进去

传统的多态机制正是 RTTI 的根本应用:假如有一个基类 Shape 和它的三个子类 Circle、Square、Triangle,当初要把 Circle、Square、Triangle 对象放入 List\<Shape\> 中,在运行时,先把放入其中的所有对象都当作 Object 对象来解决,再主动将类型转换为 Shape。所有类型转换的正确性查看都是在运行时进行的,这也正是 RTTI 的含意所在:在运行时,辨认一个对象的类型

但这样的类型转换并不彻底,Object 只是被转型为 Shape,而不是更具体的 Circle、Square、Triangle,如果咱们心愿失去更具体的类型呢?比如说咱们当初须要旋转所有图形,然而想跳过圆形(圆形旋转没有意义),这时能够应用 RTTI 查问某个 Shape 援用所指向对象的确切类型,而后抉择进行适合的解决

Class 对象

家喻户晓,每当咱们编写并编译了一个新类,就会产生一个 Class 对象,它蕴含了与类无关的信息。咱们能够应用 Class 对象来实现 RTTI,一旦某个类的 Class 对象被载入内存,它就能够用来创立这个类的所有对象

Class 对象都属于 Class 类型,既然它也是对象,那咱们就能够获取和操控它的援用。forName() 是 Class 类的一个静态方法,咱们能够应用 forName() 依据指标类的全限定名(蕴含包名)失去该类的 Class 对象。应用 forName() 会有一个副作用,那就是如果这个类没有被加载就会加载它,而在加载的过程中,Gum 类的 static 初始块会被执行。当 Class.forName() 找不到要加载的类,就会抛出异样 ClassNotFoundException

Class gumClass = Class.forName("Gum");

应用 Class.forName() 你不须要先持有这个类型的对象,但如果你曾经领有了指标类的对象,那就能够通过调用 getClass() 办法来获取 Class 援用,这个办法来自根类 Object,它将返回示意该对象理论类型的 Class 对象的援用

Gum gum = new Gum();
Class gumClass = gum.getClass();

另外,你还能够调用 getSuperclass() 办法来失去父类的 class 对象,再用父类的 Class 对象调用该办法,反复屡次,你就能够失去一个残缺的类继承构造

Class 对象的 newInstance() 办法能够让你在不晓得一个的确切类型的时候创立这个类的对象,应用 newInstance() 来创立的类,必须带有无参数的结构器

Object obj = gumClass.newInstance();

当然,因为失去的是 Object 的援用,目前你只能给它发送 Object 对象能承受的调用。如果你想申请具体对象才有的调用,你就得先获取该对象的更多类型信息,并执行转型

Java 还提供了另一种生成类对象的援用:类字面常量,这样做不仅更简略,而且更平安,因为它在编译时就会收到查看(不必放在 try 语句块中),而且铲除了对 forName() 办法的调用,效率更高

Class gumClass = Gum.class; 

类字面常量不仅能够用于一般类,也能够用于接口、数组以及根本数据类型。对于根本数据类型的包装类,还有一个规范字段 Type,Type 字段是一个援用,指向对应根本数据类型的 Class 对象,例如 int.class 就等价于 Integer.TYPE。还有一点值得注意的是:应用 .class 语法来取得对类对象的援用不会触发初始化

到这里咱们都晓得了,Class 援用总是指向某个 Class 对象,而 Class 对象能够用于产生类的实例。不过自从 Java 引入泛型当前,咱们就能够应用泛型对 Class 援用所指向的 Class 对象的类型进行限定,让它的类型变得更具体些

Class intClass = int.class;
Class<Integer> genericIntClass = int.class;
intClass = genericIntClass;    // 同一个货色
// genericIntClass = double.class    非法

好了,既然拿到了 Class 对象,那咱们就能够这个类的类型信息,罕用的办法如下:

办法 用处
asSubclass(Class<U> clazz) 把传递的类的对象转换成代表其子类的对象
Cast 把对象转换成代表类或是接口的对象
getClassLoader() 取得类的加载器
getClasses() 返回一个数组,数组中蕴含该类中所有公共类和接口类的对象
getDeclaredClasses() 返回一个数组,数组中蕴含该类中所有类和接口类的对象
forName(String className) 依据类名返回类的对象
getName() 取得类的残缺门路名字
newInstance() 创立类的实例
getPackage() 取得类的包
getSimpleName() 取得类的名字
getSuperclass() 取得以后类继承的父类的名字
getInterfaces() 取得以后类实现的类或是接口

类型转换检测

到目前为止,咱们已知的 RTTI 类型包含:

  1. 传统的类型转换,如多态
  2. 代表对象类型的 Class 对象

RTTI 在 Java 中还有第三种模式,那就是关键字 instanceof,它返回一个布尔值,通知咱们对象是不是某个特定类型的实例,能够用发问的形式应用它

if(x instanceof Dog) {((Dog)x).bark();}

Java 还提供了 Class.isInstance() 办法动静检测对象类型,例如

0 instance of String // 编译报错
String.class.isInstance(0)    // 能够通过编译

反射

如果你不晓得对象的确切类型,RTTI 会通知你,然而有一个限度:必须在编译时晓得类型,能力应用 RTTI 检测它。换句话说,编译器必须晓得你应用的所有类

看上去这并不是什么特地大的限度,但假如你援用了一个不在程序空间中的对象,比方你从磁盘文件或网络连接中取得大量的字节,并被告知这些字节代表一个类,那该怎么办呢?

类 Class 反对反射的概念,java.lang.reflect 库中反对类 Field、Method、Constructor(每一个都实现了 Member 接口),这些类型的对象由 JVM 运行时创立,以示意未知类中的对应成员。通常咱们不会间接应用反射,但反射能够用来反对其余 Java 个性,例如对象序列化等

Field 代表类的成员变量(成员变量也称为类的属性),Class 类中定义了如下办法用来获取 Field 对象

办法 用处
getField(String name) 取得某个私有的属性对象
getFields() 取得所有私有的属性对象
getDeclaredField(String name) 取得某个属性对象
getDeclaredFields() 取得所有属性对象

Field 类定义了如下办法设置成员变量的信息

办法 用处
equals(Object obj) 属性与 obj 相等则返回 true
get(Object obj) 取得 obj 中对应的属性值
set(Object obj, Object value) 设置 obj 中对应属性值

Method 代表类的办法,Class 类中定义了如下办法用来获取 Method 对象

办法 用处
getMethod(String name, Class…<?> parameterTypes) 取得该类某个私有的办法
getMethods() 取得该类所有私有的办法
getDeclaredMethod(String name, Class…<?> parameterTypes) 取得该类某个办法
getDeclaredMethods() 取得该类所有办法

Method 类定义了如下办法对办法进行调用

办法 用处
invoke(Object obj, Object… args) 传递 object 对象及参数调用该对象对应的办法

Constructor 代表类的结构器,Class 类中定义了如下办法用来获取 Constructor 对象

办法 用处
getConstructor(Class…<?> parameterTypes) 取得该类中与参数类型匹配的私有构造方法
getConstructors() 取得该类的所有私有构造方法
getDeclaredConstructor(Class…<?> parameterTypes) 取得该类中与参数类型匹配的构造方法
getDeclaredConstructors() 取得该类所有构造方法

Constructor 代表类的构造方法

办法 用处
newInstance(Object… initargs) 依据传递的参数创立类的对象

除了成员变量、办法和结构器以外,反射还能获取其余更多的信息,例如注解等,具体可查阅 Java API

反射的弱小威力大家曾经看到了,通过反射咱们甚至能够获取到一些“本不应该获取”的信息,例如程序员为了升高耦合,往往会应用接口来隔离组件,但反射却能够轻易破解

public interface A {void f();
}


class B implements A {public void f() {}
    public void g() {}
}

public class InterfaceViolation {public static void main(String[] args) {A a = new B();
        a.f();
        // a.g(); // 编译谬误
        if (a instanceof B) {B b = (B) a;
            b.g();}
    }
}

通过应用 RTTI,咱们发现 a 是用 B 实现的,只有将其转型为 B,咱们就能够调用不在 A 中的办法。如果你不心愿客户端开发者这样做,那该如何解决呢?一种解决方案是间接申明为理论类型,另一种则是让实现类只具备包拜访权限,这样包内部的客户端就看不到实现类了

除了这个以外,通过反射能够取得所有成员信息,包含 private 的,通常这种违反拜访权限的操作并不是十恶不赦的,兴许还能够帮忙你解决某些特定类型的问题

动静代理

代理是根本的设计模式之一,一个对象封装实在对象,代替实在对象提供其余不同的操作,这些操作通常波及到与实在对象的通信,因而代理通常充当两头对象。上面是一个简略的动态代理的示例:

interface Interface {void doSomething();
}

class RealObject implements Interface {
    @Override
    public void doSomething() {System.out.println("doSomething");
    }
}

class SimpleProxy implements Interface {
    private Interface proxied;

    SimpleProxy(Interface proxied) {this.proxied = proxied;}

    @Override
    public void doSomething() {System.out.println("SimpleProxy doSomething");
        proxied.doSomething();}
}

class SimpleProxyDemo {public static void consumer(Interface iface) {iface.doSomething();
    }

    public static void main(String[] args) {consumer(new RealObject());
        consumer(new SimpleProxy(new RealObject()));
    }
}

当你心愿将额定的操作与实在对象做拆散时,代理可能会有所帮忙,而 Java 的动静代理更进一步,不仅动态创建代理对象,而且能够动静地解决对代理办法的调用。在动静代理上进行的所有调用都会重定向到一个 调用处理程序,该程序负责发现调用的内容并决定如何解决,上面是一个简略示例:

class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;

    DynamicProxyHandler(Object proxied) {this.proxied = proxied;}

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.invoke(proxied, args);
    }
}

class SimpleDynamicProxy {public static void consumer(Interface iface) {iface.doSomething();
    }

    public static void main(String[] args) {RealObject real = new RealObject();
        Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),
                new Class[]{Interface.class},
                new DynamicProxyHandler(real));
        consumer(proxy);
    }
}

通过调用静态方法 Proxy.newProxyInstance() 来创立动静代理,该办法须要三个参数:类加载器、心愿代理实现的接口列表、以及接口 InvocationHandler 的一个实现。InvocationHandler 正是咱们所说的调用处理程序,动静代理的所有调用会被重定向到调用处理程序,因而通常为调用处理程序的构造函数提供一个实在对象的援用,以便执行两头操作后能够转发申请

正文完
 0