RTTI(運行時型別資訊):
運行時型別資訊可以讓你在程式運行時發現和使用型別資訊,主要用來運行時獲取向上轉型之后的物件到底是什么具體的型別,
如下實體:

上方類圖分析
基類Vehicle位于頂部,Car、Bus、Bike 3個派生類向下擴展,面向物件編程很重要的目的是:讓代碼只操作基類的參考,就可以達到我們想要的效果,而不必去直接操作派生類;如果我們想再增加一個派生類 MotorBike,則直接對此類進行擴展即可,無需改變原來的代碼邏輯,
Vehicle類
abstract class Vehicle
{
abstract void run();
}
Car類
public class Car extends Vehicle
{
@Override
void run()
{
System.out.println("小汽車開動");
}
}
Bus類
public class Bus extends Vehicle
{
@Override
void run()
{
System.out.println("公交車開動");
}
}
Bike類
public class Bike extends Vehicle
{
@Override
void run()
{
System.out.println("自行車騎行");
}
}
呼叫
public static void main(String[] args)
{
//均向上轉型
Vehicle vehicle1 = new Car();
vehicle1.run();
Vehicle vehicle2 = new Bus();
vehicle2.run();
Vehicle vehicle3 = new Bike();
vehicle3.run();
}
輸出
小汽車開動
公交車開動
自行車騎行
從上面的代碼可以看到,我們無需直接呼叫派生類,只要將派生類的實體向上轉型為基類,然后通過基類來呼叫(派生類會丟失具體的型別),
有個要注意的問題,不能向下轉型呼叫,如:
public class BYDCar extends Car
{
@Override
void run()
{
System.out.println("BYD小汽車開動");
}
}
public static void main(String[] args)
{
//BYDCar是Car的子類,這里用了向下轉型
BYDCar bydCar =(BYDCar)new Car();
bydCar.run();
}
BYDCar是Car的子類,代碼里用了向下轉型,編譯是通過的,不會報錯,但運行會報錯:
Exception in thread "main" java.lang.ClassCastException: rtti.Car cannot be cast to rtti.BYDCar
at rtti.Test1.main(Test1.java:11)
Java中所有的型別轉換都是在運行時進行正確性檢查的,也就是RTTI:在運行時,識別一個物件的型別,
Class物件
在JAVA中,每個類都有一個Class物件,它用來創建這個類的所有物件,換言之,每個類的所有物件都會關聯同一個Class物件,
Class 沒有公共構造方法,Class 物件是在加載類時由 Java 虛擬機以及通過呼叫類加載器中的 defineClass 方法自動構造的,因此不能顯式地宣告一個Class物件,
實際上所有的類都是在對其第一次使用時動態加載到JVM中的,需要用到Class時,類加載器首先會檢查這個類的Class物件是否已經加載,如果尚未加載,默認的類加載器就會根據類名查找到.class檔案,接下來是驗證階段:加載時,它們會接受驗證,以確保其沒有被破壞,并且不包含不良Java代碼,
實體來看Class物件的加載
package rtti;
public class A
{
static {
System.out.println("A is load");
}
}
package rtti;
public class Test2
{
public static void main(String[] args)
{
System.out.println("before load");
new A();
System.out.println("after load");
System.out.println("before load1");
try {
Class a=Class.forName("rtti.A");
} catch (ClassNotFoundException e) {
System.out.println("Couldn't find A");
}
System.out.println("after load1");
}
}
輸出如下
before load
A is load
after load
before load1
after load1
從輸出中可以看到,static模塊在類第一次被加載時候被執行,Class物件僅在需要的時候才被加載,static初始化是在類加載時進行的,
如何驗證同一個類的多個物件的Class物件是一個呢?
可以用A的Class物件與A的實體的物件的Class物件進行比較( == ),因為==用來比較參考是否相同,
public static void main(String[] args)
{
A a = new A();//A的實體物件
Class clazz = A.class;//A的Class物件
Class clazz1 = a.getClass();//實體物件a的Class物件
System.out.println(clazz==clazz1);
}
運行后結果是true,所以說明同一個類的多個物件的Class物件是同一個,
獲取 Class物件
1、Class.forName("類名字串")
2、類名.class
3、實體物件.getClass()
Class.forName
forName是取得Class物件參考的一種方法,傳入一個類的完整類路徑也可以獲得Class 物件,如果找不到你想要加載的類,就會拋出ClassNotFoundException例外,所以用try、catch來捕獲這個方法可能拋出的ClassNotFoundException例外,
public static void main(String[] args)
{
try {
Class a=Class.forName("rtti.A");
System.out.println(a);
} catch (ClassNotFoundException e) {
System.out.println("Couldn't find A");
}
}
輸出:class rtti.A
類名.class
這種方式叫類字面常量,這樣來獲取不僅更簡單,還更安全,因為它在編譯時就會檢查,
字面常量的獲取Class物件參考方式不僅可以應用于普通的類,也可以應用用介面,陣列以及基本資料型別,由于基本資料型別還有對應的基本包裝型別,其包裝型別有一個標準欄位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 |
public static void main(String[] args)
{
try {
Class a=Class.forName("rtti.A");
Class a1 = A.class;
System.out.println(a==a1);
System.out.println(Integer.TYPE==int.class);
System.out.println(Boolean.TYPE==boolean.class);
System.out.println(Double.TYPE==double.class);
System.out.println(Void.TYPE==void.class);
} catch (ClassNotFoundException e) {
System.out.println("Couldn't find A");
}
}
全部輸出是true,這里沒有把基本型別的其他幾種全部寫上,
實體物件.getClass()
public static void main(String[] args)
{
try {
Class clazz1=Class.forName("rtti.A");
Class clazz2 = A.class;
A a = new A();
Class clazz3 = a.getClass();//getClass 方式
System.out.println(clazz1==clazz2);
System.out.println(clazz1==clazz3);
} catch (ClassNotFoundException e) {
System.out.println("Couldn't find A");
}
}
上面3種方法都是可以得到Class物件的參考,
Class的常用方法
| forName(String name) | 靜態方法,傳入的引數是一個類的完整類路徑的字串,回傳這個類的Class 物件, |
| forName(String name, boolean initialize, ClassLoader loader) | 使用給定的類加載器,回傳與帶有給定字串名的類或介面相關聯的 Class 物件, |
| asSubclass(Class<U> clazz) | 強制轉換該 Class 物件,以表示指定的 class 物件所表示的類的一個子類, |
| cast(Object o) | 這個方法是將傳入的物件強制轉換成Class 物件所代表的型別的物件, |
| getCanonicalName() | 回傳 Java Language Specification 中所定義的底層類的規范化名稱, |
| getClasses() | 回傳一個包含某些 Class 物件的陣列,這些物件表示屬于此 Class 物件所表示的類的成員的所有公共類和介面, |
| getClassLoader() | 回傳該類的類加載器, |
| getConstructor(Class<?>... parameterTypes) | 回傳一個 Constructor 物件,它反映此 Class 物件所表示的類的指定公共構造方法, |
| getConstructors() | 回傳一個包含某些 Constructor 物件的陣列,這些物件反映此 Class 物件所表示的類的所有公共構造方法, |
| getFields() | 獲取Class物件的所有公有成員屬性的java.lang.reflect.Field陣列, |
| getField(String name) | 按照欄位名稱獲取公有欄位的Field物件,注意name是區分大小寫的, |
| getDeclaredFields() | 獲取Class物件的所有成員屬性的Field陣列; |
| getDeclaredField(String name) | 按照欄位名稱獲取所有欄位的Field 物件,注意name是區分大小寫的, |
| getMethods() | 獲取Class 物件的公有方法(以及從父類繼承的方法,但不包含構造方法)的java.lang.reflect.Method陣列 |
| getMethod(String name,Class<?> …parameterTypes) | 按照name 指定的方法名稱,parameterTypes 指定的可變陣列獲取公有方法(以及從父類繼承的方法,但不包含構造方法)的Method物件,注意name 是區分大小寫的, |
| getDeclaredMethods() | 獲取Class 物件的所有方法(不包含父類繼承的方法,構造方法)的Method陣列, |
| getDeclaredMethod(String name,Class<?> …parameterTypes) | 按照name指定的方法名稱,parameterTypes 指定的可變陣列獲取所有方法(不包含父類繼承的方法,構造方法)的Method物件,注意name 是區分大小寫的, |
| getName() | 以String 形式回傳Class 物件所表示的物體的完整類名,基本資料型別 回傳自身,陣列型別(以String 陣列為例)回傳[Ljava.lang.String;,這個方法沒有 getCanonicalName()回傳的完整,但不是所有的型別都有底層的規范化名稱, |
| getPackage() | 以java.lang.reflect.Package形式回傳Class物件的類所在的包,基本數 據型別、陣列拋出例外, |
| getResource(String url) | 查找帶有給定名稱的資源 |
public class A
{
int a=0;
int b=1;
public void say()
{
System.out.println("say");
}
public void hello()
{
System.out.println("hello");
}
}
public static void main(String[] args)
{
try {
Class clazz1=Class.forName("rtti.A");
System.out.println(clazz1.getName());//列印類名
System.out.println(clazz1.getSimpleName());//獲取類名(不包括包名)
System.out.println(clazz1.getCanonicalName());//獲取類名(包括包名)
System.out.println("------------------------");
//獲取自身的屬性
for (Field f : clazz1.getDeclaredFields()) {
System.out.println(f.getName());
}
System.out.println("------------------------");
//方法,包含繼承來的方法
for (Method m : clazz1.getMethods()){
System.out.println(m.getName());
}
} catch (ClassNotFoundException e) {
System.out.println("Couldn't find A");
}
}
關注下方公眾號,有更多資料、實體代碼、面試技巧奉上!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/277167.html
標籤:java
