Thinking in Java笔记:Runtime Type Information

来源:互联网 发布:sybase数据库12.5 编辑:程序博客网 时间:2024/05/16 23:45

    • Traditional RTTI
      • 例一
      • 例二
      • 例三
    • Reflection
      • Dynamic proxy
        • 例四

RunTime Type Information (RTTI) allows you to discover and use type information while a program is running, and it takes two forms:

  • “Traditional” RTTI, which assumes that you have all the types available at compile time.
  • reflection mechanism, which allows you to discover and use class information solely at run time.

Traditional RTTI

The Class object contains information about the class. There’s one Class object for each class that is part of your program. That is, each time you write and compile a new class, a single Class object is also created (and stored in an identically named .class file). To make an object of that class, the Java Virtual Machine (JVM) that’s executing your program uses a subsystem called a class loader. The class loader subsystem can actually comprise a chain of class loaders, but there’s only one primordial class loader, which is part of the JVM implementation. The primordial class loader loads so-called trusted classes, including Java API classes, typically from the local disk.
All classes are loaded into the JVM dynamically, upon the first use of a class. This happens when the program makes the first reference to a static member of that class. It turns out that the constructor is also a static method of a class, even though the static keyword is not used for a constructor. Therefore, creating a new object of that class using the new also counts as a reference to a static member of the class.
The class loader first checks to see if the Class object for that type is loaded. If not, the default class loader finds the .class file with that name. As the bytes for the class are loaded, they are verified to ensure that they have not been corrupted and that they do not comprise bad Java code. Once the Class object for that type is in memory, it is used to create all objects of that type.

例一

All Class objects belong to the class Class. A Class object is like any other objects, so you can get and manipulate a reference to it. One of the ways to get a reference to the Class object is the static forName() method, which takes a String containing the textual name of the particular class you want a reference for. It returns a Class reference.

// 源码存储在SweetShop.java文件中。class Candy {        // 当该类第一次被JVM装载的时候执行这条static clause。    static { System.out.println("Loading Candy"); }}class Gum {    static { System.out.println("Loading Gum"); }}class Cookie {    static { System.out.println("Loading Cookie"); }}public class SweetShop {    public static void main(String[] args) {        System.out.println("inside main");        new Candy();        System.out.println("After creating Candy");        try {            Class.forName("Gum");        } catch (ClassNotFoundException e) {            System.out.println("Couldn't find Gum");        }        System.out.println("After Class.forName(\"Gum\")");        new Cookie();        System.out.println("After creating Cookie");    }}

Class.forName()方法能够在硬盘上搜寻指定的类,并返回指向该类的引用。另外,这个方法的一个副作用为令JVM装载该类。编译并执行这段代码可以发现,Gum类被载入JVM。

$ javac SweetShop.java $ java SweetShop inside mainLoading CandyAfter creating Candy#载入Gum类。Loading GumAfter Class.forName("Gum")Loading CookieAfter creating Cookie

可以看到源码中为了防止找不到Gum类出现的错误,使用了trycatch语句。为了看到出现错误时源码逻辑是否正确,将编译后产生的SweetShop.class删除(这是因为笔者一开始认为Java编译器只为每个Java源文件编译出一个与public类相同名字的.class文件)。当删除SweetShop.class后执行程序仍然可以看到Gum类成功加载,经过一段探索后发现,Java编译器会为一个源码中的所有类生成一个.class文件,而不是只为public类生成.class文件。

例二

All Class objects belong to the class Class. A Class object is like any other objects, so you can get and manipulate a reference to it. One of the ways to get a reference to the Class object is the static forName() method, which takes a String containing the textual name of the particular class you want a reference for. It returns a Class reference.

// 因package为rtti.toy,将该源码放在rtti/toy/ToyTest.java文件中。package rtti.toy;interface HasBatteries {}interface Waterproof {}interface Shoots {}class Toy {    Toy() {}    Toy(int i) {}}class FancyToy extends Toyimplements HasBatteries, Waterproof, Shoots {    FancyToy() { super(1); }}public class ToyTest {    static void printInfo (Class cc) {            // getName()和getCanonicalName()都返回带package的全称            // getSimpleName()只返回不带package的类名。        System.out.println("Class name: " + cc.getName() +            " is interface? [" + cc.isInterface() + "]");        System.out.println("Simple name: " + cc.getSimpleName());        System.out.println("Canonical name: " + cc.getCanonicalName());    }    public static void main(String[] args) {            // 定义一个Class类引用。        Class c = null;        try {            c = Class.forName("rtti.toy.FancyToy");        } catch (ClassNotFoundException e) {            System.out.println("Cannot find FancyToy");            System.exit(1);        }        printInfo(c);        // getInterfaces()返回所有引用c所implements的interface。        for (Class face : c.getInterfaces())            printInfo(face);        // getSuperclass()返回引用c的父类。        Class up = c.getSuperclass();        Object obj = null;        try {                // newInstance()生成up类的一个实例。注意,使用该方法要求                // up类必须具有default构造函数。            obj = up.newInstance();        } catch (InstantiationException e) {            System.out.println("Cannot instantiate");            System.exit(1);        } catch (IllegalAccessException e) {            System.out.println("Cannot access");            System.exit(1);        }        // 如果我们有指向某个类的一个实例的引用,则可以用getClass()        // 得到指向该类的Class引用。        printInfo(obj.getClass());    }}

在编译该程序后,如果我们在rtti/rtti/toy/目录下执行该程序会出现如下错误:

Error: Could not find or load main class ToyTest

这是因为该程序所在的package为rtti.toy,我们必须在rtti/的父目录才能成功执行该程序。

例三

Write a method that takes an object and recursively prints all the classes in that object’s hierarchy. Modify the previous exercise so that it uses Class.getDeclaredFields( ) to also display information about the fields in a class.

package rtti.hierachy;import java.lang.reflect.*;class First {    private int first_1;    protected int first_2;    public int first_3;}class Second extends First {    private int second_1;    protected int second_2;    protected int second_3;}class Third extends Second {    private int third_1;    protected int third_2;    public int third_3;}public class HierarchyTest{    public static void main(String[] args) {        try {            // 利用for循环递归查询,其中getSuperclass()方法返回某个类的父类。            for (Class ref = Class.forName(                "rtti.hierachy.Third");                 !ref.getSimpleName().equals("Object");                ref = ref.getSuperclass()) {                System.out.println("Class name: " + ref.getSimpleName());                // getDeclaredFields()返回该类中所有的fields (包括private, public,                // 和protected字段),但是不包括从其父类继承来的字段。和该方法类似的一个                // 方法为getFields()方法,该方法仅返回public字段(注意包括其父类中的public字段)。                // 这两个方法返回的类都为Field类,该类定义在java.lang.reflect package中,                // 因此需要我们在程序开头导入java.lang.reflect.Field。从Field类中的getName()                // 方法可以得到该字段的名字。                for (Field field : ref.getDeclaredFields())                    System.out.print(field.getName() + " ");                //                 System.out.println("Superclass name: " +                     ref.getSuperclass().getSimpleName());            }        } catch (ClassNotFoundException e) {            System.out.println("Cannot find class");        }    }}

Reflection

The class Class supports the concept of reflection, along with the java.lang.reflect library which contains the classes Field, Method, and Constructor (each of which implements the Member interface). Objects of these types are created by the JVM at run time to represent the corresponding member in the unknown class. You can then use the Constructors to create new objects, the get() and set() methods to read and modify the fields associated with Field objects, and the invoke() method to call a method associated with a Method object. In addition, you can call the convenience methods getFields(), getMethods(), getConstructors(), etc., to return arrays of the objects representing the fields, methods, and constructors. Thus, the class information for anonymous objects can be completely determined at run time, and nothing need be known at compile time.
It’s important to realize that there’s nothing magic about reflection. When you’re using reflection to interact with an object of an unknown type, the JVM will simply look at the object and see that it belongs to a particular class. Before anything can be done with it, the Class object must be loaded. Thus, the .class file for that particular type must still be available to the JVM, either on the local machine or across the network. So the true difference between RTTI and reflection is that with RTTI, the compiler opens and examines the .class file at compile time. Put another way, you can call all the methods of an object in the normal way. With reflection, the .class file is unavailable at compile time; it is opened and examined by the runtime environment.

Dynamic proxy

Java’s dynamic proxy takes the idea of a proxy one step further, by both creating the proxy object dynamically and handling calls to the proxied methods dynamicaly. All calls made on a dynamic proxy are redirected to a single invocation handler, which has the job of discovering what the call is and deciding what to do about it.
You create a dynamic proxy by calling the static method Proxy.newProxyInstance(), which requires a class loader, a list of interfaces (not classes or abstract classes) that you wish the proxy to implement, and an implementation of the interface InvocationHandler. The dynamic proxy will redirect all calls to the invocation handler, so the constructor for the invocation handler is usually given the reference to the “real” object so tha it can forward requests once it performs its intermediary task.

例四

package rtti.proxy;import java.lang.reflect.*;interface Animal {    void name();    // 可以把food()方法注释掉以说明dynamic proxy只能调用    // 你传递给InvocationHandler.invoke()方法的interface    // 中具有的方法,如调用该interface的implementation中    // interface所不具有的方法,将不能通过编译。    void food();}class Dog implements Animal {    public void name() { System.out.println("Dog"); }    public void food() { System.out.println("I like bone"); }}class Cat implements Animal {    public void name() { System.out.println("Cat"); }    public void food() { System.out.println("I like mouse"); }    public void hate() { System.out.println("I hate dogs"); }}class Tiger implements Animal {    public void name() { System.out.println("Tiger"); }    public void food() { System.out.println("I like meat"); }    public void hate() { System.out.println("I hate human"); }}class Human implements Animal {    public void name() { System.out.println("Human"); }    public void food() { System.out.println("I can eat anythin :)"); }    public void dream() { System.out.println("I want a lot of money"); }}// 实现dynamic proxy必须implements接口InvocationHandler。该implementation// 在执行dynamic proxy时将被传递给Proxy.newProxyInstance()方法。class AnimalProxy implements InvocationHandler {    // 存储在实际执行dynamic proxy时被代理的对象。    private Object proxied;    // 捕获需要被代理的对象。    public AnimalProxy(Object proxied) {        this.proxied = proxied;    }    // 用此函数实现dynamic proxy方法代理。    public Object    invoke(Object proxy, Method method, Object[] args)    throws Throwable {        // 首先编写自己的逻辑。        System.out.println("*** proxy: " + proxy.getClass().getName() +            ", method: " + method + ", args: " + args);        for (Method me : proxied.getClass().getMethods())            if (me.getName().equals(method.getName()))                // 在执行完自己编写的逻辑后,最后调用被代理对象所实现的该方法。                return method.invoke(proxied, args);        System.out.println("No such method.");        return null;    }}public class ProxyTest {    public static void main(String[] args) {        // 通过Proxy.newProxyInstance()方法创建一个代理实例。        // 创建一个代理实例需要给这个方法提供三个参数,        // 第一个参数为class loader,        // 第二个参数为一个列表,该列表包含所有你希望代理implements的interface,        // 注意,仅仅是interface,不能是任何class或abstract class,否则不能通过编译。        // 第三个参数为你写好的InvocationHandler的implementation,并将你所希望被        // 代理的对象传递给这个implementation。        // Proxy.newProxyInstance返回的是一个interface,需要对其进行casting。        Animal proxy = (Animal)Proxy.newProxyInstance(            Animal.class.getClassLoader(),            new Class[] { Animal.class },            new AnimalProxy(new Tiger())            );        proxy.name();        // 如果我们在Animal接口中把food()注释掉,将不能通过编译,产生如下错误:        // "cannot find symbol proxy.food();"        // 这说明只能dynamic proxy只能代理你传递给Proxy.newProxyInstance()的        // interface中包含的方法,该interface的继承类或implementation中扩展的        // 方法将不能够被代理。        proxy.food();    }}