筆記序言
作爲一個學習Java的人怎麼能不看一遍Java的Bible呢?在這之前刷過《Head First Java》,但始終覺得不夠全面,而且因爲是‘刷’,就沒有留下任何筆記。於是趁噹噹搞活動馬上買了一本《Thinking in Java》回來,開卷之餘,順手就寫下這個筆記。翻開目錄一共二十二章,暫時打算每五章發一篇筆記,因爲視情況可能會跳過圖形化界面的章節。
第一章 對象導論
-
訪問權限關鍵字(access specifier) : public, private, protected
飾詞 本類 同一個包的類 子類 其他類 public √ √ √ √ protected √ √ √ × default √ √ × × private √ × × × -
單根繼承結構
Java中所有的類最終都繼承自單一的基類(Object)。 -
對象的創建和生命週期
Java採用動態內存分配方式,通過new關鍵字來構建對象的動態實例(基本類型是特例),所有的對象都在堆 (Heap)中分配空間。當“垃圾回收器”的機制發現對象不再被使用時,會銷燬對象,自動釋放對象佔用的內存。
第二章 一切都是對象
-
內存分配
(1) 寄存器:最快的存儲區,位於處理器內部,根據需求進行分配,在Java中不能直接控制。
(2) 堆棧:位於通用RAM(隨機訪問存儲器)中,堆棧指針下移分配內存,上移釋放內存。存放基本類型的變量數據和對象的引用,但對象本身不存放在棧中,而是存放在堆(new 出來的對象)或者常量池中(字符串常量對象存放在常量池中)。
(3) 堆:一種通用的內存池(也位於RAM區),用於存放所有的Java對象。
(4) 常量儲存:常量值通常直接放在程序代碼的內部,常量池存放字符串常量和基本類型常量(public static final)。
(5) 非RAM存儲:儲存於硬盤等永久存儲空間。基本例子:流對象&持久化對象。 -
基本類型
Java每種基本類型所佔存儲空間的大小具有不變性,所有數值類型都有正負號,沒有無符號類型。基本類型 大小 最小值 最大值 包裝器類型 默認值 boolean - - - Boolean false char 16-bit Unicode 0 Unicode 2^16-1 Character ‘\u0000’(null) byte 8bits -128 +127 Byte (byte)0 short 16bits -215 +215-1 Short (short)0 int 32bits -231 +231-1 Integer 0 long 64bits -263 +263-1 Long 0L float 32bits IEEE754 IEEE754 Float 0.0f double 64bits IEEE754 IEEE754 Double 0.0d void - - - Void 高精度計算類:BigInteger & BigDecimal
-
數組
Java數組對象自動初始化爲null,範圍檢查保證了數組無法越界訪問。 -
作用域
Java的作用域由花括號的位置決定。 -
對象的作用域
Java對象可存活於作用域之外。在Java中我們只需要創建對象,一旦不再需要,垃圾回收器會讓它們自行消失。 -
類
class ATypeName { /* Class body goes here */}
類中可設置字段(數據成員)和方法(成員函數) -
方法、參數、返回值
方法基本組成部分:名稱在、參數、返回值、方法體。
方法的參數列表指定要傳遞給方法什麼樣的信息。 -
static關鍵字
當一個事物被聲明爲static時,就意味着這個域或方法不會與包含他它的那個類的任何對象實例關聯在一起。靜態數據成員可以通過對象名來定位它,也可以直接通過類名訪問。靜態方法雷同,自行類比。class StaticTest { static int i = 48; public static void main(String[] args) { StaticTest st = new StaticTest(); /*兩種訪問方式*/ st.i; //這裏eclipse會建議使用下面那種靜態方式訪問 StaticTest.i; } }
-
第一個Java程序
java.lang默認導入到每個Java文件中,其所有類都可以被直接使用。
類的名字和文件名必須相同。 -
編碼風格
“駝峯風格”
第三章 操作符
-
操作符“+”
"+"在System.out.println()中意味着字符串拼接,當“+”後面緊接着一個非String類型的元素時,就會嘗試把這個非String類型的元素轉換爲String。 -
別名問題
class Tank { int level; } public class Assignment { public static void main(String[] args) { Tank t1 = new Tank(); Tank t2 = new Tank(); t1.level = 9; t2.level = 47; System.out.println("1: t1.level:" + t1.level + ", t2.level:" + t2.level); t1 = t2; //這裏已經綁在一起了,兩個引用指向同一個對象,老t1已經被回收 System.out.println("1: t1.level:" + t1.level + ", t2.level:" + t2.level); t1.level = 27; System.out.println("1: t1.level:" + t1.level + ", t2.level:" + t2.level); } } /*Output: 1: t1.level:9, t2.level:47 1: t1.level:47, t2.level:47 1: t1.level:27, t2.level:27 */
這裏我們期望t1和t2是獨立的,因此我們可以如此解決:
t1.level = t2.level;
-
對象的等價性
==
和!=
比較的是對象的引用。equals()方法用於比較對象的實際內容是否相同,但equals()方法不適用於基本類型,基本類型直接使用==
和!=
。
這裏值得注意的是,equals()默認行爲是比較引用,即默認是==
。大部分的java類已經實現了equals()方法的重寫,使其比較對象的實際內容。但如果自己寫的新類沒有進行重寫,則equals()方法還是比較的是對象的引用。 -
直接常量
直接常量的後綴字符標誌了它的類型。
大寫(或小寫)的L,代表long(一般不用小寫l因爲容易和數字1混淆)。
大寫(或小寫)的F,代表float。
大寫(或小寫)的D,代表double。
0x(或0X)前綴 + 0-9或小寫(或大寫)的a-f來表示十六進制。
通過使用Integer、Long類的靜態方法 toBinaryString() 輸出二進制形式的結果。 -
指數計數法
e代表10的冪次,例如1.39e-43表示的是1.39×10-43 -
按位操作符
與(&),或(|),非(~),異或(^)
運算並賦值:&=,|=,^= -
移位操作符
<<
左移位操作符(低位補0)
>>
“無符號”右移位操作符(若符號爲正,高位插入0;若符號爲負,高位插入1)
>>>
“有符號”右移位操作符(無論正負,都在高位插入0)
只能對int型數據進行操作,如果對char,byte或者short類型的數值進行移位處理,那麼在移位之前,他們會被轉換爲int類型,並且得到的結果也是一個int類型的值。 -
三元操作符
boolean - exp ? value0 : value1
第四章 控制執行流程
-
if-else語句
if(Boolean-exp) statement else statement
布爾表達式必須產生一個布爾結果,statement指用分號結尾的簡單語句或複合語句(封閉花括號內的一組簡單語句)
-
迭代語句
while語句while(Boolean-exp) statement
do-while語句(區別於while,它至少會執行一次)
do statement while(Boolean-exp)
for語句
for(initialization; Boolean-exp; step) statement
for語句的初始化表達式(initialization),布爾表達式(Boolean-exp),步進(step)都可以爲空。
-
逗號操作符
只在for語句中起作用,在初始化,和步進控制部分使用,判斷部分不能用,只能是一個布爾表達式,使用例子如下:public class CommaOperator { public static void main(String[] args) { for(int i = 1, j = i + 10; i < 5; i++, j = i * 2) { System.out.println("i = " + i + " j = " + j); } } } /*Output: i = 1 j = 11 i = 2 j = 4 i = 3 j = 6 i = 4 j = 8 */
-
Foreach語法
Java SE5引入的一種更簡潔的for語法用於數組和容器,即foreach語法。任何返回一個數組的方法都可以使用foreach。for(Type ATypeName : TheSameTypeArray) statement
-
無條件分支關鍵字
Java中有多個關鍵詞表示無條件分支,它們只是表示這個分支無需任何測試即可發生,包括return、break、continue以及一種與其他語言中的goto類似的跳轉到標號語句的方式。 -
無窮循環
while(true)
和for(;;)
-
萬惡的goto
Java中沒有goto語句,但goto認識Java的一個保留字。Java中通過continue和break關鍵字使用與goto相同的機制:標籤。
標籤是後面跟有冒號的標識符,如label1:
。==在Java中需要使用標籤的唯一理由是:因爲有循環嵌套存在,而且想從多層嵌套中break或continue。例如:lable1: outer-iteration { inner-iteration { //... break; // (1) //... continue; // (2) //... continue label1; // (3) //... break label1; // (4) } }
在(1)中,break中斷內部迭代,回到外部迭代。
在(2)中,continue使執行點移回內部迭代的起始處。
在(3)中,continue label1同時中斷內部迭代以及外部迭代,直接轉到label1處,隨後。它實際上是繼續迭代過程,但卻從外部迭代開始。
在(4)中,break label1也會中斷所有迭代,並回到label1處,但並不重新進入迭代,即它實際是完全中止了兩個迭代。規則:
(1) 一般的continue會退回最內層循環的開頭(頂部),並繼續執行。
(2) 帶標籤的continue會到達標籤的位置,並重新進入緊接在那個標籤後面的循環。
(3) 一般的break會中斷並跳出當前循環。
(4) 帶標籤的break會中斷並跳出標籤所指的循環。 -
switch語句
switch(integral-selector) { case integral-value1 : statement; break; case integral-value2 : statement; break; case integral-value3 : statement; break; case integral-value4 : statement; break; case integral-value5 : statement; break; //... default: statement; }
其中integral-selector(整數選擇因子)是一個能夠產生整數值的表達式。能用於switch判斷的類型有:byte、short、int、char(JDK1.6),還有枚舉類型,但是在JDK1.7後添加了對String類型的判斷,這裏注意並不支持long類型。
功能相同的case是可以合併的:switch(integral-selector) { case integral-value1 : case integral-value2 : statement; break; //... default: statement; }
第五章 初始化與清理
-
構造器
(1)構造器採用和類完全相同的名稱("每個方法首字母小寫"編碼風格並不適用於構造器)。
(2)不接受任何參數的構造器叫做默認構造器,有形參的構造器可以在初始化對象時提供實參。
(3)在創建對象時,會爲對象分配儲存空間,並調用相應的構造器。
(4)構造器沒有返回值。 -
方法重載
方法名相同但參數類型列表不同,每個重載的方法都必須有一個獨一無二的參數類型列表。參數順序不同也能區分重載方法,但一般不要這樣做。 -
涉及基本類型的重載
當傳入較小類型時,基本類型能從一個較小的類型自動提升至較大的類型。因此當找不到最合適的方法時,會自動向上轉型到最接近的類型(byte-short-int-long-float-double)。char略有不同,如果找不到接受char參數的方法,會直接把char提升到int。
當傳入較大類型時,需要我們手動進行窄化處理,否則會報錯。 -
關於默認構造器
當我們沒有手動創建一個構造器時,編譯器會自動生成一個默認構造器(無參構造器)。當我們定義了一個構造器(無論有無參數),編譯器不會幫我們創建默認構造器。 -
this關鍵字
this關鍵字只能在方法內部使用,表示對“調用方法的那個對象”的引用。this的用法和引用沒有什麼不同,但如果在方法內部調用同一類中的另一個方法就不必使用this,直接調用即可。
this可以實現在一個構造器中調用另一個構造器。注意的地方有三點:
①儘管可以用this調用一個構造器,但卻不能調用兩個。
②必須將構造器調用至於最起始處,否則編譯器會報錯。
③除構造器之外,編譯器禁止在其他任何方法中調用構造器。 -
static的含義
在第二章的第8條筆記提到過static關鍵字。可以在沒有創建任何對象的前提下,僅僅通過類本身來調用static方法。 這正是static方法的主要用途。 -
清理:終結處理和垃圾回收
Java垃圾回收的三個注意點:
①對象可能不被垃圾回收
②垃圾回收不等於析構
③垃圾回收只跟內存有關
使用垃圾回收器的唯一原因:回收程序不再使用的內存。
無論是“垃圾回收”還是“終結”,都不保證一定會發生。如果Java虛擬機(JVM)並沒有面臨內存耗盡的情形,它是不會浪費時間去執行垃圾回收以恢復內存的。一旦垃圾回收器準備好釋放對象佔用的存儲空間,將首先調用其finalize()方法, 並且在下一次垃圾回收動作發生時,纔會真正的回收對象佔用的內存。一般不建議使用finalize()方法。
Java虛擬機將採用一種自適應的垃圾回收技術:模式 簡述 優點 缺點 適用情形 停止-複製(stop-and-copy) 需要先將程序暫定,然後將有效的全部複製到另外一片內存中,沒有複製的就是需要清除的。 新堆是有序的,一個一個的緊湊排列的。 當沒有很多需要回收的對象時,也要進行復制。 適用於需要清除的對象比較多的情況。 標記-清掃(mark-and-sweep) 遍歷所有的引用,當引用爲存活狀態的時候,就進行標記,這個過程不會回收任何的對象,只有當全部標記完成之後才進行釋放。 當需要回收的對象比較少的時候效率會比較高。 剩下的堆空間不是連續的,垃圾回收如果希望是連續空間的話,就要重新整理剩下的對象。 適用於產生很少垃圾甚至不會產生垃圾的情景。 -
成員初始化
類的每個基本類型數據成員保證都會有一個初始值。詳見第二章第2條筆記。 -
構造器初始化
初始化順序:在類的內部,變量定義的先後順序決定了初始化的順序。
靜態數據的初始化:無論創建多少個對象,靜態數據都只佔用一份存儲區域。先初始化靜態對象,然後再初始化非靜態對象。靜態初始化只會進行一次。
初始化塊:初始化塊優先於構造器進行初始化。
總結:(靜態變量、靜態初始化塊) > (變量、初始化塊) > 構造器 -
數組初始化
有兩種方式可以定義數組int[] a1;
和int a1[];
。一般建議使用第一種。
使用花括號來初始化數組int a1 = {1, 2, 3, 4, 5};
,初始化數組int[] a2;
,並且令a2 = a1
,這裏a1和a2是相同數組的兩個別名,通過a2進行的修改a1也能看到。這裏實際做的只是複製了一個引用。
數組元素中的基本數據類型值會自動初始化成空值(對於數字和字符,就是0;對於布爾型,是false)。
如果創建的是一個引用數組,需要先創建新的對象,並把對象賦值給引用,初始化纔算結束。如果忘記了創建對象,並且試圖使用數組中的空引用,就會在運行時產生異常。也可以用花括號括起來的列表來初始化對象數組,有兩種方式:public class ArrayInit { public static void main(String[] args) { Integer[] a = { new Integer(1), new Integer(2), 3, //Autoboxing }; Integer[] b = new Integer[]{ new Integer(1), new Integer(2), 3, //Autoboxing }; System.out.println(Arrays.toString(a)); System.out.println(Arrays.toString(b)); } } /*Output: [1, 2, 3] [1, 2, 3] */ /*這裏是書上的代碼,new Integer(num)這種構造器方法在jdk1.9裏已經被廢棄,不過還能用。*/
初始化列表的最後一個逗號都是可選的(這一特性使維護長列表變得更容易)。
-
可變參數列表
創建以Object數組爲參數的方法來實現:class A {} public class VarArgs { static void printArray(Object[] args) { System.out.println(); for(Object obj : args) { System.out.print(obj + " "); } } public static void main(String[] args) { printArray(new Object[]{new Integer(47), new Float(3.14), new Double(11.11)}); printArray(new Object[]{"One", "Two", "Three"}); printArray(new Object[]{new A(), new A(), new A()}); } } /*Output: 47 3.14 11.11 One Two Three com.A@4c3e4790 com.A@38cccef com.A@5679c6c6 */
以上代碼是在Java SE5之前的實現方法,Java SE5加入了新特性來實現這個功能:
public class NewVarArgs { static void printArray(Object... args) { System.out.println(); for(Object obj : args) { System.out.print(obj + " "); } } public static void main(String[] args) { // Can take individual elements: printArray(new Integer(47), new Float(3.14), new Double(11.11)); printArray(47.3, 3.14F, 11.11); printArray("one", "two", "three"); printArray(new A(), new A(), new A()); // Or an array: printArray((Object[])new Integer[]{1, 2, 3, 4}); printArray(); //Empty list is OK } } /*Output: 47 3.14 11.11 47.3 3.14 11.11 one two three com.A@4c3e4790 com.A@38cccef com.A@5679c6c6 1 2 3 4 */
最後一行表明0個參數傳遞給可變參數列表也可以,這很有用,因爲這說明參數可有可無。Object之外類型的可變參數列表,如String…(這裏所有的可變參數都必須是String對象)。在可變參數列表中可以使用任何類型的參數,包括基本類型。
使用可變參數列表會讓重載變得複雜,如果一個方法帶有非可變參數和可變參數列表,則在重載這個方法時也要必須要有非可變參數。 -
枚舉類型
Java SE5添加的一個小特性——enum關鍵字。舉例創建一個具有5個具名值名爲Spiciness的枚舉類型://: Spiciness.java public enum Spiciness { NOT, MILD, MEDIUM, HOT, FLAMING }
由於枚舉類型的實例是常量,因此按照命名規則慣例它們都用大寫字母表示(如果在一個名字中有多個單詞,用下劃線將它們隔開)。爲了使用enum,需要創建一個該類型的引用,並將其賦值給某個實例:
//: SimpleEnumUse.java public class SimpleEnumUse { public static void main(String[] args) { Spiciness howHot = Spiciness.MEDIUM; System.out.println(howHot); } } /*Output: MEDIUM */
上面代碼編譯器自動添加了toString()方法,以便顯示某個enum實例的名字。另外,編譯器還會自動創建 ordinal() 方法,用來表示某個特定enum常量的聲明順序,以及 static values() 方法,用來按照enum常量的聲明順序產生由這些常量值構成的數組。舉例如下:
//: EnumOrder.java public class EnumOrder { public static void main(String[] args) { for(Spiciness s : Spiciness.values()) { System.out.println(s + ".ordinal " + s.ordinal()); } } } /*Output: NOT.ordinal 0 MILD.ordinal 1 MEDIUM.ordinal 2 HOT.ordinal 3 FLAMING.ordinal 4 */
可以看出其實enum就是一個類。enum有一個實用特性,它可以在switch語句內使用:
//: Burrito.java public class Burrito { Spiciness degree; public Burrito(Spiciness degree) {this.degree = degree;} public void describe() { System.out.print("This burrito is "); switch(degree) { case NOT: System.out.println("not spicy at all."); break; case MILD: case MEDIUM: System.out.println("a little hot."); break; case HOT: case FLAMING: default: System.out.println("maybe too hot."); } } public static void main(String[] args) { Burrito plain = new Burrito(Spiciness.NOT), greenChile = new Burrito(Spiciness.MEDIUM), jalapeno = new Burrito(Spiciness.HOT); plain.describe(); greenChile.describe(); jalapeno.describe(); } } /*Output: This burrito is not spicy at all. This burrito is a little hot. This burrito is maybe too hot. */