類型信息小筆記
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改不了。
所以可以說,反射確實可以無視權限… ,有好有壞咯。
利大於弊這樣。
類型信息小筆記