在 Java 中,并不是所有的型別資訊都能在編譯階段明確,有一些型別資訊需要在運行時才能確定,這種機制被稱為 RTTI,英文全稱為 Run-Time Type Identification,即運行時型別識別,有沒有一點“知行合一”的味道?運行時型別識別主要由Class類實作,
在日常的學習作業當中,有一些知識是我們在讀書的時候就能夠習得;但有一些知識不是的,需要在實踐的時候才能得到真知——這或許就是王陽明提倡的“知行合一”,
01、 Class類
在Java中,我們常用“class”(首字母為小寫的c)關鍵字來定義一個類,說這個類是對某一類物件的抽象,你比如說王二是一個網路知名作者,我們可以這樣簡單地定義作者類:
package com.cmower.java_demo.fifteen;
class Author {
private String pen_name;
private String real_name;
}
現在,我們想知道Writer這個類本身的一些資訊(比如說類名),該怎么辦呢?這時候就需要用到“Class”(首字母為大寫的C)類,該類包含了與類有關的資訊,請看以下代碼:
public class Test {
public static void main (String [] args) {
Author wanger = new Author();
Class c1 = wanger.getClass();
System.out.println(c1.getName());
//輸出 com.cmower.java_demo.fifteen.Author
}
}
當我們創建了作者物件wanger后,就可以通過wanger.getClass()獲取wanger的Class物件,通過c1.getName()可獲得wanger物件的類名,
想象一下,經過五年的刻意練習,王二從一名寫作愛好者晉升為一名作家了,我們用代碼來假裝一下:
package com.cmower.java_demo.fifteen;
class Author {
private String pen_name;
private String real_name;
}
class Writer extends Author {
private String honour;
}
public class Test {
public static void main (String [] args) {
Author wanger = new Writer();
Class c1 = wanger.getClass();
System.out.println(c1.getName());
//輸出 com.cmower.java_demo.fifteen.Writer
}
}
在上例中,即使我們將Writer的物件參考wanger向上轉型為Author,wanger的Class物件型別依然是Writer(通過輸出結果可以判定),這也就是說,Java能夠在運行時自動識別型別的資訊,它不會因為wanger的參考型別是Author而丟失wanger真正的型別資訊(Writer),Java是怎么做到這一點呢?
當Java創建某個類的物件,比如Writer類物件時,Java會檢查記憶體中是否有相應的Class物件,如果記憶體中沒有相應的Class物件,那么Java會在.class檔案中尋找Writer類的定義,并加載Writer類的Class物件,
一旦Class物件加載成功,就可以用它來創建這種型別的所有物件,這也就是說,每個物件在運行時都會有對應的Class物件,這個Class物件包含了這個物件的型別資訊,因此,我們能夠通過Class物件知道某個物件“真正”的型別,并不會因為向上轉型而丟失,
02、 獲取Class物件的其他方式
在使用getClass()方法獲取一個類的Class物件時,我們必須要先獲取這個類的物件,比如上面提到的wanger,如果我們之前沒有獲取這個類的物件,就需要用另外兩種方式來獲取類的Class物件:
Class c2 = Writer.class;
System.out.println(c2.getName());
try {
Class c3 = Class.forName("com.cmower.java_demo.fifteen.Writer");
System.out.println(c3.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
1)當使用.class來獲取Class物件時,不會自動地初始化該Class物件,初始化被延遲到了對靜態方法或者非final靜態域進行首次參考時才執行,這樣做不僅更簡單,而且更安全,因為它在編譯時就會受到檢查(因此不需要置于try陳述句塊中),
2)Class.forName會自動地初始化該Class物件,但需要指定類名,并且需要置于try陳述句塊中,
03、 Class類提供的常用方法
Class類為我們提供了一些非常有用的方法,比如說getName()用來回傳類名,getPackage()回傳類所在的包名,
我們還可以利用Class類提供的newInstance()方法來創建相應類的物件,比如:
Class c2 = Writer.class;
System.out.println(c2.getName());
try {
Writer wangsan = (Writer) c2.newInstance();
System.out.println(wangsan);
// 輸出:com.cmower.java_demo.fifteen.Writer@7852e922
} catch (InstantiationException | IllegalAccessException e1) {
e1.printStackTrace();
}
由于我們在創建Class物件c2時沒有使用泛型,所以newInstance()回傳的物件型別需要強轉為Writer,我們可以在此基礎上進行改進,示例如下:
Class<Writer> c4 = Writer.class;
System.out.println(c4.getName());
try {
Writer wangsan = c4.newInstance();
System.out.println(wangsan);
// 輸出:com.cmower.java_demo.fifteen.Writer@7852e922
} catch (InstantiationException | IllegalAccessException e1) {
e1.printStackTrace();
}
04、 反射
我們還可以通過getFields()獲取所有public修飾的欄位,通過getMethods()回傳所有public修飾的方法,
甚至,我們還可以通過getDeclaredFields()獲取更多欄位,包括公共、受保護、默認(包)訪問和私有欄位,但不包括繼承欄位,對應的,getDeclaredMethods()用來獲取更多方法,示例如下:
package com.cmower.java_demo.fifteen;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Author {
private String pen_name;
private String real_name;
}
class Writer extends Author {
private String honour;
private void makeMoney() {
System.out.println("很多很多錢");
}
}
public class Test {
public static void main(String[] args) {
Class<Writer> c4 = Writer.class;
System.out.println(c4.getName());
try {
Writer wangsan = c4.newInstance();
System.out.println(wangsan);
Field[] fields = c4.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
Method[] methods = c4.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
} catch (InstantiationException | IllegalAccessException e1) {
e1.printStackTrace();
}
}
}
上面的例子其實涉及到了反射,Field、Method(還有例子中未提到的Constructor)都來自java.lang.reflect類別庫,Class類與java.lang.reflect類別庫一起對反射的概念進行了支持,
有時候,我們需要從磁盤檔案或網路檔案中讀取一串位元組碼,并把它轉換成一個類,這時候就需要用到反射,最常見的典型例子就是將一串JSON字串(在網路傳輸中最初的形態可能是位元組陣列)反射為對應型別的物件,
阿里巴巴提供的FastJSON提供了 toJSONString() 和 parseObject() 方法來將 Java 物件與 JSON 相互轉換,呼叫toJSONString方法即可將物件轉換成 JSON 字串,parseObject 方法則反過來將 JSON 字串轉換成物件,FastJSON的內部其實用的就是反射機制,
package com.cmower.common.util;
import java.io.UnsupportedEncodingException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.alibaba.fastjson.JSON;
@SuppressWarnings("all")
public class JsonUtil {
private static Log logger = LogFactory.getLog("json");
public static byte[] objectToByte(Object obj) throws UnsupportedEncodingException {
String jsonStr = JSON.toJSONString(obj);
logger.debug("序列化后資料:" + jsonStr);
return jsonStr.getBytes("UTF-8");
}
public static <T> T byteToObject(byte[] data, Class<T> obj) throws UnsupportedEncodingException {
String objectString = new String(data, "UTF-8");
logger.debug("反序列化后資料 : " + objectString);
return JSON.parseObject(objectString, obj);
}
public static <T> Object stringToObject(String data, Class<T> obj) throws UnsupportedEncodingException {
logger.debug("反序列化后資料 : " + data);
return JSON.parseObject(data, obj);
}
}
本文首發于java黑洞網,博客園同步更新
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/265813.html
標籤:其他
