本章內容繞不開一個名詞:RTTI(Run-time Type Identification) 運行時期的類型識別 知乎上有人推斷作者是從C++中引入這個概念的,反正也無所謂,理解並能串聯本章知識才是最重要的 本章的內容其實都是為類型信息服務的,主要內容有 一.Class對象 問題: 1.Class對 ...
本章內容繞不開一個名詞:RTTI(Run-time Type Identification) 運行時期的類型識別
知乎上有人推斷作者是從C++中引入這個概念的,反正也無所謂,理解並能串聯本章知識才是最重要的
本章的內容其實都是為類型信息服務的,主要內容有
一.Class對象
問題:
1.Class對象的創建過程是怎麼樣的
2.Class對象有哪幾種創建方式,之間有什麼差異
3.使用泛型
在瞭解類型信息之前,需要瞭解class對象
創建class對象,需要先查找這個這個類的.class文件, 查找到的class文件會以位元組碼的形式載入到記憶體, 這時便可以通過記憶體中的Class對象 創建這個類的所有對象
本章中創建對象的方式有三種
第一種: 通過new 構造器 的方式
第二種: Class cls = Class.forName(“全限定名”); cls.newInstance();
第三種 Class cls = 類:.class; cls.newInstance();
說3種比較牽強,第二種和第三種通過虛擬構造器newInstance()的方式創建了對象, newInstance()有2點需要註意: ①介面不能newInstance;②類必須有的空構造器
這三種方式有什麼差別呢,在這裡會牽涉到其他的知識點
1.調用一個類的靜態方法,有沒有創建了一個對象
答案是沒有的,因為沒有涉及到以上3種創建對象的方式,只是將.class文件載入到了記憶體,對類進行了初始化, 並沒有去創建對象
public class Test { public static void main(String[] args) { // TODO Auto-generated method stub A.print(); } } class A{ static{ System.out.println("static 靜態塊"); } public A(){ System.out.println("構造方法"); } public static void print(){ System.out.println("列印class A"); } }
列印的結果如下:
static 靜態塊
列印class A
2. Class.forName(“全限定名”) 和 .class有沒有差別
有差別, Class.forName會主動去在載入靜態方法塊, 而.class不會, .class對靜態方法或非常數靜態域首次引用後才進行初始化
public static void main(String[] args) { // TODO Auto-generated method stub try { Class.forName("chapter_14.A"); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
列印: static 靜態塊
public static void main(String[] args) { // TODO Auto-generated method stub Class cls = A.class; }
沒列印
提一點題外話, 為什麼我們平時看到的被訪問的靜態常量通常是這樣的
public static final int CONSTANT = 5;
而不是
public static int CONSTANT = 5;
原因在於static final 是編譯期常量, 類.CONSTANT 不需要對類進行初始化就可以被讀取
引入泛型的意義僅僅是為了提供編譯期檢查,第15章會著重講講泛型,在這裡提供了幾個概念
和平凡.class不一樣, 泛型newInstance()返回該對象的確切類型
class A{} class B extends A{} Class<A> clsA = A.class; A objA = clsA.newInstance();
那麼超類呢?
Class<? super B> clsA = B.class.getSuperclass(); Object objA = clsA.newInstance();
超類得到不是精確類型,而只是Object
那繼承類呢?
Class<? extends A> clsB = B.class; A objA = clsB.newInstance();
此時得到的是父類的類型,在接下來的學習中,我們會經常用這種方式來創建對象,說白了這屬於多態,屬於類型信息
二.類型轉換前先做檢查
問題:
1.類型轉換前先做的檢查的意義和檢查的方式
通過顯式的向下轉型,避免出現ClassCastException
常見的檢查方式有3種,instanceof, isInstance, isAssignableFrom
舉個例子:
class A implements Iface{} class B extends A{} interface Iface{}
第一種 instanceof
//類檢查 本身類實例化對象的類型 Class<? super B> clsA = B.class.getSuperclass(); Object objA = clsA.newInstance(); System.out.println(objA instanceof A);//true //類檢查 子類實例化對象的類型 Class clsB = B.class; Object objB = clsB.newInstance(); System.out.println(objB instanceof A);//true //介面檢查 實現類實例化對象的類型 System.out.println(objA instanceof Iface);//true System.out.println(objB instanceof Iface);//true
第二種:isInstance
Class<? super B> clsA = B.class.getSuperclass(); Object objA = clsA.newInstance(); Class clsB = B.class; Object objB = clsB.newInstance(); //類的Class對象檢查 類本身的實例化對象 System.out.println(clsA.isInstance(objA));//true System.out.println(clsB.isInstance(objB));//true //類的Class對象檢查 子類的實例化對象 System.out.println(clsA.isInstance(objB));//true //介面的Class對象檢查 實現類的實例化對象 Class clsIface = Iface.class; System.out.println(clsIface.isInstance(objA));//true System.out.println(clsIface.isInstance(objB));//true
第三種 isAssignableFrom
Class<? super B> clsA = B.class.getSuperclass(); Object objA = clsA.newInstance(); Class clsB = B.class; Object objB = clsB.newInstance(); //類的Class對象檢查 類本身的Class對象 System.out.println(clsA.isAssignableFrom(clsA));//true //類的Class對象檢查 子類的Class對象 System.out.println(clsA.isAssignableFrom(clsB));//true //介面的Class對象檢查 實現類的Class對象 Class clsIface = Iface.class; System.out.println(clsIface.isAssignableFrom(clsA));//true System.out.println(clsIface.isAssignableFrom(clsB));//true
對以上3種方式進行總結,便是以下這張圖了
類型檢查在另一方面也說明瞭 類與類的關係,類與介面的關係。
三.註冊工廠
問題:
1.註冊工廠有什麼用
註冊工廠是將工廠方法設計模式和添加融合在一起,在本章中,還是和類型信息有關係,在基類中添加實現類的對象,不過都是根據工廠設計模式去實現的,這樣做的好處在於“避免新添加的數據對結構產生破壞”。本章中的例子很形象,也非常好,如果我有好的例子,也一定會放上鏈接
四.空對象
問題:
1.什麼是空對象
2.使用空對象的意義
通常,空對象是一個單例,它具有無法修改的特性
假設一個類的 某個變數預設情況下是空對象,那麼想要改變這個變數的屬性,就需要重新創建一個對象來代替這個空對象,感覺我說的是廢話,不過這是空對象的本質了,結合下以下代碼,好好考慮下
interface Null{} class Person{ private final String first; private final String last; private final String address; public Person(String first, String last, String address) { super(); this.first = first; this.last = last; this.address = address; } @Override public String toString() { return "Person [first=" + first + ", last=" + last + ", address=" + address + "]"; } static class NullPerson extends Person implements Null{ private NullPerson(){ super("None", "None", "None"); } @Override public String toString() { return "NullPerson"; } } public static final Person NULL = new NullPerson(); }
這段代碼抄自Java編程思想的空對象一節,另外有個知識點:不是每個類都會有預設的空構造器,像上面的Person類其實是沒有空構造器的,問題在於構造器的參數用final修飾,可以去探究下。
五.反射
1.反射機制是怎麼樣的
2.如何通過動態代理的方式使用反射機制
反射是程式在運行時打開和檢查.class文件,因此反射是動態的,JDK中使用Class類和java.lang.reflect類庫對反射的概念進行了支持
使用反射是由於 某些類的屬性,方法對外 沒有包訪問許可權,而我們不得不進行訪問,才能完成一些事情
可想而知,包訪問許可權對反射而言起不了作用,這裡包括了private修飾, 私有內部類和匿名內部方法
應用到反射的例子有android中組件通信的EventBus,可以下載下來看源碼
反射也可以用於動態代理(多說一句,動態代理本質還是類型信息)主要代碼抄自書上
public class Test { public static void main(String[] args) { A objA = new A(); Iface iface = (Iface) Proxy.newProxyInstance(Iface.class.getClassLoader(), new Class[]{Iface.class}, new DynamicProxyHandler(objA)); doSomething(iface); } public static void doSomething(Iface iface){ iface.doSomething(); } } class A implements Iface{ @Override public void doSomething() { System.out.println("A doSomething"); } } interface Iface{ public void doSomething(); } //所有的調用都會重定向到這個單一的處理器上 class DynamicProxyHandler implements InvocationHandler{ private Object proxied; public DynamicProxyHandler(Object proxy){ proxied = proxy; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return method.invoke(proxied, args); } }
這裡有3點需要註意的:1..重定向單一的處理器,調用的對象是什麼; 2.創建的iface必須是介面對象,創建介面對象要傳遞的第二個參數是Class數組, 它包含了所有proxied的介面名稱.class ; 3.動態代理與類型信息之間的關係
總結: 類型信息本質上還是關於 向上轉型或者向下轉型