Head First Java 讀書筆記(完整)

第0章:學習方法建議

該如何學習Java?

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

須要哪些環境和工具?

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


第1章:進入Java的世界

Java有哪些特色?

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

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

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

Java歷史?

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

Java的程序結構是怎樣的?

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

爲何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執行這個程序api

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;

    // 公有的 Setter,在類的外部,經過dog.setSize()來設置size的值
    public void setSize(int size) {
        this.size = size;
    }
    // 公有的 Getter,在類的外部,經過dog.getSize()來獲取size的值
    public int getSize() {
        return this.size;
    }

    void bark() {
        if (size > 10) {} else {}
    }
}

數組中的元素,還能夠是對象。

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;
    
    public Duck() {
        size++;
        count++;
        System.out.println("size " + size);
        System.out.println("count " + count);
    }
    public void setSize(int s) {
        size = s;
    }
    public int getSize() {
        return 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 += 1000*60*60;
        
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);
        
        // 繪製圖像
        Image img = new ImageIcon("./car.png").getImage();
        System.out.println(img);
        g.drawImage(img, 3, 4, this);
        
        // 繪製矩形
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
        int red = (int)(Math.random()*255);
        int blue = (int)(Math.random()*255);
        int green = (int)(Math.random()*255);
        // 建立隨機色
        Color color = new Color(red, green, blue);
        g.setColor(color);
        // 繪製覆蓋物
        g.fillOval(70, 70, 100, 100);
    }
}
// 使用自定義的組件:
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 = new InnerClass();  // 建立內部類實例 
    public void foo() {
        System.out.println("outer class -> 1: " + x);
        ic.go();
        System.out.println("outer class -> 3: " + x);
    }
    // 內部類
    class InnerClass {
        void go() {
            x = 42;  // 訪問外部類的成員變量 x
            System.out.println("inner class -> 2 : "+x);
        }
    }
    
    public static void main(String[] args) {
        OuterClass oc = new OuterClass();
        oc.foo();
        // 另外一種 建立內部類實例的方式
        OuterClass.InnerClass ocic = oc.new InnerClass();
        ocic.go();
    }
}

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

public class SimpleAnimation {
    int x = 70;
    int y = 70;
    
    public void go() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 建立內部類實例
        MyDrawPanel dp = new MyDrawPanel();
        frame.getContentPane().add(dp);
        frame.setSize(300,300);
        frame.setVisible(true);
        // 動畫
        for (int i=0; i<120; i++) {
            x++;
            y++;
            dp.repaint();
            try {
                Thread.sleep(50);  // 間隔50毫秒
            }catch (Exception ex) {
                System.out.println(ex);
            }
        }
    }
    
    // 內部類
    class MyDrawPanel extends JPanel {
        public void paintComponent(Graphics g) {
            // 重置畫布
            g.setColor(Color.white);
            g.fillRect(0, 0, this.getWidth(), this.getHeight());
            // 重繪圓形
            g.setColor(Color.red);
            g.fillOval(x, y, 40, 40);
        }
    }
    
    public static void main(String[] args) {
        SimpleAnimation sa = new 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;

    // 實現 ActionListener 接口的方法
    public void actionPerformed(ActionEvent arg0) {
        text.append("button clicked \n");       
    }
    public void go() {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        JButton button = new JButton("Just Click It");
        // 給 button 註冊點擊事件
        button.addActionListener(this);
        text = new JTextArea(10, 20);
        text.setLineWrap(true);
        
        JScrollPane scroller = new 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(350, 300);
        frame.setVisible(true);
    }
    public static void main(String[] args) {
        TestSwing gui = new 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() {}
    
    // 該id變量,就不會被序列化
    transient 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」)。
這四要素的關係以下:一、客戶端對象覺得它是在與真正的服務器溝通,它覺得客戶端輔助設施就是真正的服務器; 二、客戶端輔助設施假裝成服務器,但實際上它只是真正服務器的代理人而已; 三、服務端輔助設施收到客戶端輔助設施的請求後,解開包裝,調用真正的服務器對象; 四、在服務器上,纔會真正地執行這個對象方法。

使用 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
相關文章
相關標籤/搜索