4 泛化的 Class
引用
點擊上方「藍字」關注咱們java
在運行期識別對象和類的信息有兩種方式:
編程
「傳統」 RTTI數組
編譯時就已知全部的類型安全
反射機制服務器
運行時發現和使用類的信息微信
1 爲何須要 RTTI
面向對象編程的一個基本目的:代碼只操縱對基類(這裏即 Shape
)的引用。所以一般建立一個具體的對象(Circle
、Square
或者 Triangle
),把它向上轉型成 Shape
,忽略對象的具體類型,而且在後面的程序中使用 Shape
引用來調用在具體對象中被重載的方法。網絡
在把 Shape
對象放入 Stream<Shape>
中時就會進行隱式向上轉型,但在向上轉型的時候也丟失了這些對象的具體類型。對 stream
而言,它們只是 Shape
對象。app
Stream<Shape>
其實是把放入其中的全部對象都當作 Object
,只是取元素自動將類型轉爲 Shape
。這也是 RTTI 最基本的使用形式ide
全部類型轉換的正確性檢查都是在運行時。這也正是 RTTI 的含義所在:在運行時,識別一個對象的類型。flex
該例中,類型轉換並不完全:Object
被轉型爲 Shape
,而不是 Circle
、Square
或者 Triangle
。這是由於目前咱們只能確保這個 Stream<Shape>
保存的都是 Shape
:
編譯期,
stream
和 Java 泛型系統確保放入stream
的都是Shape
對象(Shape
子類的對象也可視爲Shape
的對象),不然編譯器會報錯運行時,自動類型轉換確保了從
stream
中取出的對象都是Shape
類型
Shape
對象實際執行的代碼,由引用的具體對象決定。
一般,咱們但願大部分代碼儘量少了解對象具體類型,而是隻與對象家族中的一個通用的高層抽象打交道。這樣,代碼更易讀和維護;設計也更容易實現,更易於理解和修改。
但有時但願知道 Stream<Shape>
裏邊的形狀具體類型。使用 RTTI,便可查詢某個 Shape
引用所指向對象的確切類型。
2 2 Class 對象
由 Class
特殊對象完成,它包含了與類有關的信息。實際上,Class
對象就是用來建立該類全部」常規」對象的。Java 使用 Class
對象來實現 RTTI,即使是類型轉換這樣的操做都是用 Class
對象實現的。跟其餘普通對象同樣,咱們也能夠控制它的引用。
類是程序的一部分,每一個類都有一個 Class
對象。每編譯一個新類,就產生一個 Class
對象,即保存在一個同名 .class
文件。
爲了生成這個類的對象,JVM先會調用」類加載器」把該類加載到內存。
類加載器子系統包含一條類加載器鏈,但有且只有一個原生類加載器。
原生類加載器加載的是」可信類」(如Java API),一般從本地盤加載。在這條鏈中,一般不須要添加額外的類加載器,可是若是你有特殊需求(例如以某種特殊的方式加載類,以支持 Web 服務器應用,或者經過網絡下載類),也能夠掛載額外的類加載器。
類在第一次被使用時動態加載到JVM,即程序建立第一個對類的靜態成員的引用時。構造器(new 時調用)實質也是靜態方法。因此Java 程序不少部分在須要時才加載。
類加載器首先檢查類的 Class
對象是否已加載
還沒有加載,則默認類加載器根據類名查找
.class
文件類的字節碼被加載後,JVM 會對其進行驗證
一旦某個類的 Class
對象被載入內存,就可用來建立該類的全部對象。
結果
可見Class
對象僅在須要的時候纔會被加載,static
初始化是在類加載時進行的。
2.3 Class.forName(className)
Class
類的靜態方法,可使用其根據類名獲得Class
對象:
上面程序forName()
調用是爲了獲得它產生的「反作用」:若是 Gum
類未被加載,那麼就加載之。因而在加載的過程當中,Gum
的 static
初始化塊也被執行了。
想在運行時使用類型信息,就必須獲得其 Class
對象的引用。
使用該方法無需先持有這個類型的對象。
若是已持有類對象,可調用 getClass()
方法來獲取 Class
引用:
這個方法來自根類 Object
,它將返回表示該對象實際類型的 Class
對象的引用。產生完整類名。
getSimpleName()
產生不帶包名的類名
getCanonicalName()
產生完整類名,除內部類和數組,對類產生的結果與 getName()
相同
isInterface()
判斷某個 Class
對象表明的是否爲接口
Class.getInterfaces()
返回存放 Class
對象的數組,裏面的 Class
對象表示的是那個類實現的接口。
getSuperclass()
獲得父類的 Class
對象,父類的 Class
對象繼續調用可得完整的類繼承結構。
newInstance()
實現「虛擬構造器」:在不知類的確切類型時,建立該類的對象
使用 newInstance()
來建立的類,必須帶無參構造器
3 類字面常量
另外一種生成類對象的引用之法。
形如
優勢
簡單安全,由於編譯時就會檢查(所以沒必要放在 try
中)
相比 forName()
方法調用,效率也更高。
適用範圍
普通類、接口、數組及基本數據類型。
TYPE
對於基本數據類型的包裝類,還有一個字段 TYPE
。
TYPE
字段是一個引用,指向對應的基本數據類型的 Class
對象
…等價於… | |
---|---|
boolean.class | Boolean.TYPE |
char.class | Character.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
建議使用 .class
形式,以保持統一。
初始化有效地實現了儘量的「惰性」
-
使用
.class
語法建立對 Class 對象的引用時,不會自動初始化該 Class 對象 -
Class.forName()
產生Class
引用,會當即進行對象的初始化
若是一個 static final
值是「編譯期常量」,那麼不須要初始化所在類就可被讀取。以下:
但只將字段設爲 static
final
,並不足以確保這種行爲。例如,對 Initable.staticFinal2
的訪問將強制進行類的初始化,由於它不是一個編譯期常量。以下:
若是一個 static
字段非 final
,那麼在對它訪問時,總要求在它被讀取前,先進行連接(爲字段分配存儲空間)和初始化(初始化該存儲空間)
Class
對象可產生類的實例,包含可做用於這些實例的方法,還包含類的 static
成員,所以咱們說 Class
引用代表了它所指向對象的確切類型:Class
類的一個對象。
Java 設計者將它的類型變得更具體。Java 引入泛型,限定 Class
引用所指向的 Class
對象的類型。
普通的類引用不會產生警告信息,能夠從新賦值任何其餘的 Class
對象
可是當使用泛型限定類引用後,只能指向其聲明的類型,讓編譯器強制執行額外的類型檢查。
放寬限制
這彷佛起做用,由於 Integer
繼承自 Number
。如上所見其實不行,由於 Integer
的 Class
對象並非 Number
的 Class
對象的子類。
爲了在使用
Class
引用時放鬆限制,使用通配符?
,表示「任何事物」
使用 Class<?> 代替 Class
雖然它們等價,且單純使用 Class
不會產生警告。
Class<?>
的好處:表示你並不是是碰巧或者疏忽才使用了一個非具體的類引用,而是故意的。
限定類型的 Class 引用
要建立指向限定類型的 Class
引用,須要搭配通配符與 extends
,建立範圍限定。這與僅僅聲明 Class<Number>
是不一樣的!
Class
引用添加泛型只是爲了提供編譯期類型檢查。而使用普通的 Class
引用,一旦你犯了錯誤,就要到運行時才能發現。
下面的示例使用了泛型語法,它保存了一個類引用,稍後又用 newInstance()
方法產生類的對象:
package typeinfo.toys;
import java.util.function.*;import java.util.stream.*;
class CountedInteger { private static long counter; private final long id = counter++;
public String toString() { return Long.toString(id); }}
public class DynamicSupplier<T> implements Supplier<T> { private Class<T> type; public DynamicSupplier(Class<T> type) { this.type = type; } public T get() { try { return type.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } }
public static void main(String[] args) { Stream.generate( new DynamicSupplier<>(CountedInteger.class)) .skip(10) .limit(5) .forEach(System.out::println); }}
將泛型語法用於 Class
對象時,newInstance()
將返回該對象的確切類型,而不只是Object
。
然而,這有些受限:
若是你想獲得超類,那編譯器將只容許你聲明超類引用爲「某個類,它是 FancyToy
的超類」,即 Class<? super FancyToy>
,不會接受Class<Toy
這看上去很怪,由於 getSuperClass()
返回的是基類,編譯器在編譯期就知道它的類型( Toy.class
),而不只僅只是」某個類」。
正是這種含糊性,up.newInstance
的返回值不是精確類型,而是 Object
掃碼二維碼
獲取更多精彩
JavaEdge
本文分享自微信公衆號 - JavaEdge(Java-Edge)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。