Head First Java 讀書筆記(完整)


第0章:學習方法建議

該如何學習Java?

1.慢慢來。理解的越多,就越不需要死記硬背。時常停下來思考。
2.勤作筆記,勤做習題。
3.動手編寫程序並執行,把代碼改到出錯為止。

需要哪些環境和工具?

1.文本編輯器
2.Java API 文檔
3.安裝 JDK5 以上
4.配置Java環境變量,讓java/javac知道去哪里尋找Java/bin中的執行文件。
5.UML圖例與規則


第1章:進入Java的世界

Java有哪些特點?

面向對象、內存管理、跨平台write once, run anywhere.
跨平台,這是Java的核心目標。

Java如何工作?Java編譯器和Java虛擬機,各有什么用?

1.源代碼 -> 2.編譯器 -> 3.輸出字節碼 -> 4.JVM(Java虛擬機)
.java文件 -> .class文件 -> 在JVM上運行
第1步:編寫源代碼文件
第2步:用javac編譯器編譯源代碼。編譯器同時會檢查錯誤,如果有錯誤就要改正,然后才能產出正確的輸出。
第3步:編譯器會產出字節碼。任何支持Java的裝置都能把它編譯成可執行的內容。編譯后的字節碼與平台無關。
第4步:JVM(Java虛擬機)可以讀取並執行字節碼。只要設備上安裝了JVM,即可運行Java字節碼程序。

Java歷史?

Java1.02 / Java1.1 / Java1.2~1.4 / Java1.5(即Java5)

Java的程序結構是怎樣的?

類存在於源文件里面,方法存在於類中,語句存在於方法中。
什么是源文件?源文件以.java為擴展名,其中帶有類的定義。
什么是類?類中帶有一個或多個方法。
什么是方法?方法必須在類中聲明,方法是由一組語句組成。
每個Java程序至少有一個類,並且每個Java程序有且僅有一個main()函數。Java中所有的東西都會屬於某個類。

為什么Java中所有的東西都得包含在類中?

因為Java是面向對象語言,類是對象的藍圖。Java中,絕大多數的東西都是對象。

如何解讀Java程序的main()方法?
public class App() {
    public static void main (String[] args) {
        // 程序的入口
    }
}

Java虛擬機啟動時,它會尋找你在命令行中所指定的類,並以該類中的main()方法為入口函數進行程序執行。
main()的兩種用途?
1.用於測試我們編寫的Java類。2.作為入口,啟動我們的Java程序。

App.java -> App.class -> JVM執行這個程序

javac App.java      // java編譯器編譯這個程序
java App.class      // JVM執行這個程序

Java有哪些語句?
循環語句、條件語句。


第2章:拜訪對象村

當你在設計類時,要記得對象是靠類的模型塑造出來的。對象是已知的事物,對象會執行動作。

什么是對象(實例)?什么是實例變量?什么是方法?

對象帶有實例變量和方法,這是類設計的一部分。
對象本身已知的狀態,即實例變量,它代表對象的狀態,每個對象都會獨立地擁有一份該類型的值。
對象可以執行的動作,即方法,方法可以讀取或操作實例變量。

類和對象之間到底有什么不同?

類不是對象,卻是用來創建對象的模型。類是對象的藍圖,類會告訴JVM如何創建出某種類型的對象。根據一個類,JVM可以創建出無數的對象。

如何理解Java垃圾回收機制?

堆,可自動回收垃圾的堆。

jar包中的manifest文件,指定了程序中唯一main()方法所在的類。


第3章:認識變量

如何理解Java變量?

Java變量就像是杯子,是一種容器,用於承載某種事物,它有大小和類型。Java編譯器不允許將大杯的內容放到小杯中,反過來則可以。
Java的命名規則有哪些限制?什么是Java保留字?

Java變量有哪兩種類型?

1.八種primitive主數據類型:boolean / char / byte / short / int / long / float / double
2.引用類型:引用到對象的變量。對象只會存在於可回收垃圾堆上。

如何深入理解引用類型?

引用類型變量,其大小決定於JVM廠商。在同一台JVM上,引用類型變量的大小是相同的。在Java中,我們不能對引用類型變量進行任何的運算。

Java變量必須得有類型和名稱:

char x = 'x';
int age;
float pi = 3.14f;   // 不加 f,它將被當作double類型來使用。
什么是Java數組?

Java數組是對象。數組好比是杯架。
一旦數組被聲明出來,你就只能裝入所聲明類型的元素。double類型的變量不能裝入int數組中,但是int類型的變量可以裝入到double類型的數組中。

Dog[] dogs = new Dog[7];
int[] nums = new int[7];
實例變量未初始化時,它的默認值是多少?

數值類型的變量默認值都為0,char類型的默認值也是0。
布爾類型的默認值是false。
引用類型的默認值是null。

實例變量和局部變量有哪些區別?

實例變量是聲明在類中的,可以不初始化。
局部變量是聲明在方法中的,在被使用之前,必須先初始化。方法的參數,也屬於局部變量。

如何判斷兩個變量是否相等?
  1. == , 比較是兩個變量的字節組合是否相等。
  2. equals(),用於判斷兩個對象是否是真正意義上的相等。
    它們在判斷基本數據類型和引用類型時,有什么區別?


第4章:對象的行為

對象中,實例變量和方法之間有着怎樣的關系?

狀態影響行為,行為影響狀態。實例變量即狀態,方法即行為。

什么是形參(parameter)和實參(argument)?

我們可以給方法傳入參數。實參是傳給方法的值。當實參傳入方法后就成了形參。
給方法傳入實參時,實參的順序、類型、數量必須與形參的順序、類型、數量完全一致。
你還可以把變量當作實參傳入方法,只要其類型符合要求即可。

方法的參數傳遞為什么是值傳遞?什么是值傳遞?

Java方法的參數傳遞是值傳遞形式,即通過拷貝傳遞。實參進入方法之后就成了形參,形參的變化不會改變實參,這就是值傳遞的好處。

什么是方法的返回值?

在Java中,方法可以有返回值,每個方法都必須顯示地指明返回值類型。如果方法沒有返回值,指明為void類型即可。

int getAge() {
    return 30;      // 有返回值,類型為int
}

int age = getAge(); // 接收方法的返回值

什么是Getter和Setter ?

Getter 與 Setter,前者用於返回實例變量的值,后者用於設定實例變量的值。

為什么要封裝?為什么把類中的變量暴露出去會很危險?如何對變量數據進行隱藏?

實現方案:把實例變量標記為private私有的,並提供public公有的getter和setter來控制實例變量的存取動作。

封裝有什么好處?

封裝會對我們的實例變量加上絕對領域,因此沒有人能夠惡搞我們的實例變量。

class Dog {
    // 私有的實例變量,從而避免了dog.size的方式對實例變量進行操作,因為這很危險。
    private int size;
<span class="hljs-comment">// 公有的 Setter,在類的外部,通過dog.setSize()來設置size的值</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setSize</span><span class="hljs-params">(<span class="hljs-keyword">int</span> size)</span> </span>{
    <span class="hljs-keyword">this</span>.size = size;
}
<span class="hljs-comment">// 公有的 Getter,在類的外部,通過dog.getSize()來獲取size的值</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getSize</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.size;
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">bark</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">if</span> (size &gt; <span class="hljs-number">10</span>) {} <span class="hljs-keyword">else</span> {}
}

}

數組中的元素,還可以是對象。

Dog[] pets = new Dog[7];
pets[0] = new Dog();
pets[1] = new Dog();

pets[0].setSize(80); // 數組中對象調用對象方法


第5章:超強力方法

什么是面向對象的編程思維?

所謂面向對象,就是要專注於程序中出現的事物,而不是過程。

什么是偽碼、測試碼、真實碼?分別有什么用?
  1. 偽碼的作用是幫助你專注於邏輯,而不需要顧慮到程序的語法。
  2. 測試碼,用於程序代碼的測試。
  3. 真實碼,即實際設計出來的真正Java程序代碼。

Java程序應該從高層的設計開始。通常在創建新的類時,要寫出“偽碼->測試碼->真實碼”。偽碼應該描述要做什么事情而不是如何做,使用偽碼可以幫助測試碼的設計,在實現真實碼之前應該要編寫測試碼。

有哪些循環語句?

for循環、while循環、foreach循環(加強版的for循環,用於遍歷數組等)

有哪兩種強制類型轉換?
int a = (int)(Math.random()*5);     // 把非int類型的數字,強轉為int類型
int b = Integer.parsetInt("100");   // 把字段串強轉為int類型

i++ 和 ++i 有什么區別?


第6章:使用Java函數庫

ArrayList 和 一般數組的如此區別?ArrayList有哪些實用的API?

什么是Java包?

在java中,類是被包裝在包中的。要使用API中的類,你必須知道它被放在哪個包中。

使用java類的完整名稱:

java.util.ArrayList<Dog> list = new java.util.ArrayList<Dog>();
public void go(java.util.ArrayList<Dog> list) { };
public java.util.ArrayList<Dog> foo() { };

除了java.lang包之外,其它的java包的類在被使用時,都需要使用類的全名,即java.xxx.className形式。或者在程序開頭處使用import指令導入所需要的類。

Java包機制有什么好處和意義?

以javax開頭的包代表什么意思?如何高效地使用 java api 手冊?


第7章:繼承與多態

繼承與方法重寫override,什么是方法重寫?

由子類重新定義從父類中繼承來的方法,將其改變或延伸。成員變量不存在重寫這個說法。

public void roam() {
  // 繼承父類方法的功能
  super.roam();
  // 擴展的功能
}
區分 IS-A 與 HAS-A 關系

IS-A,表示二者具有繼承關系,這是一種單向的鏈式關系。
HAS-A,表示二者是包含關系。

四種訪問控制修改符,它們有哪些區別?

private < default < protected < public
public類型的成員,會被繼承。private類型的成員,不會被繼承。
一個類的成員,包含自己定義的變量和方法,還包含從父類所繼承下來的成員。

繼承到底有什么意義?

避免重復的代碼;定義共同的協議。

什么是多態?
對象的聲明、創建與賦值:
Dog d = new Dog();

在上述對象的創建過程中,引用類型和對象的類型相同。在多態中,引用類型和對象的類型可以不同

Animal d = new Dog();    // 這就是多態

在多態中,即引用類型可以是實際對象類型的父類(滿足 IS-A 關系,就產生了多態)。

Animal[] animals = new Animal[3];
animals[0] = new Dog();
animals[1] = new Cat();
animals[2] = new Wolf();
for(int i=0; i<animals.length; i++) {
  animals[i].eat();     // 調用子類Dog的 eat()方法
  animals[i].roam();    // 調用子類Cat的 roam()方法
}

Dog / Cat / Wolf 都是 Animals 的子類。

方法的參數或返回值,也可以是多態。

public Animal getAnimal( Animal a ) {
  // 參數a, 可以是Animal子類的對象
  return a;  // 返回Animal對象或者其子類的實例對象。
}

使用多態,你就可以編寫出引進新子類時也不必修改的程序。

繼承關系中,方法覆寫要遵守哪些約定?

  1. 參數必須要一樣,且返回類型必須要兼容。
  2. 不能降低方法的存取權限。

什么是方法重載?
方法名相同,但參數列表不同,即是重載,它與方法的返回值類型、方法的存取權限都無關。方法重載與繼承關系中的方法覆寫有什么不同?

public class Overloads {
  // 以下兩個方法,即為方法重載
  public int addNums ( int a, int b) {
    return a + b;
  }
  public double addNums (double a, double b) {
    return a + b;
  }
}


第8章:接口與抽象類(深入多態)

什么是抽象類?

用abstract關鍵字聲明抽象類,抽象類不能用new 關鍵字進行實例化。在設計繼承結構時,必須決定清楚什么類是抽象類,什么類是具體類。編譯器不會讓你初始化一個抽象類。抽象類,除了被繼承以外,是沒有其它任何用途的。抽象類中,必須包含有抽象方法,還可以包含非抽象方法。

什么是抽象方法?

即用 abstract關鍵字聲明的方法,抽象方法沒有方法實體,即沒有具體的實現過程。擁有抽象方法的類,必須聲明為抽象類。抽象類中的抽象方法,用於規定一組子類共同的協議。

abstract class Animal {
    // 抽象方法,沒有方法體
    public abstract void eat();
}

在繼承過程中,具體類必須實現抽象父類的所有抽象方法

抽象方法沒有具體的方法體,它只是為了標記出多態而存在。在覆寫抽象父類的抽象方法時,方法名、參數列表必須相同,返回值類型必須兼容。Java很在乎你是否實現了抽象類的抽象方法。

public class Canine extends Animal {
    // 覆寫抽象類的抽象方法
    public void eat() {
        System.out.println("Canine,會吃食物!!");
    }
    // 非繼承的方法
    public void roam() {
}

}

多態的使用

在Java中,所有類都是從Object這個類繼承而來的,Object是所有類的源頭,它是所有類的父類。Object有很有用的方法,如 equals(), getClass(), hashCode(), toString()等。

  1. Object類,是抽象類嗎? 答:不是,它沒有抽象方法。
  2. 是否可以覆寫Object中的方法? 答:Object類中帶有 final關鍵字的方法,不能被覆寫。
  3. Object類有什么用? 答:用途一,它作為多態可以讓方法應付多種類型的機制,以及提供Java在執行期對任何對象都需要的方法實現。另一個用途,它提供了一部分用於線程的方法。
  4. 既然多態類型這么有用,為什么不把所有的參數類型、返回值類型都設定為Object? 答:因為Java是強類型語言,編譯器會檢查你調用的是否是該對象確實可以響應的方法。即,你只能從確實有該方法的類中去調用。
Object dog = new Dog();
dog.toString();  // 這可以通過編譯,因為toString()是Object類中自有的方法。
dog.eat();  // 這將無法通過編譯,因為dog是Object類型,它調用的eat()方法在Object類中沒有。

在使用多態時,要注意對象多種類型之間的差異。如下代碼:

Dog dog1 = new Dog();
Animal dog2 = new Dog();
Object dog3 = new Dog();

注意這三個dog對象的區別: dog1 擁有 Dog / Animal / Object中所有的方法。dog2 擁有 Animal / Object 中的方法,不能調用 Dog 類特有的方法。 dog3 只擁有Object 中的方法,不能調用 Animal / Dog類中的方法。這就是在使用多態過程中,需要特別注意的問題。

那么該如何把 Object 類型的 dog轉化成真正的 Dog 類型呢?
if (dog2 instanceof Dog) {
Dog dog4 = (Dog)dog2;
}
if (dog3 instanceof Dog) {
Dog dog5 = (Dog)dog3;
}
// 此時,dog4 / dog5 就是真正的 Dog類型了。

什么是接口?

接口,是一種100%純抽象的類。接口中的所有方法,都是未實現的抽象方法。

為什么需要接口?

接口存在的意義,就是為了解決Java多重繼承帶來的致命方塊問題。為什么接口可以解決致命方塊的問題呢?因為在接口中,所有方法都是抽象的,如此一來,子類在實現接口時就必須實現這些抽象方法,因此Java虛擬機在執行期間就不會搞不清楚要用哪一個繼承版本了。

// interface關鍵字,用於定義接口
public interface Pet {
    public abstract void beFriendly();
    public abstract void play();
}
// 繼承抽象父類 Animal類, 實現 Pet接口
public class Dog extends Animal implements Pet {
    // 實現接口中的抽象方法
    public void beFriendly() {
        System.out.println("實現 Pet接口中的 beFriendly()方法");
    }
    // 實現接口中的抽象方法
    public void play() {
        System.out.println("實現 Pet接口中的 play()方法");
    }
    // 覆寫抽象父類中的抽象方法
    public void eat() {
        System.out.println("覆寫抽象父類中的eat()抽象方法");
    }
}
同一個類,可以實現多個接口!
public class Dog extends Animal implements Pet, Saveable, Paintable { ... }

如何判斷應該是設計 類、子類、抽象類、還是接口呢?

  1. 如果新的類無法對其它的類通過 IS-A 測試時,就設計成不繼承任何類的類。
  2. 只有在需要某個類的特殊化版本時,以覆寫或增加新的方法來繼承現有的類,得到子類。
  3. 當你需要定義一群子類的模板,又不想讓程序員初始化該模板時,就設計出抽象類。
  4. 如果希望類可以扮演多態的角色,就設計出完全抽象的接口。

super關鍵字代表什么?

super代表父類,在子類中使用 super關鍵字指代父類,通過super還可以調用父類的方法。

// 抽象父類
abstract class Animal {
  void run () {}
}
// 繼承父類
class Dog extends Animal {
  void run () {
    super.run();  // 這里,調用並執行父類的 run() 方法
    // do other things
  }
}
Dog d = new Dog();
d.run();    // 這調用的是子類Dog對象的 run()方法。


第9章:構造器與垃圾收集器

什么是棧與堆? 堆(heap)、棧(stack)

當Java虛擬機啟動時,它會從底層操作系統中取得一塊內存,以此區段來執行Java程序。實例變量保存在所屬的對象中,位於堆上。如果實例變量是對象引用,則這個引用和對象都是在堆上。

構造函數與對象創建的三個步驟

對象創建的三個步驟:聲明、創建、賦值。
構造函數,讓你有機會介入 new 的過程。構造函數,沒有顯示的指定返回值類型,構造函數不會被繼承。如果一個類,沒有顯示地編寫構造器函數,Java編譯器會默認地為該類添加一個沒有參數的構造器函數。反之,Java編譯器則不會再添加任何默認的構造函數。

Dog dog = new Dog();

什么構造器函數重載?

即一個類,有多個構造器函數,且它們的參數都不能相同,包括參數順序不同、或者參數類型不同、或者參數個數不同。重載的構造器,代表了該類在創建對象時可以有多種不同的方式。

public class Mushroom {
    // 以下五個構造器,都是合法的,即構造器重載
    public Mushroom() {}
    public Mushroom( int size ) {}
    public Mushroom( boolean isMagic ) {}
    public Mushroom( boolean isMagic, int size ) {}
    public Mushroom( int size, boolean isMagic ) {}
}

什么是構造函數鏈? super()

構造函數在執行的時候,第一件事就是去執行它的父類的構造函數。這樣的鏈式過程,就被稱為“構造函數鏈(Constructor Chaining)”。

class Animal {
    public Animal() {
        System.out.println("Making an Animal");
    }
}

class Dog extends Animal {
public Dog() {
super(); // 如果沒有這句,Java編譯器會默認添加上這句,即調用父類的無參構造器
System.out.println("Making an Dog");
}
}

public class ChainTest {
public static void main(String[] args) {
System.out.println("Starting...");
Dog d = new Dog();
}
}

如果一個類,沒有顯示地書寫構造器函數,Java編譯器會為它添加上默認的無參構造器。如果在一個子類的構造器中沒有使用super()調用父類的某個重載構造構造器,Java編譯器會為這個子類的構造器默認添加上super(),即在子類的構造器函數中調用父類的無參構造器。
父類的構造器函數,必須在子類的構造器函數之前調用。在子類構造器函數中調用父類構造器時,必須與父類構造器的參數列表一致。

在類中,this 和 super 有什么區別? this() 和 super() 有什么區別?

使用 this() 可以在某個構造函數中調用同一個類的另外一個構造函數。 this() 只能在構造函數中使用,並且必須是第一行。 this() 和 super() 不能同時使用。

class Car {
    private String name;
    // 父類的有參構造器
    public Car(String name) {
        this.name = name;
        System.out.println(name);
    }
}

class MiniCar extends Car {
// 構造器
public MiniCar(String name) {
// 調用父類的有參構造器
super(name);
}
// 另一個構造器
public MiniCar() {
// 調用同一個類的另一個構造器
this("這里子類汽車的名稱");
}
}

public class TestThis {
public static void main(String[] args) {
MiniCar mc1 = new MiniCar();
MiniCar mc2 = new MiniCar("動態的名字");
}
}

對象、變量的生命周期是怎樣的?

對象的生命周期決定於對象引用變量的生命周期,如果引用還在,則對象也在;如果引用死了,對象會跟着被 GC 回收。當最后一個引用消失時,對象就會變成可回收的。
局部變量,只存活在對象的方法中,方法結束,局部變量就死了。
實例變量,存活在對象中,它的生命周期與對象一致。

Life 和 Scope 有什么區別?

Life,只要變量的推棧塊還存在於堆棧上,局部變量就算是活着。局部變量會活到方法執行完畢為止。
Scope,局部變量的作用范圍只限於它所在的方法中。當該方法調用別的方法時,該局部變量還活着,但不在目前的范圍內,當被調用的其它方法執行完畢后,該總局變量的范圍又跟着回來了。

有哪 3 種方法可以釋放對象的引用?

class Apple {}

public class LifeTest {
void go() {
// 1 - 當 go 方法調用結束時,銷毀對象
Apple a = new Apple();
}
public static void main(String[] args) {
Apple a1 = new Apple();
// 2 - 引用被重新賦值時,銷毀舊對象
a1 = new Apple();
// 3 - 把引用賦值為null時,銷毀對象
a1 = null;
}
}

什么是 null ?

當你把引用變量賦值為 null 時,你就等於抹除了遙控器的功能。對 null 對象使用圓點調用方法,會報空指針異常 NullPointerException。


第10章:數字與靜態性

Math 有什么特點?

在 Java 中沒有東西是全局(global)的。但,Math 方法是接近全局的方法。Math不能用來創建實例變量。因為Math是用來執行數據計算的,所以沒有必要創建對象來進行數學計算,創建對象是浪費內存空間的做法。Math中所有方法都靜態方法。

Long a = Math.round(46.25);
int b = Math.max(2, 3);
int c = Math.abs(-500);

非靜態方法與靜態方法,有哪些差別?

  1. 靜態方法,使用 static 關鍵字聲明,以類的名稱進行調用。
  2. 非靜態方法,以實例對象進行調用,沒有 static修飾。

Math類是如何阻止被實例化的?

Math類阻止被實例化,采取的策略是使用 private 修飾了其構造函數。

在靜態方法中,為什么不能使用非靜態的變量?為什么也不能調用非靜態的方法?

靜態變量有哪些特點?靜態變量與非靜態變量有哪些區別?

靜態變量,會被同類的所有實例共享,因為它隸屬於類。靜態變量,在類第一次載入時初始化。靜態變量,會在所有對象創建之前進行初始化,也會在任何靜態方法執行之前就初始化。靜態變量,只能由類來調用。
非靜態變量,只被單個對象獨有,它隸屬於實例。非靜態變量,在類實例化時進行初始化。
實例對象不會維護靜態變量的拷貝,靜態變量由類進行維護。非靜態變量由實例對象進行維護。

class Duck {
    // 非靜態變量,屬於對象
    private int size = 0;
    // 靜態變量,屬於類
    private static int count = 0;
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Duck</span><span class="hljs-params">()</span> </span>{
    size++;
    count++;
    System.out.println(<span class="hljs-string">"size "</span> + size);
    System.out.println(<span class="hljs-string">"count "</span> + count);
}
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setSize</span><span class="hljs-params">(<span class="hljs-keyword">int</span> s)</span> </span>{
    size = s;
}
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getSize</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> size;
}

}

public class TestStatic {
public static void main(String[] args) {
Duck d1 = new Duck(); // size = 1 count = 1
Duck d2 = new Duck(); // size = 1 count = 2
Duck d3 = new Duck(); // size = 1 count = 3
}
}

如何聲明一個靜態常量?

public static final double PI = 3.1415926;

public 表示可供各方讀取。 static 表示靜態。 final 表示“固定不變”。 常量的標識符,一般建議字母大寫,字母之間可以用下划線連接。

深入理解 final

final 的核心意思是,它所修飾的元素不能被改變。final 不僅可以修飾變量,還可以修飾方法和類。

// 用 final 修飾的類,不能被繼承
final class Foo {
    // final 修飾靜態變量,得到靜態常量
    public static final double PI = 3.1415926;
    // final 修飾非靜態變量,該變量將無法再被修改
    final String name = "geekxia";
    void changeName() {
        // 修改 final變量,失敗
        // name = "Evatsai";
    }
    // final 修飾局部變量,該局部變量也將無法再被修改
    void doFoo(final int x) {
        // 修改局部變量,失敗
        // x = 100;
    }
    // final 修飾的方法,子類將不能覆寫
    final String getName() {
        return name;
    }
}

什么是主數據類型的包裝類?

Boolean / Character / Byte / Short / Integer / Long / Float / Double
主數據類型的包裝類,都放在 java.lang 中,所以無需 import 它們。當你需要以對象的方式來處理主數據類型時,你就需要用包裝類把它們包裝起來,Java5.0之前必須這么做。

int a = 123;
// 包裝
Integer aWrap = new Integer(a);
// 解包
int b = aWrap.intValue();

Java5.0以后,autoboxing 使得主數據類型和包裝類型不必分得那么清楚。autoboxing 的功能能夠自動地將主數據類型和包裝類型進行轉換。看看下面的例子:

ArrayList<Integer> list = new ArrayList<Integer>();
// 自動地把主數據類型和包裝類型進行轉換
list.add(3);
int c = list.get(0);

Boolean bool = new Boolean(null);
if (bool) {}

Integer d = new Integer(3);
d++;
Integer e = d + 3;

void takeNumber(Integer i) {}
Integer getNumber() { return 4; }

主數據類型與字符串之間,如何進行相互轉化?

// 把字符串轉化成主數據類型
String a = "12";
int b = Integer.parseInt(a);        
double c = Double.parseDouble("50.789");        
boolean d = new Boolean("true").booleanValue();

// 把主數據類型轉化成字符串
double e = 34.789;
String f = "" + e;
String g = Double.toString(e);

如何對數字進行格式化?

// 第二個參數,以第一個參數的格式化指令進行輸出
String s = String.format("%, d", 1000000000);
String m = String.format("I have %,.2f bugs to fix.", 489369.123456);

如何對日期進行格式化?

String d = String.format("%tB", new Date());

更多有關“格式化指令”的參考,請查看相關手冊。

使用 Calendar 抽象類操作日期

// 獲取日歷的實例對象
Calendar cal = Calendar.getInstance();
cal.set(2014, 1, 7, 15, 40);

long day1 = cal.getTimeInMillis();
day1 += 10006060;

cal.setTimeInMillis(day1);
cal.add(cal.DATE, 35);


第11章:異常處理

如果你把有風險的程序代碼包含在 try/catch 塊中,那么編譯器會放心很多。 try/catch 塊會告訴編譯器你確實知道所調用的方法會有風險,並且也已經准備好要處理它。

try {
    // 有風險的行為
    Sequencer seq = MidiSystem.getSequencer();
    System.out.println("had got a sequencer");
} catch (MidiUnavailableException ex) {
    System.out.println(ex);
} finally {
    System.out.println("總會被執行!");
}

異常也是多態的,Exception子類的實例對象,都是 Exception類型的。編譯器會忽略 RuntimeException類型的異常,RuntimeException類型的異常不需要聲明或被包含在 try/catch 塊中。

如果 try 或 catch 塊中有 return 指令,finally還是會執行。流程會跳到 finally執行,finally執行完畢后再回跳到return 指令。

如何處理多重異常?

class Laundry {
    public void doLaundry() throws PantsException, LingerieException {}
}

public class HandleException {
public void go() {
Laundry lau = new Laundry();
try {
lau.doLaundry();
} catch (PantsException e) {
System.out.print(e);
} catch (LingerieException e) {
System.out.print(e);
} finally {
}
}
}

如果有必要的話,方法可以拋出多個異常。但該方法的聲明必須要有含有全部可能的檢查異常(如果兩個或兩個以上的異常有共同的父類時,可以只聲明該父類就行)。

有多重異常需要捕獲時,異常要根據繼承關系從小到大排列,如果更高一級的異常排在了前面,將會導致低一級的異常將沒有機會被使用。同級別的異常,排列順序無所謂。

異常從哪里來?

異常是由方法 throws 來的。

public void doLaundry() throws PantsException, LingerieException {}

如果你調用了一個有風險的方法,但你又不想 try/catch捕獲時怎么辦?你可以繼續使用 throws 關鍵字,把異常拋給下一個調用我的人,這好比是在踢皮球哈哈。

從上面看,程序有兩種方式來處理異常,一種是使用 try/catch 來捕獲異常,另一種是使用 throws 把異常拋給下一個調用者。


第12章:圖形用戶接口

什么是 JFrame ?

JFrame 是個代表屏幕上的window對象。你可以把 button / text / checkbox 等 widget 接口放在 window上面。

如何編寫第一個 GUI 程序?

public class TestGui implements ActionListener {
    JButton btn;
    void init() {
        // 創建 frame
        JFrame frame = new JFrame();
        // 當window關閉時,把程序結束掉
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 創建 widget
        btn = new JButton("click me");
        // 給btn 注冊事件
        btn.addActionListener(this);
        // 把 btn添加到 frame上
        frame.getContentPane().add(btn);
        // 顯示
        frame.setSize(300, 300);
        frame.setVisible(true);
    }
    // 覆寫 ActionListener接口的中的方法
    @Override
    public void actionPerformed(ActionEvent event) {
        btn.setText("I've been clicked.");      
    }
    public static void main(String[] args) {
        TestGui tg = new TestGui();
        tg.init();
    }
}

圖形界面中,事件三要素:事件源、監聽、事件對象。其中,事件對象攜帶着事件的詳細信息。

  1. 在 frame上放置 widget。javax.swing這個包中有很多 widget組件可以用。
  2. 在 frame上繪制 2D圖形。使用 graphics 對象來繪制 2D 圖形。
  3. 在 widget上繪制 jpeg圖片。 graphics.drawImage(img, 10, 10, this);

如何自定義 widget 組件?

繼承 JPanel ,並覆寫 paintComponent()這個方法。

public class CustomComponent extends JPanel {
    // 覆寫方法
    public void paintComponent(Graphics g) {
        // 繪制矩形
        g.setColor(Color.orange);
        g.fillRect(20,  50,  100, 100);
    <span class="hljs-comment">// 繪制圖像</span>
    Image img = <span class="hljs-keyword">new</span> ImageIcon(<span class="hljs-string">"./car.png"</span>).getImage();
    System.out.println(img);
    g.drawImage(img, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-keyword">this</span>);
    
    <span class="hljs-comment">// 繪制矩形</span>
    g.fillRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-keyword">this</span>.getWidth(), <span class="hljs-keyword">this</span>.getHeight());
    <span class="hljs-keyword">int</span> red = (<span class="hljs-keyword">int</span>)(Math.random()*<span class="hljs-number">255</span>);
    <span class="hljs-keyword">int</span> blue = (<span class="hljs-keyword">int</span>)(Math.random()*<span class="hljs-number">255</span>);
    <span class="hljs-keyword">int</span> green = (<span class="hljs-keyword">int</span>)(Math.random()*<span class="hljs-number">255</span>);
    <span class="hljs-comment">// 創建隨機色</span>
    Color color = <span class="hljs-keyword">new</span> Color(red, green, blue);
    g.setColor(color);
    <span class="hljs-comment">// 繪制覆蓋物</span>
    g.fillOval(<span class="hljs-number">70</span>, <span class="hljs-number">70</span>, <span class="hljs-number">100</span>, <span class="hljs-number">100</span>);
}

}

// 使用自定義的組件:
public class TestGui {
    void init() {
        JFrame frame = new JFrame();   // 創建 frame
        CustomComponent cc = new CustomComponent();
        frame.getContentPane().add(cc);
        frame.setSize(300, 300);
        frame.setVisible(true);
    }
}

什么是內部類?如何創建內部類的實例?

一個類可以嵌套在另一個類的內部,這便構成了內部類。內部類可以訪問外部類中的所有成員,包括外部類的成員變量和成員方法,即使它們是私有的。

public class OuterClass {
    private int x;
InnerClass ic = <span class="hljs-keyword">new</span> InnerClass();  <span class="hljs-comment">// 創建內部類實例 </span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">foo</span><span class="hljs-params">()</span> </span>{
    System.out.println(<span class="hljs-string">"outer class -&gt; 1: "</span> + x);
    ic.go();
    System.out.println(<span class="hljs-string">"outer class -&gt; 3: "</span> + x);
}
<span class="hljs-comment">// 內部類</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InnerClass</span> {</span>
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">go</span><span class="hljs-params">()</span> </span>{
        x = <span class="hljs-number">42</span>;  <span class="hljs-comment">// 訪問外部類的成員變量 x</span>
        System.out.println(<span class="hljs-string">"inner class -&gt; 2 : "</span>+x);
    }
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
    OuterClass oc = <span class="hljs-keyword">new</span> OuterClass();
    oc.foo();
    <span class="hljs-comment">// 另一種 創建內部類實例的方式</span>
    OuterClass.InnerClass ocic = oc.<span class="hljs-keyword">new</span> InnerClass();
    ocic.go();
}

}

內部類,有什么用? (試着寫一個GUI動畫,玩一下!)

public class SimpleAnimation {
    int x = 70;
    int y = 70;
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">go</span><span class="hljs-params">()</span> </span>{
    JFrame frame = <span class="hljs-keyword">new</span> JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    <span class="hljs-comment">// 創建內部類實例</span>
    MyDrawPanel dp = <span class="hljs-keyword">new</span> MyDrawPanel();
    frame.getContentPane().add(dp);
    frame.setSize(<span class="hljs-number">300</span>,<span class="hljs-number">300</span>);
    frame.setVisible(<span class="hljs-keyword">true</span>);
    <span class="hljs-comment">// 動畫</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>; i&lt;<span class="hljs-number">120</span>; i++) {
        x++;
        y++;
        dp.repaint();
        <span class="hljs-keyword">try</span> {
            Thread.sleep(<span class="hljs-number">50</span>);  <span class="hljs-comment">// 間隔50毫秒</span>
        }<span class="hljs-keyword">catch</span> (Exception ex) {
            System.out.println(ex);
        }
    }
}

<span class="hljs-comment">// 內部類</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyDrawPanel</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JPanel</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">paintComponent</span><span class="hljs-params">(Graphics g)</span> </span>{
        <span class="hljs-comment">// 重置畫布</span>
        g.setColor(Color.white);
        g.fillRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-keyword">this</span>.getWidth(), <span class="hljs-keyword">this</span>.getHeight());
        <span class="hljs-comment">// 重繪圓形</span>
        g.setColor(Color.red);
        g.fillOval(x, y, <span class="hljs-number">40</span>, <span class="hljs-number">40</span>);
    }
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
    SimpleAnimation sa = <span class="hljs-keyword">new</span> SimpleAnimation();
    sa.go();
}

}


第13章:Swing

什么是 Swing 組件?

組件(Component),也稱作元件。它們就是那些放在界面上與用戶進行交互的東西,如 Button / List 等。事實上,這些 GUI 組件,都來自於 java.swing.JComponent。 在 Swing 中,幾乎所有組件都可以嵌套,即一個組件可以安置在另一個組件之上。

創建 GUI 的四個步驟再回顧?

// 第1步:創建windon (JFrame)
JFrame frame = new JFrame();
// 第2步:創建組件
JButton btn = new JButton("click me");
// 第3步:把組件添加到 frame 上
frame.getContentPane().add(BorderLayout. EAST, btn);
// 第4步:顯示 GUI 界面
frame.setSize(300, 300);
frame.setVisible(true);

什么是布局管理器?

布局管理器(Layout Managers)是個與特定組件關聯的Java對象,它大多數是背景組件。布局管理器,負責組件的大小和位置。

布局管理器,是如何工作的?
有哪三大首席布局管理器?(BorderLayout / FlowLayout / BoxLayout)
BorderLayout布局有哪5個區域?(東區 / 西區 / 北區 / 南區 / 中央區)
FlowLayout布局的組件流向是怎樣的?(從左至右,從上至下)
BoxLayout布局能解決什么問題?
如何創建Swing組件?如何操作組件?

Swing實例:JTextArea文本框

public class TestSwing implements ActionListener {  
    JTextArea text;
<span class="hljs-comment">// 實現 ActionListener 接口的方法</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">actionPerformed</span><span class="hljs-params">(ActionEvent arg0)</span> </span>{
    text.append(<span class="hljs-string">"button clicked \n"</span>);       
}
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">go</span><span class="hljs-params">()</span> </span>{
    JFrame frame = <span class="hljs-keyword">new</span> JFrame();
    JPanel panel = <span class="hljs-keyword">new</span> JPanel();
    JButton button = <span class="hljs-keyword">new</span> JButton(<span class="hljs-string">"Just Click It"</span>);
    <span class="hljs-comment">// 給 button 注冊點擊事件</span>
    button.addActionListener(<span class="hljs-keyword">this</span>);
    text = <span class="hljs-keyword">new</span> JTextArea(<span class="hljs-number">10</span>, <span class="hljs-number">20</span>);
    text.setLineWrap(<span class="hljs-keyword">true</span>);
    
    JScrollPane scroller = <span class="hljs-keyword">new</span> JScrollPane(text);
    scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    panel.add(scroller);
    frame.getContentPane().add(BorderLayout.CENTER, panel);
    frame.getContentPane().add(BorderLayout.SOUTH, button);
    frame.setSize(<span class="hljs-number">350</span>, <span class="hljs-number">300</span>);
    frame.setVisible(<span class="hljs-keyword">true</span>);
}
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
    TestSwing gui = <span class="hljs-keyword">new</span> TestSwing();
    gui.go();
}

}


第14章:序列化與文件的輸入輸出

對象可以被序列化,也可以展開。對象有狀態和行為兩種屬性,行為存在於類中,而狀態存在於個別的對象中。本章將討論以下兩種選項:

  1. 如果只有自己寫的Java程序會用到這些數據。用序列化(Serialization),將被序列化的對象寫到文件中。然后就可以讓你的程序去文件中讀取序列化的對象,並把它們展開回到活生生的狀態。
  2. 如果數據需要被其它程序引用。寫一個純文本文件,用其它程序可以解析的特殊字符寫到文件中。

如何把序列化對象寫入文件?

public class TestOutputStream {
    public static void main(String[] args) {
        try {
            // 如果文件不存在,就自動創建該文件
            FileOutputStream fileStream = new FileOutputStream("mygame.ser");
            // 創建存取文件的 os 對象
            ObjectOutputStream os = new ObjectOutputStream(fileStream);
            // 把序列化對象寫入文件
            os.writeObject(new Dog());   // Dog必須是可序列化的類
            os.writeObject(new Dog());
            // 關閉所關聯的輸出串流
            os.close();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

什么是串流?

將串流( stream )連接起來代表來源與目的地(文件或網絡端口)的連接。串流必須要連接到某處才能算是個串流。Java的輸入輸出API帶有連接類型的串流,它代表來源與目的地之間的連接,連接串流即把串流與其它串流連接起來。

當對象被序列化時,被該對象引用的實例變量也會被序列化。且所有被引用的對象也會被序列化。最重要的是,這些操作都是自動完成的。

如果要讓類能夠序列化,就要實現Serializable

Serializable接口,又被稱為marker或tab類的標記接口,因為此接口並沒有任何方法需要被實現。它唯一的目的就是聲明有實現它的類是可以被序列化的。也就是說,此類型的對象可以通過序列化的機制來存儲。如果某個類是可以序列化的,則它的子類也自動地可以序列化。

// Serializable沒有方法需要實現,它只是用來告訴Java虛擬機這個類可以被序列化
public class Box implements Serializable {
    public Box() {}
}

注意:序列化是全有或全無的,即對象序列化時不存在“一部分序列化成功、另一部分序列化失敗”的情況,如果對象有一部分序列化失敗,則整個序列化過程就是失敗的。只有可序列化的對象才能被寫入到串流中。

那么在一個可序列化的類中,如何指定部分實例變量不執行序列化呢?

如果希望某個實例變量不能或不應該被序列化,就把它標記為 transient(瞬時)的,即可。

// Serializable沒有方法需要實現,它只是用來告訴Java虛擬機這個類可以被序列化
public class Box implements Serializable {
    public Box() {}
<span class="hljs-comment">// 該id變量,就不會被序列化</span>
<span class="hljs-keyword">transient</span> String id;
String username;

}

如何從文件中讀取序列化對象,並將其還原?

把對象恢復到存儲時的狀態。解序列化,可看成是序列化的反向操作。

public class TestInputStream {
    public static void main(String[] args) {
        try {
            // 打開文件流,如果文件不存在就會報錯
            FileInputStream fileStream = new FileInputStream("mygame.ser");
            // 創建 輸入流
            ObjectInputStream os = new ObjectInputStream(fileStream);
            // 讀取 序列化對象 
            Object one = os.readObject();
            Object two = os.readObject();
            // 類型轉換,還原對象類型
            Dog d1 = (Dog)one;
            Dog d2 = (Dog)two;
            // 關注 輸入流
            os.close();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

序列化(存儲)和解序列化(恢復)的過程,到底到生了什么事?

你可以通過序列化來存儲對象的狀態,使用ObjectOutputStream來序列化對象。Stream是連接串流或是鏈接用的串流,連接串流用來表示源或目的地、文件、網絡套接字連接。鏈接用串流用來鏈接連接串流。使用FileOutputStream將對象序列化到文件上。靜態變量不會被序列化,因為所有對象都是共享同一份靜態變量值。
對象必須實現Serializable 這個接口,才能被序列化。如果父類實現了它,則子類就自動地有實現。
解序列化時,所有的類都必須能讓Java虛擬機找到。讀取對象的順序必須與寫入時的順序一致。

如何把字符串寫入文件文件?

try {
  // FileWriter
  FileWriter writer = new FileWriter("foo.txt");
  writer.write("hello foo!");
  writer.close();
} catch (IOException e) {
  System.out.print(e);
}

java.io.File

File對象代表磁盤上的文件或者目錄的路徑名稱,如 /Users/Kathy/Data/game.txt 。但它並不能讀取或代表文件中的數據。使用 File對象,可以做以下事情:

// 創建 File對象
File f = new File("game.txt");
// 創建新的目錄
File f1 = new File("data");
f1.mkdir();
// 列舉目錄下的內容
f1.list();
// 獲取文件或目錄的絕對路徑
f1.getAbsolutePath();
// 刪除文件或目錄
f1.delete();

什么是緩沖區?為什么使用緩沖區會提升數據讀寫的效率?

沒有緩沖區,就好像逛超市沒有推車一樣,你只能一次拿一項商品去結賬。緩沖區能讓你暫時地擺一堆東西,直到裝滿為止。用了緩沖區,就可以省下好幾趟的來回。
緩沖區的奧妙之處在於,使用緩沖區比沒有使用緩沖區的效率更好。通過 BufferedWriter 和 FileWriter 的鏈接,BufferedWriter 可以暫存一堆數據,等到滿的時候再實際寫入磁盤,這樣就可以減少對磁盤的操作次數。如果想要強制緩沖區立即寫入,只要調用 writer.flush() 這個方法即可。

如何從文本文件中讀取數據?

File對象表示文件,FileReader用於執行實際的數據讀取,BufferedReader讓讀取更有效率。讀取數據,使用 while 循環來逐行讀取,直到 readLine() 的結果為 null 為止。這是最常見的數據讀取方式(幾乎所有的非序列化對象都是這樣的)。

public class TestFileReader {
    public static void main(String[] args) {
        try {
            File file = new File("foo.txt");
            // 連接到文件文件的串流
            FileReader fr = new FileReader(file);
            // 使用 Buffered 緩沖區
            BufferedReader reader = new BufferedReader(fr);
            String line = null;
            while((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            // 關閉流
            reader.close();
        } catch (Exception ex) {
            System.out.println(ex);
        }
    }
}

什么是 serialVersionUID?為什么要使用 serialVersionUID?

每當對象被序列化的同時,該對象(以及所有在其版圖上的對象)都會被“蓋”上一個類的版本識別ID,這個ID就被稱為 serialVersionUID ,它是根據類的結構信息計算出來的。在對象被解序列化時,如果在對象被序列化之后類有了不同的 serialVersionUID,則解序列化會失敗。雖然會失敗,但你還可以有控制權。
如果你認為類有可能會深化,就把版本識別ID(serialVersionUID)放在類中。當Java嘗試解序列化還原對象時,它會對比對象與Java虛擬機上的類的serialVersionUID 是否相同。如果相同,則還原成功;否則,還原將失敗,Java虛擬機就會拋出異常。因此,把 serialVersionUID 放在類中,讓類在演化過程中保持 serialVersionUID 不變。

public class Dog {
  // 類的版本識別ID
  private static final long serialVersionUID = -54662325652236L;
}

若想知道某個類的 serialVersionUID 是多少?則可以使用 Java Development Kit 里的 serialver 工具進行查詢。

serialver Dog
Dog: static final long serialVersionUID = -54662325652236L;


第15章:網絡與線程(網絡聯機)

在 Java中,所有網絡運作的低層細節都已經由 java.net 函數庫處理。Java中,傳送和接收網絡上的數據,是在鏈接上使用不同鏈接串流的輸入和輸出。

什么是 Socket 連接?

Socket 是個代表兩台機器之間網絡連接的對象(java.net.Socket)。什么是連接?就是兩台機器之間的一種關系,讓兩個軟件相互認識對方,能夠彼此發送和接收數據。在Java中,這是一種讓運行在Java虛擬機上的程序能夠找到方法去通過實際的硬件(比如網卡)在機器之間傳送數據的機制。

如何創建一個 Socket 連接呢?

要創建 Socket,得知道兩項關於服務器的信息:它在哪里(IP地址)?它使用的是哪個端口號?

Socket chat = new Socket("196.164.1.103", 3000);

什么是 TCP 端口號?

TCP 端口號,是一個 16位寬、用來識別服務器上特定程序的數字。端口號,代表了服務器上執行軟件的邏輯識別。如果沒有端口號,服務器就無法分辨客戶端是要連接哪個應用程序的服務。每個應用程序可能都有獨特的工作交談方式,如果沒有識別就發送的話會造成很大的麻煩,所以在同一台機器上不同的應用程序服務其端口號一定不同。
從 0 ~ 1023 的TCP 端口號是保留已知的特定服務使用,我們不應該使用這些端口號。當我們開發自己的應用程序服務時,可以從 1024 ~ 65535 之間選擇一個數字作為端口號。

如何從 Socket 上讀取數據?

用串流來通過 Socket 連接來溝通。使用 BufferedReader 從 Socket上讀取數據。在Java中,大部分的輸入與輸出工作並不在乎鏈接串流的上游是什么,也就是說可以使用BufferedReader讀取數據而不用管串流是來自文件還是Socket。

// 1 - 建立對服務器的 Socket 連接
Socket chat = new Socket("192.168.1.100", 3000);
// 2 - 建立連接到Socket上低層的輸入串流
InputStreamReader stream = new InputStreamReader(chat.getInputStream());
// 3 - 建立Buffer來提升數據讀取的效率
BufferedReader reader = new BufferedReader(stream);
// 4 - 讀取數據
String msg = reader.readLine();
read.close();

如何向 Socket 中寫入數據?

使用 PrintWriter 是最標准的做法。它的 print() / println() 方法,就跟 System.out 里的方法剛好一樣。

// 1 - 對服務器建立 Socket 連接
Socket chat = new Socket("192.168.1.100", 3000);
// 2 - 建立連接到 Socket 的 PrintWriter
PrintWriter writer = new PrintWriter(chat.getOutputStream());
// 3 - 寫入數據
writer.println("hello world");
writer.print("hello socket");

如何編寫一個 Socket 服務器?

public class TestSocket {
    public void go() {
        try {
            // 創建 服務端Socket,並監聽 3000端口
            ServerSocket serverSocket = new ServerSocket(3000);
            // 服務器進入無窮循環,等待客戶端的請求
            while(true) {
                // 等待連接
                Socket sock = serverSocket.accept();
                // 當有連接時,向 socket中寫入數據
                PrintWriter writer = new PrintWriter(sock.getOutputStream());
                writer.println("hello world");
                writer.close();
            }
        } catch (IOException e) {
            System.out.println(e);
        }
    }
    public static void main(String[] args) {
        TestSocket ts = new TestSocket();
        // 啟動 socket 服務
        ts.go();
    }
}

什么是 Java 線程?

  1. 一個Java線程(thread),就是一個獨立的執行空間(stack)。Java語言中內置了多線程功能,線程的任務就是運行程序代碼。
  2. java.lang.Thread類,表示Java中的線程,使用它可以創建線程實例。
  3. 每個Java應用程序都會啟動一個主線程——把 main() 方法放進它自己執行空間的最開始處。Java虛擬機會負責主線程的啟動。

怎么理解多線程的工作原理?

當有超過一個以上的執行空間時,看起來像是有好幾件事情同時在發生。實際上,只有真正的多核處理器系統才能夠同時做好幾件事情。使用Java多線程可以讓它看起來好像同時在做多個事情。也就是說,執行運作可以在執行空間中非常快速地來回切換,因此你會感覺到每項任務都在執行。線程要記錄的一項事物是目前線程執行空間做到了哪里。Java虛擬機,會在多個線程之間來回切換,直到它們都執行完為止。

如何創建一個新的線程?

對一個線程而言,Thread線程就是一個工人,而Runnable就是這個工作的工作。Runnable帶有會放在執行空間的第一項的方法:run() 。Thread對象不可以重復使用,一旦該線程的run()方法完成后,該線程就不能再重新啟動了。

public class TestThread implements Runnable {
    // 實現 Runnable 接口的 run 方法
    public void run() {
        System.out.println("執行");
    }
    public static void main(String[] args) {
        // 創建線程的工作任務
        Runnable threadObj = new TestThread();
        // 創建線程,並把工作任務傳遞給線程
        Thread myThread = new Thread(threadObj);
        // 啟動線程
        myThread.start();
    }
}

線程有哪三種狀態?

線程實例一旦 start() 后,會有三種可能的狀態:可執行狀態、執行中狀態、堵塞狀態。在不發生堵塞的情況下,線程會在可執行狀態和執行中狀態之間來來回回地切換。

什么是線程調度器?

線程調度器會決定哪個線程從等待狀態中被挑出來運行,以及何時把哪個線程送回等待被執行狀態。它會決定某個線程要運行多久,當線程被踢出去時,調度器也會指定線程要回去等待下一個機會或者是暫時地堵塞。我們無法控制線程調度器,沒有API可以調用調度器,調度器在不同的Java虛擬機上也不盡相同。
線程調度器會做所有的決定,誰跑誰停都由它說了算。它通常是公平的,但沒有人能保證這件事兒,有時候某些線程會很受寵,有些線程會被冷落。多個線程任務,誰先執行完誰后執行完,並不能人為地確定。

線程休眠 Thread.sleep()

如果想要確定其它線程有機會執行的話,就把線程放進休眠狀態。當線程醒來的時候,它會進入可以執行狀態等待被調度器挑出來執行。使用 sleep() 能讓程序變得更加可預測。

try {
  Thread.sleep(2000);
} catch (InterruptedException ex) {
  e.printStackTrace();
}

給線程取個非默認的名稱

Thread thread = new Thread(runnableOjb);
// 自定義線程的名字
thread.setName("a name");
// 獲取當前在執行線程的名字
String name = Thread.currentThread().getName();

線程有什么缺點嗎?

線程,可能會導致並發性問題(concurrency),發生競爭狀態,競爭狀態會引發數據的損毀。兩個或以上的線程存取單一對象的數據,也就是說兩個不同執行空間上的方法都在堆上對同一對象執行 getter / setter。

如何解決線程並發性引起的競爭狀態問題呢?

這就需要一把 getter/setter 的鎖。比如當多個執行空間上有多個方法同時執行同一個銀行賬戶的交易任務時:當沒有交易時,這個鎖是開啟的;當有交易任務時,這個鎖會被鎖住;從而保證了在同一時刻有且僅有一個方法能執行賬戶的交易任務。從而有效地避免了並發性的競爭狀態問題。
使用 synchronized 關鍵字來修飾線程任務中的方法,它代表線程需要一把鑰匙來存取被同步化過的線程。要保護數據,就把作用在數據上的方法同步化。
每個Java對象都有一個鎖,每個鎖只有一把鑰匙。通常對象都沒上鎖,也沒有人在乎這件事兒。但如果對象有同步化的方法,則線程只能在取得鎖匙的情況下進入線程。也就是說,並沒有其它線程進入的情況下才能進入。
用同步機制,讓操作同一個數據對象的多個方法原子化。一旦線程進入了方法,我們必須保證在其它線程可以進入該方法之前所有的步驟都會完成(如同原子不可分割一樣)。

// 使用 synchronized 關鍵字,讓方法原子化!
public synchronized void increment() {
  int i = balance;
  balance = i + 1;
}

注意:雖然 synchronized同步化可以解決多線程並發性引起的競爭狀態問題,但並不是說讓所有方法都同步化。原則上,程序中讓同步化的方法越少越好。

什么是同步化的死鎖問題?

同步化死鎖會發生,是因為兩個線程相互持有對方正在等待的東西。沒有方法可以脫離這個情況,所以兩個線程只好停下來等,一直等。在Java中,並沒有處理死鎖的內置機制,所以編寫程序時要注意這一問題,避免發生死鎖。


第16章:集合與泛型(數據結構)

有哪些常用的集合?

  1. ArrayList
  2. TreeSet 以有序狀態保存並可防止數據重復
  3. HashMap 以鍵值對的形式保存數據
  4. LinkedList 針對經常插入或刪除中間元素所設計的高效率集合
  5. HashSet 防止重復的集合,可快速地尋找相符的元素
  6. LinkedHashMap

什么是泛型?為什么使用泛型?

在Java中,看到 <> 這一組尖括號,就代表泛型,這是從Java5.0開始加入的特性。
幾乎所有你會以泛型寫的程序都與處理集合有關。雖然泛型還可以用到其它地方,但它主要的目的還是讓你能夠寫出有類型安全性的集合。也就是說,讓編譯器能夠幫忙防止你把 Dog加入到一群 Cat中。

關於泛型,最重要的 3 件事

  1. 如何創建被泛型化類的實例?
  2. 如何聲明指定泛型類型的變量?多態遇到泛型類型會怎樣?
  3. 如何聲明或調用泛型類型的方法?
// 創建被泛型化的類
List<Song> songList = new ArrayList<Song>();
// 聲明泛型類型的方法
public void foo(List<Song> list) {}
// 調用泛型類型的方法
x.foo(songList);

集合有哪 3 類主要的接口?

  1. List,是一種知道索引位置的集合,這是對待順序的好幫手。
  2. Set,不允許重復的集合,它注重獨一無二的性質。
  3. Map,使用鍵值對存儲數據的集合,用 key 來搜索。

對象要怎樣才算相等? Set集合是如何檢查元素是否重復的?

如果 foo 和 bar 兩對象相等,則 foo.equals(bar) 會返回 true ,且二者的 hashCode() 也會返回相同的值。要讓 Set 能把對象視為重復的,就必須讓它們符合上面的對象相等條件。

  1. 引用相等性:堆上同一對象的兩個引用。 foo == bar; // true
  2. 對象相等性:堆上兩個不同的對象在意義上是相同的。 foo.equals(bar) && foo.hashCode() == bar.hashCode(); // true

特點注意: == / equals() / hashCode() 三者之間的異同。

TreeSet的元素必須是 Comparable 的

TreeSet無法猜到程序員的想法,你必須指出TreeSet中的元素該如何排序。方案有二,其一是集合的元素都必須是實現了 Comparable 的類型;其二使用重載,取用 Comparator 參數的構造函數來創建 TreeSet。代碼示例如下:

// 實現了 Comparable 接口的類,可用於TreeSet的元素
class Book implements Comparable {
    String title;
    public Book(String t) {
        title = t;
    }
    // 實現接口方法
    public int compareTo(Object b) {
        Book book = (Book)b;
        return (title.compareTo(book.title));
    }
}
public class TestTreeSet {
    public static void main(String[] args) {
        Book b1 = new Book("yiyi");
        Book b2 = new Book("titi");
        Book b3 = new Book("bibi");
        TreeSet<Book> ts = new TreeSet<Book>();
        ts.add(b1);
        ts.add(b2);
        ts.add(b3);
        System.out.print(ts);
    }
}
public class TestComparator implements Comparator<Book> {
    // 實現接口方法
    public int compare(Book b1, Book b2) {
        return (b1.title.compareTo(b2.title));
    }
    public static void main(String[] args) {
        TestComparator tc = new TestComparator();
        TreeSet<Book> tree = new TreeSet<Book>(tc);
        tree.add(new Book("zizi"));
        tree.add(new Book("cici"));
        tree.add(new Book("mimi"));
        tree.add(new Book("fifi"));
        System.out.print(tree);
    }
}

Map 數據結構的使用(鍵名不可重復)

public class TestMap {
    public static void main(String[] args) {
        HashMap<String, Integer> scores = new HashMap<String, Integer>();
        scores.put("Kathy", 40);
        scores.put("Bert", 50);
        System.out.println(scores);
    }
}

泛型與多態

如果方法的參數是 Animal數組,那么它就能夠取用 Animal次類型的數組。也就是說,如果方法是這樣聲明的:void foo( Animal[] a ) {} 。 若Dog 有 extends Animal,你就可以用以下兩種方式調用 foo方法:foo(anAnimalArray); foo(aDogArray) 。

public class TestType {
    public void go() {
        Animal[] animals = { new Dog(), new Cat() };
        Dog[] dogs = { new Dog(), new Dog() };
        // 多態
        takeAnimals(animals);
        takeAnimals(dogs);
    }
    public void takeAnimals(Animal[] animals) {
        // 多態參數: animals 可以是 Animal及其子類的對象數組
        for (Animal a: animals) {
            System.out.println(a);
        }
    }
}

什么是萬用字符?有什么用?

使用萬用字符,你可以操作集合元素,但不能新增集合元素。如此保證了程序執行期間的安全性。

public <T extends Animal> void takeThing(ArrayList<T> list) { }
// 等價於
public void takeThing(ArrayList<? extends Animal> list) { }


第17章:包、jar存檔文件和部署(發布程序)

Java程序,是由一組類所組成的,這就是開發過程的輸出。本章將討論如何組織、包裝和部署Java程序。

如何組織Java代碼文件?

組織代碼文件的方案有很多。建議一種幾乎已經成為了標准的組織方案——在 source目錄下放置 .java 源文件;在 classes 目錄下放置 .class 編譯結果。使用 -d 選項,可以指定編譯結果的存儲目錄。

// -d 用於指定編譯結果的存儲目錄
javac  -d  ../classes  MyApp.java

mainifest文件 與 JAR包

JAR即 Java ARchive。這種文件是個 pkzip 格式的文件,它能夠讓你把一組類文件包裝起來,所以交付時只需要一個 JAR文件即可。它類似 Linux上的 tar命令。可執行的 JAR 代表用戶不需要把文件抽出來就能運行,這個秘密就在於 mainifest文件,它攜帶着 JAR包的若干信息,並且能告訴Java虛擬機哪個類中含有 main() 方法。

如何創建 JAR 包?

cd  myproject/classes
jar  -cvmf  mainifest.txt  app.jar  *.class
或者
jar  -cvmf  mainifest.txt  app.jar  MyApp.class

如何執行 JAR包?

cd  myproject/classes
java  -jar  app.jar

大部分的Java應用,都是以可執行的JAR包來部署的。Java虛擬機能夠從JAR包中載入類,並調用該類中的 main() 方法。事實上,整個應用程序都可以包在 JAR包中。一旦 main() 方法開始執行,Java虛擬機就不會在乎類是從哪里來的,只要能夠找到就行。其中一個來源就是 classpath 指定位置的所有 JAR文件。
Java虛擬機必須要能找到JAR,所以它必須在 classpath 下。讓 JAR曝光的最好方式就是把 JAR放置在工作目錄下。Java虛擬機會檢查 JAR包中的 manifest文件以尋找入口,如果沒有找到就會發生運行期間異常。
根據操作系統的設置,雙擊 JAR文件也可以直接運行 JAR文件。

如何防止類名、包名的命名沖突?

  1. 用包防止類名的命名沖突。
  2. 用 domain 名稱防止包名的命名沖突。
./source/cn/geekxia/*.java
./classes/cn/geekxia/*.class

如何列舉 JAR包中的文件?

jar  -tf  app.jar

如何解壓 .jar包?

jar  -xf  app.jar

關於 Java Web Start 技術的一些知識點

Java Web Start技術讓你能夠從網站來部署獨立的客戶端程序。Java Web Start有個必須要安裝在客戶端的helper app。Java Web Start程序由兩個部分組成:可執行的JAR包和.jnlp文件。 .jnlp文件是用來描述Java Web Start應用程序的XML文件。它有tag以指定JAR的名稱和位置,以及帶有 main()的類名稱。當瀏覽器從服務器上取得 .jnlp文件時,瀏覽器就會啟動 Java Web Start的 helper app。Java Web Start會讀取 .jnlp文件來判斷要從服務器上下載的可執行JAR包。取得JAR包之后,它就會調用 .jnlp指定的 main()。


第18章:遠程部署 RMI(分布式計算)

什么是 RMI ?

即 Remote Method Invocation,遠程方法調用技術。

截止目前,我們學習到的Java方法調用,都是發生在相同堆上的兩個對象之間。即對象的方法調用是發生在同一個Java虛擬機上面的。那么,如果我們要調用另一台Java機器上的對象方法,該如何實現呢?這就需要用到 RMI 遠程方法調用技術

如何設計一個 RMI 遠程過程調用?

四要素:服務器、客戶端、服務器輔助設施 和 客戶端輔助設施。(客戶端對象看起來像是在調用遠程機器上的對象方法,但實際上它只是在調用本地處理Socket和串流細節的客戶端輔助設施這一“代理 proxy”)。
這四要素的關系如下:1、客戶端對象以為它是在與真正的服務器溝通,它以為客戶端輔助設施就是真正的服務器; 2、客戶端輔助設施偽裝成服務器,但實際上它只是真正服務器的代理人而已; 3、服務端輔助設施收到客戶端輔助設施的請求后,解開包裝,調用真正的服務器對象; 4、在服務器上,才會真正地執行這個對象方法。

使用 Java RMI

在Java中,RMI 已經幫我們創建好了客戶端和服務端的輔助設施。使用 RMI ,我們無需編寫任何網絡或輸入/輸出的程序,客戶端在對遠程方法的調用時就跟調用同一台Java虛擬機上的方法是一樣的。
在 RMI 中,客戶端輔助設施被稱為 RMI stub,而服務端輔助設施被稱為 RMI skeleton。代碼示例如下:

創建 Remote 接口:

public interface MyRemote extends Remote {
    public String sayHello() throws RemoteException;
}

實現 Remote Service:

public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
    public MyRemoteImpl() throws RemoteException {}
    public String sayHello() {
        return "Server says, Hi ";
    }
    public static void main(String[] args) {
        try {
            MyRemote service = new MyRemoteImpl();
            Naming.rebind("Remote Hello", service);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

完整的客戶端程序代碼:

public class MyRemoteClient {
    public void go() {
        try {
            MyRemote service = (MyRemote)Naming.lookup("rmi://127.0.0.1/Remote Hello");
            String s = service.sayHello();
            System.out.print(s);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

什么是 Servlet ?

Servlet是放在HTTP Web服務器上面運行的 Java程序。當用戶通過瀏覽器和網頁進行交互時,請求會被發送至網頁服務器。如果請求需要Java的 Servlet時,服務器會執行或調用已經執行的 Servlet程序代碼。Servlet只是在服務器上運行的程序代碼,執行並響應用戶發出請求所要的結果。

public class MyServlet extends HttpServlet {
  public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
    res.setContentType("text/html");
    // ...
  }
}


附錄:最后十大知識點

枚舉 Enum 、多維數組、String 和 StringBuffer / StringBuilder 、存取權限和訪問修飾符、Anonymous 和 Static Nested Classes 、鏈接的調用、塊區域、斷言、不變性、位操作。



本書 完 2018-08-06!!!

原文地址:https://www.jianshu.com/p/6001c9190ac9


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM