【Java基礎】RTTI與反射之Java


一、引言 

  很多時候我們的程序可能需要在運行時識別對象和類的信息,比如多態就是基於運行時環境進行動態判斷實際引用的對象。在運行時識別對象和類的信息主要有兩種方式:1.RTTI,具體是Class對象,它假定我們在編譯時已經知道了所有類型。2.反射機制,運行我們在運行時發現和使用類的信息。

二、RTTI

  RTTI(Run-Time Type Infomation),運行時類型信息。可以在運行時識別一個對象的類型。類型信息在運行時通過Class對象表示,Class對象包含與類有關的信息,可以使用Class對象來創建類的實例。

  每個類對應一個Class對象,這個Class對象放在.class文件中,當我們的程序中首次主動使用某類型時,會把該類型所對應的Class對象加載進內存,在這篇文章JVM之類加載器中闡述了哪些情況符合首次主動使用。

  既然RTTI和Class對象有莫大的關系,即有了Class對象,就可以進行很多操作,那么,我們如何獲取到Class對象呢?有三種方法1. Class.forName("全限定名");(其中,全限定名為包名+類名)。2. 類字面常量,如String.class,對應String類的Class對象。3.通過getClass()方法獲取Class對象,如String str = "abc";str.getClass();。

  通過一個類對應的Class對象后,我們可以做什么?我們可以獲取該類的父類、接口、創建該類的對象、該類的構造器、字段、方法等等。總之,威力相當大。

  下面我們通過一個例子來熟悉Class對象的各種用法。

package com.hust.grid.leesf.algorithms;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

interface SuperInterfaceA {
};

interface SuperInterfaceB {
};

class SuperC {
    private String name;

    public SuperC() {

    }

    public SuperC(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

class Sub extends SuperC implements SuperInterfaceA, SuperInterfaceB {
    private String name;
    public Sub() {
        super();
    }

    public Sub(String name) {    
        super(name);
        this.name = name;    
    }
    
    public String getName() {
        return name;
    }
}

public class Main {
    public static Sub makeInstance(Class<?> clazz) {
        Sub sub = null;
        try {
            sub = (Sub) clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }

        return sub;
    }

    public static void printBasicInfo(Class<?> clazz) {
        System.out.println("CanonicalName : " + clazz.getCanonicalName());
        System.out.println("Name : " + clazz.getName());
        System.out.println("Simple Name : " + clazz.getSimpleName());
        System.out.println("SuperClass Name : "
                + clazz.getSuperclass().getName());
        Class<?>[] interfaces = clazz.getInterfaces();
        for (Class<?> inter : interfaces) {
            System.out.println("Interface SimpleName : "
                    + inter.getSimpleName());
        }
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> cons : constructors) {
            System.out.println("Constructor Name : " + cons.getName()
                    + " And Parameter Count : " + cons.getParameterCount());
        }
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println("Method Name : " + method.getName());
        }
    }

    public static void main(String[] args) {
        //Sub sub = new Sub();
        //Class<?> clazz = sub.getClass();
        Class<?> clazz = Sub.class;
        Sub instance = makeInstance(clazz);
        if (instance != null) {
            System.out.println("make instance successful");
        } else {
            System.out.println("make instance unsuccessful");
        }
        printBasicInfo(clazz);
    }
}
View Code

  運行結果: 

make instance successful
CanonicalName : com.hust.grid.leesf.algorithms.Sub
Name : com.hust.grid.leesf.algorithms.Sub
Simple Name : Sub
SuperClass Name : com.hust.grid.leesf.algorithms.SuperC
Interface SimpleName : SuperInterfaceA
Interface SimpleName : SuperInterfaceB
Constructor Name : com.hust.grid.leesf.algorithms.Sub And Parameter Count : 0
Constructor Name : com.hust.grid.leesf.algorithms.Sub And Parameter Count : 1
Method Name : getName
View Code

  說明:使用method1、method2、method3三種方法都可以獲得Class對象,運行結果是等效的。但是三者還是有稍許的區別。區別是從類的初始化角度來看的。如Class.forName("全限定名")會導致類型的加載、鏈接、初始化過程,而.class則不會初始化該類。顯然,getClass肯定是會初始化該類的,因為這個方法時依托於類的對象。

  下面我們通過一個例子比較.class和forName()兩種方法的區別。

package com.hust.grid.leesf.algorithms;
import java.util.Random;
class Init1 {
    static final int staticFinal1 = 1;
    static final int staticFinal2 = Main.random.nextInt(100);
    static {
        System.out.println("init init1");
    }
}

class Init2 {
    static int staticNonFinal1 = 3;
    static {
        System.out.println("init init2");
    }
}

class Init3 {
    static int staticNonFinal1 = 5;
    static {
        System.out.println("init init3");
    }
}

public class Main {
    public static Random random = new Random(47);
    public static void main(String[] args) {
        Class<?> clazzClass = Init1.class;
        System.out.println("after init init1 ref");
        System.out.println(Init1.staticFinal1);
        System.out.println(Init1.staticFinal2);
        
        System.out.println(Init2.staticNonFinal1);
        try {
            Class<?> clazz1 = Class.forName("com.hust.grid.leesf.algorithms.Init3");
            System.out.println("after init init3 ref");
            System.out.println(Init3.staticNonFinal1);
        } catch (ClassNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
    }
}
View Code

  運行結果:

after init init1 ref
1
init init1
58
init init2
3
init init3
after init init3 ref
5
View Code

  說明:從結果也進一步驗證了.class不會初始化類,而.forName()會初始化類。並且,對常量靜態域的使用也不會導致類的初始化。

三、反射

  與RTTI必須在編譯器就知道所有類型不同,反射不必在編譯期就知道所有的類型,它可以在運行過程中使用動態加載的類,而這個類不必在編譯期就已經知道。反射主要由java.lang.reflect類庫的Field、Method、Constructor類支持。這些類的對象都是JVM在運行時進行創建,用來表示未知的類。

  關於兩者的區別更深刻表達如下:對於RTTI而言,編譯器在編譯時打開和檢查.class文件;對於反射而言,.class文件在編譯時是不可獲取的,所以在運行時打開和檢查.class文件。

  其實在的第一個例子中我們已經用到了Constructor、Method類,現在我們來更加具體的了解Constructor、Method、Field類。  

package com.hust.grid.leesf.algorithms;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;

class Human {

}

class Girl extends Human {
    private boolean beautiful;
    private int height;
    private String name;
    
    public Girl() {

    }

    public Girl(String name, int height, boolean beautiful) {
        this.name = name;
        this.height = height;
        this.beautiful = beautiful;
    }

    public boolean isBeautiful() {
        return beautiful;
    }

    public String toString() {
        return "height = " + height + " name = " + name + " beautiful = " + beautiful;
    }
    
    private void print() {
        System.out.println("i am a private method");
    }
}

class Boy extends Human {
    private boolean handsome;
    private int height;
    private String name;
    public Boy() {

    }

    public Boy(String name, int height, boolean handsome) {
        this.name = name;
        this.height = height;
        this.handsome = handsome;
    }

    public boolean isHandsome() {
        return handsome;
    }

    public String toString() {
        return "height = " + height + " name = " + name + " handsome = " + handsome;
    }

    private void print() {
        System.out.println("i am a private method");
    }
}

public class Test {
    public static void main(String[] args) throws NoSuchMethodException,
            SecurityException, InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException,
            NoSuchFieldException {
        Scanner scanner = new Scanner(System.in);
        String input = scanner.nextLine();
        Human human = null;
        String name = "leesf";
        int height = 180;
        boolean handsome = true;
        boolean flag = false;
        if ("boy".equals(input)) {
            human = new Boy(name, height, handsome);
            flag = true;
        } else {
            human = new Girl("dyd", 168, true);
        }
        scanner.close();
        Class<?> clazz = human.getClass();
        Constructor<?> constructor = clazz.getConstructor(String.class,
                int.class, boolean.class);
        Human human1 = (Human) constructor.newInstance("leesf_dyd", 175, true);
        System.out.println(human1);
        Method method = null;
        if (flag) {
            method = clazz.getMethod("isHandsome");
        } else {
            method = clazz.getMethod("isBeautiful");
        }
        System.out.println(method.invoke(human));
        Method method2 = clazz.getDeclaredMethod("print");
        method2.setAccessible(true);
        method2.invoke(human);

        Field field = clazz.getDeclaredField("height");
        System.out.println(human);
        field.setAccessible(true);
        field.set(human, 200);
        System.out.println(human);
    }
}
View Code

  輸入:boy

  運行結果: 

boy
height = 175 name = leesf_dyd handsome = true
true
i am a private method
height = 180 name = leesf handsome = true
height = 200 name = leesf handsome = true
View Code

  說明:反射可以讓我們創建一個類的實例、在類外部訪問類的私有方法、私有字段。反射真的很強大~

四、動態代理-反射的應用

  動態創建代理並且動態處理對所代理方法的調用。在動態代理上所做的所有調用都會被重定向到單一的調用處理器上。

  下面是動態代理的例子

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Interface {
    void doSomething();

    void doSomethingElse(String str);
}

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

    @Override
    public void doSomethingElse(String str) {
        System.out.println("doSomething else " + str);
    }
}

class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if (method.getName().startsWith("do")) {
            System.out.println("call do*** methods");
        }
        method.invoke(proxied, args);
        return null;
    }

}

public class DynamicProxy {
    public static void main(String[] args) {
        RealObject proxied = new RealObject();
        proxied.doSomething();
        proxied.doSomethingElse("leesf");
        Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class
                .getClassLoader(), new Class[] { Interface.class },
                new DynamicProxyHandler(proxied));
        proxy.doSomething();
        proxy.doSomethingElse("leesf");
    }
}
View Code

  運行結果: 

doSomething
doSomething else leesf
call do*** methods
doSomething
call do*** methods
doSomething else leesf
View Code

  說明:可以在invoke方法中進行過濾操作。過濾出以do開頭的方法進行轉發。

五、總結

  RTTI和反射分析就到此為止,RTTI和反射確實很強大,可以幫助我們干很多事情,用對地方絕對威力無窮,謝謝各位園友的觀看~

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM