1. 程式人生 > >類型信息小筆記

類型信息小筆記

eric 泛型 isp not in .com ESS illegal method lec

RTTI

是Runtime type information的縮寫,可以讓你在程序運行的時候,發現和使用類的類型信息。

在有了泛型的容器中拿元素出來,就是一個RTTI最基本的體現。因為需要把容器中存的Object對象,轉換成你泛型寫的那個對象,這個轉換的檢查是發生在運行時的,所以是RTTI。

(Shape)強制轉型也是RTTI的一個體現

Class Object

要知道RTTI在Java中是怎麽工作的,你總得在運行時知道類型信息是怎麽展示的吧。

這個類型信息在運行時的獲取,就是通過這個Class Object 來獲取的,這個對象存儲著類的信息。

在你的程序中的每個類,都有一個對應的Class 對象,這個Class Object其實就和普通的對象一樣,類名是Class而已。每次你寫完並編譯一個類,這個類的Class Object也同時被創建,並存儲在.class文件中。為了創建這個Class對象,JVM用了個叫類加載器

的子系統。

類加載器子系統包含一條類加載器鏈。
但只有一個原生類加載器,這個也是JVM的實現的一部分。
這個原聲類加載器加載的都是可信類,包括Java的API,一般都是直接從本地硬盤上加載的。

這個鏈中,一般不需要添加額外的類加載器,除非你有些特殊的需求。

所以類都是在第一次被使用的時候,被動態加載到JVM中去的。

類加載器首先檢查類對象是否被加載。
沒的話,默認的類加載器根據類名找.class文件;
附加的類加載器可能會去數據庫找字節碼。類的字節碼在加載的時候,她們會接受驗證,保證沒有被破壞和不包含壞的Java代碼。static的初始化也是在類加載的時候進行的。

獲得Class Object對象引用的方法:

1. 用Class類的static方法——Class.forName()

傳一個類名String進去,然後可以獲得這個類的Class對象的引用。

在調用這個forName()方法的時候,如果這個類還沒被加載,就會加載它,所以這裏static初始化語句被執行了。這是forName()方法很重要也很有用的一個功能。

2.Object類的getClass()方法

如果你已經有了這個類的對象,那麽你可以通過Object類的一個方法—— getClass()來獲取這個類的Class對象引用。

3.類字面常量——.class

類名.class的形式咯就。

對於基本數據類型的封裝類,有個標準eld叫做TYPE,這個field提供了基本數據類型的Class Object。 比如Integer.TYPE就可以獲得基本數據類型int的Class Object或者說是int.class

意,.class有個和forName方法不一樣的地方:.class會返回一個Class Object的引用,但不會自動進行初始化操作。這裏的初始化指的是這個類的對象的類加載和一些static的初始化。或者說是類初始化。加載一個類需要三步:

技術分享圖片

而第三步初始化類,被延遲到你第一次執行該類的static方法才會進行。這個就和Class.forName()方法不一樣了。

通過這個Class Object你可以獲得很多類型信息。下面這個例子展示了一些方法:

interface HasBatteries {}
interface Waterproof {}
interface Shoots {}

class Toy {
    // Comment out the following default constructor
    // to see NoSuchMethodError from (*1*)
    Toy() {}
    Toy(int i) {}
}

class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots {
    FancyToy() { super(1); }
}

public class ToyTest {
    static void printInfo(Class cc) {
        print("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]");
        print("Simple name: " + cc.getSimpleName());
        print("Canonical name : " + cc.getCanonicalName());
    }
    public static void main(String[] args) {
        Class c = null;
        try {
            c = Class.forName("typeinfo.toys.FancyToy");
        } catch(ClassNotFoundException e) {
            print("Can’t find FancyToy");
            System.exit(1);
        }
        printInfo(c);
        for(Class face : c.getInterfaces())
            printInfo(face);
        Class up = c.getSuperclass();
        Object obj = null;
        try {
            // Requires default constructor:
            obj = up.newInstance();
        } catch(InstantiationException e) {
            print("Cannot instantiate");
            System.exit(1);
        } catch(IllegalAccessException e) {
            print("Cannot access");
            System.exit(1);
        }
        printInfo(obj.getClass());
    }
} 
/* Output:
Class name: typeinfo.toys.FancyToy is interface? [false]
Simple name: FancyToy
Canonical name : typeinfo.toys.FancyToy
Class name: typeinfo.toys.HasBatteries is interface? [true]
Simple name: HasBatteries
Canonical name : typeinfo.toys.HasBatteries
Class name: typeinfo.toys.Waterproof is interface? [true]
Simple name: Waterproof
Canonical name : typeinfo.toys.Waterproof
Class name: typeinfo.toys.Shoots is interface? [true]
Simple name: Shoots
Canonical name : typeinfo.toys.Shoots
Class name: typeinfo.toys.Toy is interface? [false]
Simple name: Toy
Canonical name : typeinfo.toys.Toy
*///:~

幾點要註意的:

1.Class.forName()一定要填完整的類名。

2.介個newInstance()方法是Class中一個實現虛擬構造器的方法。例子中,up是一個Class Object的引用,但在編譯期不具備任何進一步的類型信息。然後你利用這個newInstance()方法獲得了一個Object對象的引用。

但用這個方法,你這個類要有默認的構造器,想想也知道,這個方法是無參數的,所以你必須要一個無參的構造器呢。

加上泛型的Class引用

一個Class引用可以指向一個Class對象,看下面的代碼:

Class intClass = int.class;
Class<Integer> genericIntClass = int.class;
genericIntClass = Integer.class; // Same thing
intClass = double.class;
// genericIntClass = double.class; // Illegal

看這個例子就知道要講什麽東西了,本來你可以用一個Class Object的引用去指向任意一個類的Class Object。
但加上泛型後,像genericIntClass,這個Class Object的引用就只能指向正確的類的Class Object了。

使用泛型語法,可以讓編譯器進行額外的類型檢查

哦對,如果你的Class引用有泛型的話,那麽執行newInstance()就不再返回Object對象了,會幫你正確轉型。

新的轉型語法

直接看例子:

//: typeinfo/ClassCasts.java
class Building {}
class House extends Building {}
public class ClassCasts {
    public static void main(String[] args) {
        Building b = new House();
        Class<House> houseType = House.class;
        House h = houseType.cast(b);
        h = (House)b; // ... or just do this.
    }
} ///:

說,這個cast方法似乎看起來直接用(House)b這樣的強制轉型就可以了,但有時候你不是很方便用這種普通的轉型,比如在寫泛型代碼的時候,你有一個Class Object的引用,然後想轉型的時候,這個cast就有用了。

RTTI的第三種形式——instanceof

前面兩種是(Shape)強制 轉換還有Class Object。

技術分享圖片

instanceof方法有個局限哦,只能比較類的名稱,不能是一個Class Object。

還有個動態的instanceof:

技術分享圖片

利用Class Object中的isInstance()後更方便,消除了之前傻逼的instanceof語句,然後而且現在代碼設計也更好了。

instanceof和isInstance()生成的結果完全一樣;如果獲得Class引用,用equals()和==來檢查Class對象是否相等,這兩個方法的結果也一樣。

但這兩組方法的含義卻不同,instanceof系列保持了類型的概念,它指的是”你是這個類嗎,或者是你是這個類的派生類嗎?“而用equals()或者==比較實際的Class對象,沒有考慮繼承——它或者是確切的類型,或者不是。

還有個判斷子類的方法:

又一個判斷是不是的方法,
superClass.isAssignableFrom(childClass) 屬於 Class.java。它的對象和參數都是類,意思是“父類(或接口類)判斷給定類是否是它本身或其子類”
子接口也可以判斷

反射

我們的這個RTTI呢,有個限制,就是這個類型必須是編譯時期已知的,換種話說,在編譯時,編譯器必須知道所有要通過RTTI處理的類。

但有時候捏,你會獲得一個指向並不在你程序空間中的對象的引用,這個時候的類是在編譯後過了很久才出現的,所以用RTTI無法知道它的類型信息。

這個時候就要用反射的機制了。

Class類還有java.lang.reflect類庫一起對反射進行了支持,這個reflect庫中有Field、Method、Constructor類,每個類都實現了Member接口。這些幾個類都的JVM在運行時才創建的,用以表示未知類的對應成員,其實就把未知類的成員都抽象成類。這樣你就可以用Constructor創建新的對象;用get和set方法讀取和修改Field對象相關聯的字段;用invoke方法調用與Method對象相關聯的方法,還有一些很方便的方法等等會介紹。

這樣未知類的信息就可以在運行時知道了,在編譯期什麽都不用知道。

當通過反射來和一個未知類型的對象打交道的時候,JVM只是簡單地檢查這個對象,看它屬於哪個類,在進行任何操作之前,必須加載這個類的Class Object;所以這個類的.class文件對於JVM來說必須是可獲取的,要麽在本地機器上,要麽通過網絡。

所以關於RTTI和反射真正的區別是:對於RTTI而言,編譯器在編譯時打開和檢查.class文件;對於反射而言,.class文件在編譯時是不可獲取的,所以在運行時打開和檢查.class文件。

看個簡單的用法,就查看類的相關方法信息的:

Class<?> c = Class.forName(args[0]);
Method[] methods = c.getMethods();
Constructor[] ctors = c.getConstructors();
if(args.length == 1) {
    for(Method method : methods)
        print(p.matcher(method.toString()).replaceAll(""));
    for(Constructor ctor : ctors)
        print(p.matcher(ctor.toString()).replaceAll(""));
    lines = methods.length + ctors.length;
} else {
    for(Method method : methods)
        if(method.toString().indexOf(args[1]) != -1) {
            print(p.matcher(method.toString()).replaceAll(""));
            lines++;
        }
    for(Constructor ctor : ctors)
        if(ctor.toString().indexOf(args[1]) != -1) {
            print(p.matcher(ctor.toString()).replaceAll(""));
            lines++;
    }
}

代碼是不完整的哈哈,理解下就是。

動態代理

代理是基本設計模式之一。它是為你提供額外的或者是不同的操作,而插入的用來替代實際對象的對象。這些操作通常涉及與實際對象的通信,因此代理經常充當中間人的角色。

代理的一個很簡單的實現的方式就是,和真實對象實現同一個接口,然後就可以充當真實對象了,看個例子:

技術分享圖片
interface Interface {
    void doSomething();
    void somethingElse(String arg);
}

class RealObject implements Interface {
    public void doSomething() { print("doSomething"); }
    public void somethingElse(String arg) {
        print("somethingElse " + arg);
    }
}

class SimpleProxy implements Interface {
    private Interface proxied;

    public SimpleProxy(Interface proxied) {
        this.proxied = proxied;
    }
    public void doSomething() {
        print("SimpleProxy doSomething");
        proxied.doSomething();
    }
    public void somethingElse(String arg) {
        print("SimpleProxy somethingElse " + arg);
        proxied.somethingElse(arg);
    }
}

class SimpleProxyDemo {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("bonobo");
    }
    public static void main(String[] args) {
        consumer(new RealObject());
        consumer(new SimpleProxy(new RealObject()));
    }
} 
/* Output:
doSomething
somethingElse bonobo
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo
*///:~
View Code

代理可以幫你把一些額外的操作放在別的地方。

一般都會在代理類中放一個被代理類也就是真實對象的引用,方便通信和操作。

Java的動態代理就肯定更厲害了,可以動態地創建代理並且動態地處理對代理方法的調用。在動態代理上所做的所有調用都會被重定向到單一的調用處理器上,Java有一個專門作為這個處理器的接口InvocationHandler;這個處理器的工作就是揭示調用的類型並確定相應的對策。

下面是用Java動態代理重寫的一個例子:

import java.lang.reflect.*;
class DynamicProxyHandler implements InvocationHandler {
    private Object proxied; //被代理對象,真實。
    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);
        if(args != null)
            for(Object arg : args)
                System.out.println(" " + arg);
        return method.invoke(proxied, args);
    }
}

class SimpleDynamicProxy {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("bonobo");
    }
    public static void main(String[] args) {
        RealObject real = new RealObject();
        consumer(real);
        // Insert a proxy and call again:
        Interface proxy = (Interface)Proxy.newProxyInstance(
            Interface.class.getClassLoader(),
            new Class[]{ Interface.class },
            new DynamicProxyHandler(real));
        consumer(proxy);
    }
} 
/* Output: (95% match)
doSomething
somethingElse bonobo
**** proxy: class $Proxy0, method: public abstract void
Interface.doSomething(), args: null
doSomething
**** proxy: class $Proxy0, method: public abstract void
Interface.somethingElse(java.lang.String), args:
[Ljava.lang.Object;@42e816
bonobo
somethingElse bonobo
*///:~

首先介紹這個調用處理器的接口——InvocationHandler.

實現這個接口要重寫Object invoke()方法。這個方法有三個參數:

參數1:
  代理的對象,代理人,或者說中間人。就是動態生成的代理類的實例,這個是Java會自動傳進來的,這個參數你可以1. 可以使用反射獲取代理對象的信息(也就是proxy.getClass().getName());

2.可以將代理對象返回以進行連續調用,這就是proxy存在的目的。因為this並不是代理對象。所以好像說,在invoke方法內部調用這個proxy代理對象的方法要很小心,因為會被重定向為對代理的調用。
參數2:
  調用的方法,被執行的方法。這個參數是個Method類的對象,而這個對象有個方法Method.invoke(),通過這個方法你可以把請求轉發給被代理的那個對象,並傳入需要的參數。
參數3:
  執行該方法所需要的參數

理解就是,你用Java幫你動態生成的代理類執行一個方法後,這個方法的執行立刻就會被重定向到這個指定的InvocationHandler的實現類中,然後映射到這個invoke方法中進行處理。

然後創建動態代理,是通過Proxy.newProxyInstance()方法。這個方法一般也需要三個參數:

參數1:
  類加載器,可以從已經加載的對象那裏拿一個喔。(似乎傳的都是要實現的接口的類加載器)。

  理解應該是:指明生成代理對象使用哪個類裝載器。

  thinking in Java說,一般可以從已經加載的對象中獲取其類加載器然後傳遞給它喔。
參數2:
  你想讓動態代理類實現的接口。

  這裏似乎傳的是接口的Class Object列表。
參數3:
  InvocationHandler接口的實現類。動態代理會把所有調用重定向到這個invocation handler上,所以一般這個invocation handler的實現類的構造器會傳一個真正對象的引用。

通過反射可以使用private的方法,還可以訪問和修改private的field

一個用反射來調用方法的例子:

public interface A {
    void f();
} ///:

class HiddenImplementation {
    static void callHiddenMethod(Object a, String methodName) throws Exception {
        Method g = a.getClass().getDeclaredMethod(methodName);
        g.setAccessible(true);
        g.invoke(a);
    }
}

class InnerA {
    private static class C implements A {
        public void f() { print("public C.f()"); }
        public void g() { print("public C.g()"); }
        void u() { print("package C.u()"); }
        protected void v() { print("protected C.v()"); }
        private void w() { print("private C.w()"); }
    }
    public static A makeA() { return new C(); }
}

public class InnerImplementation {
    public static void main(String[] args) throws Exception {
        A a = InnerA.makeA();
        a.f();
        System.out.println(a.getClass().getName());
        // Reflection still gets into the private class:
        HiddenImplementation.callHiddenMethod(a, "g");
        HiddenImplementation.callHiddenMethod(a, "u");
        HiddenImplementation.callHiddenMethod(a, "v");
        HiddenImplementation.callHiddenMethod(a, "w");
    }
} 
/* Output:
public C.f()
InnerA$C
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

callHidenMethod方法就是對某個對象中的方法的調用,什麽方法都可以,利用反射。

還有訪問和修改private filed的例子:

class WithPrivateFinalField {
    private int i = 1;
    private final String s = "I’m totally safe";
    private String s2 = "Am I safe?";
    public String toString() {
        return "i = " + i + ", " + s + ", " + s2;
    }
}

public class ModifyingPrivateFields {
    public static void main(String[] args) throws Exception {
        WithPrivateFinalField pf = new WithPrivateFinalField();
        System.out.println(pf);
        Field f = pf.getClass().getDeclaredField("i");
        f.setAccessible(true);
        System.out.println("f.getInt(pf): " + f.getInt(pf));

        f.setInt(pf, 47);
        System.out.println(pf);
        f = pf.getClass().getDeclaredField("s");
        f.setAccessible(true);
        System.out.println("f.get(pf): " + f.get(pf));

        f.set(pf, "No, you’re not!");
        System.out.println(pf);
        f = pf.getClass().getDeclaredField("s2");
        f.setAccessible(true);
        System.out.println("f.get(pf): " + f.get(pf));

        f.set(pf, "No, you’re not!");
        System.out.println(pf);
    }
} 
/* Output:
i = 1, I’m totally safe, Am I safe?
f.getInt(pf): 1
i = 47, I’m totally safe, Am I safe?
f.get(pf): I’m totally safe
i = 47, I’m totally safe, Am I safe?
f.get(pf): Am I safe?
i = 47, I’m totally safe, No, you’re not!
*///:~

用反射可以去訪問和修改private的field,實驗說明只有final的field改不了。

所以可以說,反射確實可以無視權限… ,有好有壞咯。
利大於弊這樣。

類型信息小筆記