Java程式設計思想重點筆記
2. is-a關係和is-like-a關係
-
is-a關係屬於純繼承,即只有在基類中已經建立的方法才可以在子類中被覆蓋,如下圖所示:
基類和子類有著完全相同的介面,這樣向上轉型時永遠不需要知道正在處理的物件的確切型別,這通過多型來實現。 -
is-like-a關係:子類擴充套件了基類介面。它有著相同的基本介面,但是他還具有由額外方法實現的其他特性。
缺點就是子類中介面的擴充套件部分不能被基類訪問,因此一旦向上轉型,就不能呼叫那些新方法。
3. 執行時型別資訊(RTTI + 反射)
- 概念
RTTI:執行時型別資訊使得你可以在程式執行時發現和使用型別資訊。 -
使用方式
Java是如何讓我們在執行時識別物件和類的資訊的,主要有兩種方式(還有輔助的第三種方式,見下描述):- 一種是“傳統的”RTTI,它假定我們在編譯時已經知道了所有的型別,比如
Shape s = (Shape)s1;
- 另一種是“反射”機制,它執行我們在執行時發現和使用類的資訊,即使用
Class.forName()
。 - 其實還有第三種形式,就是關鍵字
instanceof
,它返回一個bool值,它保持了型別的概念,它指的是“你是這個類嗎?或者你是這個類的派生類嗎?”。而如果用==或equals比較實際的Class物件,就沒有考慮繼承—它或者是這個確切的型別,或者不是。
- 一種是“傳統的”RTTI,它假定我們在編譯時已經知道了所有的型別,比如
-
工作原理
要理解RTTI在Java中的工作原理,首先必須知道型別資訊在執行時是如何表示的Class物件
的特殊物件完成的,它包含了與類有關的資訊。Java送Class物件來執行其RTTI,使用類載入器的子系統實現。
無論何時,只要你想在執行時使用型別資訊,就必須首先獲得對恰當的Class物件的引用,獲取方式有三種:
(1)如果你沒有持有該型別的物件,則Class.forName()
就是實現此功能的便捷途,因為它不需要物件資訊;
(2)如果你已經擁有了一個感興趣的型別的物件,那就可以通過呼叫getClass()
方法來獲取Class引用了,它將返回表示該物件的實際型別的Class引用。Class包含很有有用的方法,比如:
package rtti;interface HasBatteries{} interface WaterProof{} interface Shoots{} class Toy { Toy() {} Toy(int i) {} } class FancyToy extends Toy implements HasBatteries, WaterProof, Shoots { FancyToy() { super(1); } } public class RTTITest { static void printInfo(Class cc) { System.out.println("Class name: " + cc.getName() + ", is interface? [" + cc.isInterface() + "]"); System.out.println("Simple name: " + cc.getSimpleName()); System.out.println("Canonical name: " + cc.getCanonicalName()); } public static void main(String[] args) { Class c = null; try { c = Class.forName("rtti.FancyToy"); // 必須是全限定名(包名+類名) } catch(ClassNotFoundException e) { System.out.println("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) { System.out.println("Can't Instantiate"); System.exit(1); } catch (IllegalAccessException e) { System.out.println("Can't access"); System.exit(1); } printInfo(obj.getClass()); } }
輸出:
Class name: rtti.FancyToy, is interface? [false]
Simple name: FancyToy
Canonical name: rtti.FancyToy
Class name: rtti.HasBatteries, is interface? [true]
Simple name: HasBatteries
Canonical name: rtti.HasBatteries
Class name: rtti.WaterProof, is interface? [true]
Simple name: WaterProof
Canonical name: rtti.WaterProof
Class name: rtti.Shoots, is interface? [true]
Simple name: Shoots
Canonical name: rtti.Shoots
Class name: rtti.Toy, is interface? [false]
Simple name: Toy
Canonical name: rtti.Toy
(3)Java還提供了另一種方法來生成對Class物件的引用,即使用類字面常量。比如上面的就像這樣:FancyToy.class;
來引用。
這樣做不僅更簡單,而且更安全,因為它在編譯時就會受到檢查(因此不需要置於try語句塊中),並且它根除了對forName方法的引用,所以也更高效。類字面常量不僅可以應用於普通的類,也可以應用於介面、陣列以及基本資料型別。
注意:當使用“.class”來建立對Class物件的引用時,不會自動地初始化該Class物件,初始化被延遲到了對靜態方法(構造器隱式的是靜態的)或者非final靜態域(注意final靜態域不會觸發初始化操作)進行首次引用時才執行:。而使用Class.forName時會自動的初始化。
為了使用類而做的準備工作實際包含三個步驟:
- 載入:由類載入器執行。查詢位元組碼,並從這些位元組碼中建立一個Class物件
- 連結:驗證類中的位元組碼,為靜態域分配儲存空間,並且如果必需的話,將解析這個類建立的對其他類的所有引用。
- 初始化:如果該類具有超類,則對其初始化,執行靜態初始化器和靜態初始化塊。
這一點非常重要,下面通過一個例項來說明這兩者的區別:
package rtti; import java.util.Random; class Initable { static final int staticFinal = 47; static final int staticFinal2 = ClassInitialization.rand.nextInt(1000); static { System.out.println("Initializing Initable"); } } class Initable2 { static int staticNonFinal = 147; static { System.out.println("Initializing Initable2"); } } class Initable3 { static int staticNonFinal = 74; static { System.out.println("Initializing Initable3"); } } public class ClassInitialization { public static Random rand = new Random(47); public static void main(String[] args) { // Does not trigger initialization Class initable = Initable.class; System.out.println("After creating Initable ref"); // Does not trigger initialization System.out.println(Initable.staticFinal); // Does trigger initialization(rand() is static method) System.out.println(Initable.staticFinal2); // Does trigger initialization(not final) System.out.println(Initable2.staticNonFinal); try { Class initable3 = Class.forName("rtti.Initable3"); } catch (ClassNotFoundException e) { System.out.println("Can't find Initable3"); System.exit(1); } System.out.println("After creating Initable3 ref"); System.out.println(Initable3.staticNonFinal); } }
輸出:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
- RTTI的限制?如何突破? — 反射機制
如果不知道某個物件的確切型別,RTTI可以告訴你,但是有一個限制:這個型別在編譯時必須已知,這樣才能使用RTTI識別它,也就是在編譯時,編譯器必須知道所有要通過RTTI來處理的類。
可以突破這個限制嗎?是的,突破它的就是反射機制。
Class
類與java.lang.reflect
類庫一起對反射的概念進行了支援,該類庫包含了Field
、Method
以及Constructor
類(每個類都實現了Member
介面)。這些型別的物件是由JVM在執行時建立的,用以表示未知類裡對應的成員。這樣你就可以使用Constructor
建立新的物件,用get()/set()
方法讀取和修改與Field
物件關聯的欄位,用invoke()
方法呼叫與Method
物件關聯的方法。另外,還可以呼叫getFields()、getMethods()和getConstructors()
等很便利的方法,以返回表示欄位、方法以及構造器的物件的陣列。這樣,匿名物件的類資訊就能在執行時被完全確定下來,而在編譯時不需要知道任何事情。
####反射與RTTI的區別
當通過反射與一個未知型別的物件打交道時,JVM只是簡單地檢查這個物件,看它屬於哪個特定的類(就像RTTI那樣),在用它做其他事情之前必須先載入那個類的Class
物件,因此,那個類的.class
檔案對於JVM來說必須是可獲取的:要麼在本地機器上,要麼可以通過網路取得。所以RTTI與反射之間真正的區別只在於:對RTTI來說,編譯器在編譯時開啟和檢查.class檔案(也就是可以用普通方法呼叫物件的所有方法);而對於反射機制來說,.class檔案在編譯時是不可獲取的,所以是在執行時開啟和檢查.class檔案。
下面的例子是用反射機制打印出一個類的所有方法(包括在基類中定義的方法):
package typeinfo; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.regex.Pattern; // Using reflection to show all the methods of a class. // even if the methods are defined in the base class. public class ShowMethods { private static String usage = "usage: \n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or: \n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving 'word'"; private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if(args.length < 1) { System.out.println(usage); System.exit(0); } int lines = 0; try { Class<?> c = Class.forName(args[0]); Method[] methods = c.getMethods(); Constructor[] ctors = c.getConstructors(); if(args.length == 1) { for(Method method : methods) { System.out.println(p.matcher(method.toString()).replaceAll("")); } for(Constructor ctor : ctors) { System.out.println(p.matcher(ctor.toString()).replaceAll("")); } lines = methods.length + ctors.length; } else {相關推薦
java程式設計思想重點筆記(java程式設計師必看)
Java中的多型性理解(注意與C++區分) Java中除了static方法和final方法(private方法本質上屬於final方法,因為不能被子類訪問)之外,其它所有的方法都是動態繫結,這意味著通常情況下,我們不必判定是否應該進行動態繫結—它會自動發生。 fin
Java程式設計思想重點筆記(Java開發必看)
Java程式設計思想,Java學習必讀經典,不管是初學者還是大牛都值得一讀,這裡總結書中的重點知識,這些知識不僅經常出現在各大知名公司的筆試面試過程中,而且在大型專案開發中也是常用的知識,既有簡單的概念理解題(比如is-a關係和has-a關係的區別),也有深入的涉及RTTI和JVM底層反編譯知識。
Java程式設計思想重點筆記(Java開發必看)
1. Java中的多型性理解(注意與C++區分) Java中除了static方法和final方法(private方法本質上屬於final方法,因為不能被子類訪問)之外,其它所有的方法都是動態繫結,這意味著通常情況下,我們不必判定是否應該進行動態繫結—它會自動發生。 fi
Java程式設計思想重點筆記
2. is-a關係和is-like-a關係 is-a關係屬於純繼承,即只有在基類中已經建立的方法才可以在子類中被覆蓋,如下圖所示: 基類和子類有著完全相同的介面,這樣向上轉型時永遠不需要知道正在處理的物件的確切型別,這通過多型來實現。 is-like-a關係:子類擴充套件了基類介
Java程式設計思想學習筆記-第11章
.title { text-align: center; margin-bottom: .2em } .subtitle { text-align: center; font-size: medium; font-weight: bold; margin-top: 0 } .todo { font-famil
JAVA程式設計思想學習筆記(一)
物件導論 1.1 抽象過程 Smalltalk的五個基本特性: 萬物皆為物件。 程式是物件的集合,它通過傳送訊息來告知彼此所要做的。 每個物件都有自己的由其他物件所構成的儲存。 每個物件都有其型別。 某一特定型別的所有物件都可以接受同樣的訊息。
java程式設計思想學習筆記——第1章 物件導論
1.1 抽象過程 面向物件思想的實質:程式可以通過新增新型別的物件使自身適用於某個特定問題。 面向物件思想的五個基本特徵: 1)萬物皆物件 2)程式是物件的集合 3)每個物件都有自己的由其他物件所構成的儲存 4)每個物件都有其型別 5)某一特定型別的所有物件都可以接收同樣的訊息 物件具有行為、
《Java程式設計思想》筆記14.型別資訊
執行時型別資訊使得你可以在執行時發現和使用型別資訊,主要有兩種方式: “傳統的”RTTI,它假定我們在編譯時已經知道了所有的型別; “反射”機制,它允許我們在執行時發現和使用類的資訊。 14.1 為什麼需要RTTI RTTI維護型別型別的資訊,為多型機制的實現提供基礎。 14.2 Cla
java程式設計思想學習筆記——第2章 一切都是物件
儘管java是基於C的,但是相比之下,java是一種更“純粹”的面向物件程式設計語言。 2.1 用引用操縱物件 一切都視為物件,因此可採用單一固定的語法。儘管這一切都看作物件,但操縱的識別符號實際上是物件的一個“引用(reference)”。 java語言的一個特性:字串可以用帶引號的文字初始化。通常,
java程式設計思想讀書筆記二(物件的建立)
java物件 物件的建立 java的物件是在執行時建立的,建立物件的的觸發條件有以下幾種: 用new語句建立物件,這是最常用的建立物件方法。 運用反射手段,呼叫java.lang.reflect.Constructor類的newInstance()例項方法。
java程式設計思想讀書筆記一(面向物件)
面向物件 我們常見的程式設計正規化有指令式程式設計,函數語言程式設計,邏輯式程式設計,而面向物件程式設計也是一種指令式程式設計。 指令式程式設計式面向計算機硬體的一種抽象,有變數(儲存單元),賦值語句(獲取儲存指令),表示式(記憶體引用和算術運算)和控制語句(跳轉指令),命令式程
java程式設計思想讀書筆記三(HashMap詳解)
Map Map介面規定了一系列的操作,作為一個總規範它所定義的方法也是最基礎,最通用的。 AbstractMap AbstractMap是HashMap、TreeMap,、ConcurrentHashMap 等類的父類。當我們巨集觀去理解Map時會發現,其實Map就是一
Java程式設計思想讀書筆記(一)第1~13、16章
目錄: 第1章 物件導論 1.1 伴隨多型的可互換物件 面向物件程式設計語言使用了後期繫結的概念。當向物件傳送訊息時,被呼叫的程式碼直到執行時才能確定。也叫動態繫結。 編譯器確保被呼叫方法的存在,並對呼叫引數和返回值執行型別檢查(Java是強型別的語言,無法
java程式設計思想學習筆記:初始&過載&this&static
構造器初始化 構造器 ,採用與類相同的名稱,建立物件時,自動呼叫構造器來初始化物件 預設構造器(無參構造器):不接受任何引數的構造器 構造器也可帶引數,指定如何建立物件 當類中沒有構造器,編譯器會自動建立一個預設構造器; 當類中已經定義了一個構造
JAVA程式設計思想學習筆記(三)操作符
操作符 別名問題 先來看段程式碼,猜猜最後輸出的是什麼: class Test{ int t; } public class A { public static void main(String[] args) { // TODO Auto-gener
Java程式設計思想讀書筆記_8_多型
如果一種語言想實現後期繫結,就必須具有某種機制以便在執行時能判斷物件的型別,從而呼叫恰當的方法,也就是說,編譯器不知道物件的型別,但是方法呼叫機制能找到並呼叫正確的方法體; Java中除了static方法和final方法(private方法也屬於final方法)以外,其它的方法都是後期繫結的;(因此以前還可
【Java】《Java程式設計思想》筆記(含練習題答案程式碼)-第二章 一切都是物件
2.1 用引用操縱物件【String】 遙控器(引用)- 電視機(資料)建立String引用,如果向無物件的引用傳送資訊會返回一個執行時錯誤,所以安全的做法是:建立一個引用的同時便進行初始化Stri
java程式設計思想讀書筆記 第十五章 泛型 (匿名內部類和擦除)
1.匿名內部類 泛型還可以應用於內部類以及匿名內部類。下面的例子使用匿名內部類實現了Generator介面: public class Customer { private static long counter = 1; private f
JAVA程式設計思想學習筆記(八)介面
介面 抽象類和抽象方法 抽象方法:這種方法不完整,僅有宣告而沒有方法體。所採用的語法如下: abstract void f(); 抽象類:包含抽象方法的類叫做抽象類,如果一個類包含一個或多個抽象方法,該類必須被限定為抽象的。 介面 關鍵字:interface 介面定
JAVA程式設計思想學習筆記(七)多型
多型 繫結 繫結: 將一個方法呼叫同一個方法主體關聯起來被稱作繫結。 前期繫結: 若在程式執行前進行繫結,叫做前期繫結,它是面嚮物件語言不需要選擇就預設的繫結方式。 後期繫結: 它的含義就是在執行時根據物件的型別進行繫結,也叫做動態繫結或執行時繫結。java中除了static和fin