目錄
介紹
《On Java 8》是什麼?
它是《Thinking In Java》的作者Bruce Eckel基於Java8寫的新書。裏面包含了對Java深入的理解及思想維度的理念。可以比作Java界的“武學祕籍”。任何Java語言的使用者,甚至是非Java使用者但是對面向對象思想有興趣的程序員都該一讀的經典書籍。目前豆瓣評分9.5,是公認的編程經典。
爲什麼要寫這個系列的精讀博文?
由於書籍讀起來時間久,過程漫長,因此產生了寫本精讀系列的最初想法。除此之外,由於中文版是譯版,讀起來還是有較大的生硬感(這種差異並非譯者的翻譯問題,類似英文無法譯出唐詩的原因),這導致我們理解作者意圖需要一點推敲。再加上原書的內容很長,只第一章就多達一萬多字(不含代碼),讀起來就需要大量時間。
所以,如果現在有一個人能替我們先仔細讀一遍,篩選出其中的精華,讓我們可以在地鐵上或者路上不用花太多時間就可以瞭解這邊經典書籍的思想那就最好不過了。於是這個系列誕生了。
一些建議
推薦讀本書的英文版原著。此外,也可以參考本書的中文譯版。我在寫這個系列的時候,會盡量的保證以“陳述”的方式表達原著的內容,也會寫出自己的部分觀點,但是這種觀點會保持理性並儘量少而精。本系列中對於原著的內容會以引用的方式體現。
最重要的一點,大家可以通過博客平臺的評論功能多加交流,這也是學習的一個重要環節。
第二章 安裝Java和本書用例
本章總字數:2400
關鍵詞:
- 選擇一個自己用着舒服的編輯器(IDE)
- 安裝Java環境
- 運行示例代碼
本章是書的過度章節,主要介紹瞭如何選擇適合的開發編輯器,以及安裝配置Java配置環境。在書中,作者提到了“適合的就是最好的”概念。任何編輯都是爲編程服務的,所以挑選一個自己用着舒服的編輯器即可。作者還着重推薦了Atom 文本編輯器(一個免費開源編輯器)。
在本章的最後部分,作者介紹瞭如何安裝和運行Java。在這裏就不作爲重點講解了。如果你是Java初學者可以參考《Java+IDEA環境配置整合》。需要說明的是,作者提到可以通過GitHub的OnJava8-Examples獲取對應的教學實例代碼。
第三章 萬物皆對象
本章總字數:11000
關鍵詞:
- 創建對象
- 數據存儲
- 作用域
- 類的創建
- 引用組件
- static
- 第一個Java程序
對象操縱
作者依然對比C++語言,在C++中“對象的操縱是通過指針來完成的”。而Java中利用了“萬物皆對象”的思想簡化問題,所以使用了引用方式來操縱。
舉例:我們可以用遙控器(引用)去操縱電視(對象)。只要擁有對象的“引用”,就可以操縱該“對象”。換句話說,我們無需直接接觸電視,就可通過遙控器(引用)自由地控制電視(對象)的頻道和音量。此外,沒有電視,遙控器也可以單獨存在。就是說,你僅僅有一個“引用”並不意味着你必然有一個與之關聯的“對象”。
String s;
String s2 = "asdf";
前者s只是創建了一個對象的引用,而不是對象。如果直接使用會出錯。而s2是創建了引用且同時進行了初始化。
“引用”用來關聯“對象”
上方的代碼也可以用new的方式來創建一個新對象。
String s = new String("asdf");
數據存儲
數據存儲部分是本章的關鍵內容。Java中數據可以存儲在五個不同的地方。這五個地方各有不同:
寄存器(Registers)最快的存儲區域,位於 CPU 內部。然而,寄存器的數量十分有限,所以寄存器根據需求進行分配。我們對其沒有直接的控制權,也無法在自己的程序裏找到寄存器存在的蹤跡(另一方面,C/C++> 允許開發者向編譯器建議寄存器的分配)。
棧內存(Stack)存在於常規內存RAM區域中,可通過棧指針獲得處理器的直接支持。棧指針下移分配內存,上移釋放內存,這是一種快速有效的內存分配方法,速度僅次於寄存器。創建程序時,Java 系統必須準確地知道棧內保存的所有項的生命週期。這種約束限制了程序的靈活性。因此,雖然在棧內存上存在一些 Java 數據,特別是對象引用,但 Java 對象卻是保存在堆內存的。
堆內存(Heap)這是一種通用的內存池(也在 RAM 區域),所有 Java 對象都存在於其中。與棧內存不同,編譯器不需要知道對象必須在堆內存上停留多長時間。因此,用堆內存保存數據更具靈活性。創建一個對象時,只需用 new 命令實例化對象即可,當執行代碼時,會自動在堆中進行內存分配。這種靈活性是有代價的:分配和清理堆內存要比棧內存需要更多的時間(如果可以用 Java 在棧內存上創建對象,就像在 C++ 中那樣的話)。隨着時間的推移,Java 的堆內存分配機制現在已經非常快,因此這不是一個值得關心的問題了。
常量存儲(Constant storage)常量值通常直接放在程序代碼中,因爲它們永遠不會改變。如需嚴格保護,可考慮將它們置於只讀存儲器 ROM (只讀存儲器,Read Only Memory)中。
非 RAM 存儲(Non-RAM storage)數據完全存在於程序之外,在程序未運行以及脫離程序控制後依然存在。兩個主要的例子:(1)序列化對象:對象被轉換爲字節流,通常被髮送到另一臺機器;2)持久化對象:對象被放置在磁盤上,即使程序終止,數據依然存在。這些存儲的方式都是將對象轉存於另一個介質中,並在需要時恢復成常規的、基於 RAM 的對象。Java 爲輕量級持久化提供了支持。而諸如 JDBC 和 Hibernate 這些類庫爲使用數據庫存儲和檢索對象信息提供了更復雜的支持。
Java中常用的基本類型由存儲在棧內存的各種數據類型構成,因此更加高效。
基本類型的幾種使用方式:
char c = 'x';
Character ch = new Character(c);
Character ch1 = new Character('x');
Character ch2 = 'x';//自動裝箱
char c = ch;//自動拆箱
在Java中,使用數組是安全的。Java對數組的使用有嚴格要求(相比C++),“數組使用前需要被初始化,並且不能訪問數組長度以外的數據”。這使得數組的使用可以在安全性和效率上的得以提高。
當我們創建對象數組時,實際上是創建了一個引用數組,並且每個引用的初始值都爲 null 。在使用該數組之前,我們必須爲每個引用指定一個對象 。如果我們嘗試使用爲 null 的引用,則會在運行時報錯。因此,在 Java 中就防止了數組操作的常規錯誤。
作用域
Java 的變量只有在其作用域內纔可用。合法使用作用域:
{
int x = 12;
// 僅 x 變量可用
{
int q = 96;
// x 和 q 變量皆可用
}
// 僅 x 變量可用
// 變量 q 不在作用域內
}
以下則是非法的使用方式( C++ 中是合法的):
{
int x = 12;
{
int x = 96; // Illegal
}
}
對象的作用域:
{
String s = new String("a string");
}
// 作用域終點
Java 的垃圾收集器會檢查所有 new 出來的對象並判斷哪些不再可達,繼而釋放那些被佔用的內存,供其他新的對象使用。也就是說,我們不必擔心內存回收的問題了。你只需簡單創建對象即可。當其不再被需要時,能自行被垃圾收集器釋放。垃圾回收機制有效防止了因程序員忘記釋放內存而造成的“內存泄漏”問題。
類的創建
class關鍵詞用來標記類,來創建一種對象的類型。
class ATypeName {
// 這裏是類的內部
}
可以使用new的方式這種類型的對象。
ATypeName a = new ATypeName();
字段位於類(class)的內部,可以是數值類型也可以是引用類型。
class DataOnly {
int i;
double d;
boolean b;
}
我們可以在創建了對象後使用字段。
DataOnly data = new DataOnly();
data.i = 47;
data.d = 1.1;
data.b = false;
方法表示類型(class)可以做的“行爲”。創建一個方法:
[返回類型] [方法名](/*參數列表*/){
// 方法體
}
使用一個方法:
[對象引用].[方法名](參數1, 參數2, 參數3);
當返回類型爲 void 時, return 關鍵字僅用於退出方法,因此在方法結束處的 return 可被省略。我們可以隨時從方法中返回,但若方法返回類型爲非 void,則編譯器會強制我們返回相應類型的值。
命名空間
Java使用反向 URL 的方式來解決不同模塊的命名問題。
Java 創建者希望我們反向使用自己的網絡域名,因爲域名通常是唯一的。因此我的域名是 MindviewInc.com,所以我將我的 foibles 類庫命名爲 com.mindviewinc.utility.foibles。反轉域名後,. 用來代表子目錄的劃分。
但是這種命名方式導致了一個問題,目錄結構可能會因爲命名而變得越來越複雜,因爲每一個“.”就代表一個目錄。而且這種目錄的路徑也會很長。這時候就需要一個類庫的專門管理者,例如像 IntelliJ IDEA這樣的IDE。
我們可以使用import關鍵詞使用其他模塊的代碼。
import java.util.ArrayList;
import java.util.*;//可以使用通配符 * 來導入其中部分類
static關鍵字
因爲只有在用new的方式創建一個類型的對象後,纔會分配數據存儲空間。這會導致如果我們某些時候想簡便的,直接創建一個可以使用的字段或者方法時忽然發現還得先創建對象。所以,Java引入了C++的static關鍵詞。
class StaticTest {
static int i = 47;
}
以下兩個StaticTest 對象共享同一個字段i,i只佔一份存儲空間。
StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
第一個Java程序
// objects/HelloDate.java
import java.util.*;
public class HelloDate {
public static void main(String[] args) {
System.out.println("Hello, it's: ");
System.out.println(new Date());
}
}
在你的Java環境都安裝無誤的前提下,移動到子目錄 objects 下並鍵入:
javac HelloDate.java
java HelloDate
其結果輸出當前時間。
Java使用“駝峯命名法”。對於類,方法,字段(成員變量)和對象引用名都採用駝峯命名的方式,區別是類的命名首字母需要大寫,但是其他的首字母不需要大寫。
第四章 運算符
本章總字數:14000
關鍵詞:
- 基礎運算符
- 關係運算符
- 類型轉換
作者在本章一開始就說明了,如果讀者已經瞭解 C 或 C++的基本運算符,大可以跳過本章和下一章。我的建議也一樣。四、五兩章適合對基礎語法不瞭解的讀者。
基礎運算符
幾乎所有運算符都只能操作基本類型(Primitives)。唯一的例外是 =、== 和 !=,它們能操作所有對象(這也是令人混淆的一個地方)。除此以外,String 類支持 + 和 +=。
// operators/Precedence.java
public class Precedence {
public static void main(String[] args) {
int x = 1, y = 2, z = 3;
int a = x + y - 2/2 + z; // [1]
int b = x + (y - 2)/(2 + z); // [2]
System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
結果:
a = 5
b = 1
括號改變了運算符的優先級,而優先級的不同會導致計算方式的區別。所以結果也不相同。此外,字符串使用的+號表示將兩個字符串拼接。“編譯器會將 + 連接的非字符串嘗試轉換爲字符串”。
Java 的基本算術運算符與其他大多編程語言是相同的。其中包括加號 +、減號 -、除號 /、乘號 * 以及取模 %(從整數除法中獲得餘數)。整數除法會直接砍掉小數,而不是進位。
Java使用+ + 與- - 的方式表示遞增與遞減。
// operators/AutoInc.java
// 演示 ++ 和 -- 運算符
public class AutoInc {
public static void main(String[] args) {
int i = 1;
System.out.println("i: " + i);
System.out.println("++i: " + ++i); // 前遞增
System.out.println("i++: " + i++); // 後遞增
System.out.println("i: " + i);
System.out.println("--i: " + --i); // 前遞減
System.out.println("i--: " + i--); // 後遞減
System.out.println("i: " + i);
}
}
結果:
i: 1
++i: 2
i++: 2
i: 3
--i: 2
i--: 2
i: 1
關係運算符
關係運算符會通過產生一個布爾(boolean)結果來表示操作數之間的關係。如果關係爲真,則結果爲 true,如果關係爲假,則結果爲 false。關係運算符包括小於 <,大於 >,小於或等於 <=,大於或等於 >=,等於 == 和不等於 !=。== 和 != 可用於所有基本類型,但其他運算符不能用於基本類型 boolean,因爲布爾值只能表示 true 或 false,所以比較它們之間的“大於”或“小於”沒有意義。
// operators/Equivalence.java
public class Equivalence {
public static void main(String[] args) {
Integer n1 = 47;
Integer n2 = 47;
System.out.println(n1 == n2);
System.out.println(n1 != n2);
System.out.println(n1.equals(n2));
}
}
結果:
true
false
true
三元運算符
布爾表達式 ? 值 1 : 值 2
//若表達式計算爲 true,則返回結果 值 1 ;如果表達式的計算爲 false,則返回結果 值 2。
示例:
// operators/TernaryIfElse.java
public class TernaryIfElse {
static int ternary(int i) {
return i < 10 ? i * 100 : i * 10;
}
static int standardIfElse(int i) {
if(i < 10)
return i * 100;
else
return i * 10;
}
public static void main(String[] args) {
System.out.println(ternary(9));
System.out.println(ternary(10));
System.out.println(standardIfElse(9));
System.out.println(standardIfElse(10));
}
}
結果:
900
100
900
100
假若我們打算頻繁使用它(三元運算符)的話,還是先多作一些思量: 它易於產生可讀性差的代碼。
類型轉換
在不同類型數據的運算時,會出現類型上的轉換。“例如,假設我們爲 float 變量賦值一個整數值,計算機會將 int 自動轉換成 float。”我們可以使用在數據前加數據類型的方式強制轉換數據類型。
// operators/Casting.java
public class Casting {
public static void main(String[] args) {
int i = 200;
long lng = (long)i;
lng = i; // 沒有必要的類型提升
long lng2 = (long)200;
lng2 = 200;
// 類型收縮
i = (int)lng2; // Cast required
}
}
誠然,你可以這樣地去轉換一個數值類型的變量。但是上例這種做法是多餘的:因爲編譯器會在必要時自動提升 int 型數據爲 long 型。
當然,爲了程序邏輯清晰或提醒自己留意,我們也可以顯式地類型轉換。
java.lang.Math 的 round() 方法可以對數值進行四捨五入。“因爲 round() 方法是 java.lang 的一部分,所以我們無需通過 import 就可以使用。”
第五章 控制流
本章總字數:7600
關鍵詞:
- if-else
- do-while
- for
- for-in
- switch
if-else
如果符合某一個條件,則做某種操作。這種邏輯是常用的。if-else的作用就是如此。
示例:
// control/IfElse.java
public class IfElse {
static int result = 0;
static void test(int testval, int target) {
if(testval > target)
result = +1;
else if(testval < target) // [1]
result = -1;
else
result = 0; // Match
}
public static void main(String[] args) {
test(10, 5);
System.out.println(result);
test(5, 10);
System.out.println(result);
test(5, 5);
System.out.println(result);
}
}
結果:
1
-1
0
其中else 是可選的,表示“例外”的情況。else if 則表示例外當中符合某種條件的情況。
迭代
while ,do-while 和 for 三種語句實現了循環做某種操作的邏輯。
示例:
int i=0;
while(i<=3)
{
i++;
System.out.println("Inside 'while'");
}
結果:
Inside 'while'
Inside 'while'
Inside 'while'
Inside 'while'
while 和 do-while 之間的唯一區別是:即使條件表達式返回結果爲 false, do-while 語句也至少會執行一次。
for 循環在第一次迭代之前執行初始化。隨後,它會執行布爾表達式,並在每次迭代結束時,進行某種形式的步進。
// control/ListCharacters.java
public class ListCharacters {
public static void main(String[] args) {
for(char c = 0; c < 128; c++)
if(Character.isLowerCase(c))
System.out.println("value: " + (int)c +
" character: " + c);
}
}
value: 97 character: a
value: 98 character: b
value: 99 character: c
...
for-in 是Java5開始引入的寫法。是對for的增強使用版,可以用來循環操縱集合與數組。
示例:
// control/ForInString.java
public class ForInString {
public static void main(String[] args) {
for(char c: "An African Swallow".toCharArray())
System.out.print(c + " ");
}
}
結果:
A n A f r i c a n S w a l l o w
break 和 continue
在任何迭代語句的主體內,都可以使用 break 和 continue 來控制循環的流程。 其中,break 表示跳出當前循環體。而 continue 表示停止本次循環,開始下一次循環。
switch
當 if-else語句很多的時候,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 能夠將這個表達式的結果與每個 integral-value (整數值)相比較。若發現相符的,就執行對應的語句(簡單或複合語句,其中並不需要括號)。若沒有發現相符的,就執行 default 語句。
在Java7開始,選擇因子除了 int 和char ,加入了對字符串的使用。在後續的章節裏作者會介紹 switch搭配枚舉的使用。