硬肝一個月,為你寫成Java開發手冊

程序員cxuan 發佈 2020-05-16T09:27:50+00:00

先來看一下本篇文章的思維導圖吧,我會圍繞下面這些內容進行講解。內容很乾,小夥伴們看完還希望不吝轉發。下面開始我們的文章。Java 概述什麼是 Java?Java 是 Sun Microsystems 於1995 年首次發布的一種程式語言和計算平台。

先來看一下本篇文章的思維導圖吧,我會圍繞下面這些內容進行講解。內容很乾,小夥伴們看完還希望不吝轉發。

下面開始我們的文章。

Java 概述

什麼是 Java?

Java 是 Sun Microsystems 於1995 年首次發布的一種程式語言和計算平台。程式語言還比較好理解,那麼什麼是 計算平台呢?

計算平台是在電腦中運行應用程式(軟體)的環境,包括硬體環境和軟體環境。一般系統平台包括一台電腦的硬體體系結構、作業系統、運行時庫。

Java 是快速,安全和可靠的。 從筆記本電腦到數據中心,從遊戲機到科學超級計算機,從手機到網際網路,Java 無處不在!Java 主要分為三個版本

  • JavaSE(J2SE)(Java2 Platform Standard Edition,java平台標準版)
  • JavaEE(J2EE)(Java 2 Platform,Enterprise Edition,java平台企業版)
  • JavaME(J2ME)(Java 2 Platform Micro Edition,java平台微型版)。

Java 的特點

  • Java 是一門面向對象的程式語言

什麼是面向對象?面向對象(Object Oriented) 是一種軟體開發思想。它是對現實世界的一種抽象,面向對象會把相關的數據和方法組織為一個整體來看待。

相對的另外一種開發思想就是面向過程的開發思想,什麼面向過程?面向過程(Procedure Oriented) 是一種以過程為中心的編程思想。舉個例子:比如你是個學生,你每天去上學需要做幾件事情?

起床、穿衣服、洗臉刷牙,吃飯,去學校。一般是順序性的完成一系列動作。

class student {
                void student_wakeUp(){...}
        void student_cloth(){...}
        void student_wash(){...}
        void student_eating(){...}
        void student_gotoSchool(){...}
}

而面向對象可以把學生進行抽象,所以這個例子就會變為

class student(){
        void wakeUp(){...}
        void cloth(){...}
        void wash(){...}
        void eating(){...}
        void gotoSchool(){...}
}

可以不用嚴格按照順序來執行每個動作。這是特點一。

  • Java 摒棄了 C++ 中難以理解的多繼承、指針、內存管理等概念;不用手動管理對象的生命周期,這是特徵二。
  • Java 語言具有功能強大和簡單易用兩個特徵,現在企業級開發,快速敏捷開發,尤其是各種框架的出現,使 Java 成為越來越火的一門語言。這是特點三。
  • Java 是一門靜態語言,靜態語言指的就是在編譯期間就能夠知道數據類型的語言,在運行前就能夠檢查類型的正確性,一旦類型確定後就不能再更改,比如下面這個例子。
public void foo() {
    int x = 5;
    boolean b = x;
}

靜態語言主要有 Pascal, Perl, C/C++, JAVA, C#, Scala 等。

相對應的,動態語言沒有任何特定的情況需要指定變量的類型,在運行時確定的數據類型。比如有**Lisp, Perl, Python、Ruby、JavaScript **等。

從設計的角度上來說,所有的語言都是設計用來把人類可讀的代碼轉換為機器指令。動態語言是為了能夠讓程式設計師提高編碼效率,因此你可以使用更少的代碼來實現功能。靜態語言設計是用來讓硬體執行的更高效,因此需要程式設計師編寫準確無誤的代碼,以此來讓你的代碼儘快的執行。從這個角度來說,靜態語言的執行效率要比動態語言高,速度更快。這是特點四。

  • Java 具有平台獨立性和可移植性

Java 有一句非常著名的口號: Write once, run anywhere,也就是一次編寫、到處運行。為什麼 Java 能夠吹出這種牛批的口號來?核心就是 JVM。我們知道,計算機應用程式和硬體之間會屏蔽很多細節,它們之間依靠作業系統完成調度和協調,大致的體系結構如下

那麼加上 Java 應用、JVM 的體系結構會變為如下

Java 是跨平台的,已編譯的 Java 程序可以在任何帶有 JVM 的平台上運行。你可以在 Windows 平台下編寫代碼,然後拿到 Linux 平台下運行,該如何實現呢?

首先你需要在應用中編寫 Java 代碼;

用 Eclipse 或者 javac 把 Java 代碼編譯為 .class 文件;

然後把你的 .class 文件打成 .jar 文件;

然後你的 .jar 文件就能夠在 Windows 、Mac OS X、Linux 系統下運行了。不同的作業系統有不同的 JVM 實現,切換平台時,不需要再次編譯你的 Java 代碼了。這是特點五。

  • Java 能夠容易實現多線程

Java 是一門高級語言,高級語言會對用戶屏蔽很多底層實現細節。比如 Java 是如何實現多線程的。從作業系統的角度來說,實現多線程的方式主要有下面這幾種

在用戶空間中實現多線程

在內核空間中實現多線程

在用戶和內核空間中混合實現線程

而我認為 Java 應該是在 用戶空間 實現的多線程,內核是感知不到 Java 存在多線程機制的。這是特點六。

  • Java 具有高性能

我們編寫的代碼,經過 javac 編譯器編譯稱為 字節碼(bytecode),經過 JVM 內嵌的解釋器將字節碼轉換為機器代碼,這是解釋執行,這種轉換過程效率較低。但是部分 JVM 的實現比如 Hotspot JVM 都提供了 JIT(Just-In-Time) 編譯器,也就是通常所說的動態編譯器,JIT 能夠在運行時將熱點代碼編譯機器碼,這種方式運行效率比較高,這是編譯執行。所以 Java 不僅僅只是一種解釋執行的語言。這是特點七。

  • Java 語言具有健壯性

Java 的強類型機制、異常處理、垃圾的自動收集等是 Java 程序健壯性的重要保證。這也是 Java 與 C 語言的重要區別。這是特點八。

  • Java 很容易開發分布式項目

Java 語言支持 Internet 應用的開發,Java 中有 net api,它提供了用於網絡應用編程的類庫,包括URL、URLConnection、Socket、ServerSocket等。Java的 RMI(遠程方法激活)機制也是開發分布式應用的重要手段。這是特點九。

Java 開發環境

JDK

JDK(Java Development Kit)稱為 Java 開發包或 Java 開發工具,是一個編寫 Java 的 Applet 小程序和應用程式的程序開發環境。JDK是整個Java的核心,包括了Java運行環境(Java Runtime Environment),一些Java 工具 和 Java 的核心類庫(Java API)。

我們可以認真研究一下這張圖,它幾乎包括了 Java 中所有的概念,我使用的是 jdk1.8,可以點進去 Description of Java Conceptual Diagram, 可以發現這裡面包括了所有關於 Java 的描述

Oracle 提供了兩種 Java 平台的實現,一種是我們上面說的 JDK,Java 開發標準工具包,一種是 JRE,叫做Java Runtime Environment,Java 運行時環境。JDK 的功能要比 JRE 全很多。

JRE

JRE 是個運行環境,JDK 是個開發環境。因此寫 Java 程序的時候需要 JDK,而運行 Java 程序的時候就需要JRE。而 JDK 裡面已經包含了JRE,因此只要安裝了JDK,就可以編輯 Java 程序,也可以正常運行 Java 程序。但由於 JDK 包含了許多與運行無關的內容,占用的空間較大,因此運行普通的 Java 程序無須安裝 JDK,而只需要安裝 JRE 即可。

Java 開發環境配置

這個地方不再多說了,網上有很多教程配置的資料可供參考。

Java 基本語法

在配置完 Java 開發環境,並下載 Java 開發工具(Eclipse、IDEA 等)後,就可以寫 Java 代碼了,因為本篇文章是從頭梳理 Java 體系,所以有必要從基礎的概念開始談起。

數據類型

在 Java 中,數據類型只有四類八種

  • 整數型:byte、short、int、long

byte 也就是字節,1 byte = 8 bits,byte 的默認值是 0 ;

short 占用兩個字節,也就是 16 位,1 short = 16 bits,它的默認值也是 0 ;

int 占用四個字節,也就是 32 位,1 int = 32 bits,默認值是 0 ;

long 占用八個字節,也就是 64 位,1 long = 64 bits,默認值是 0L;

所以整數型的占用字節大小空間為 long > int > short > byte

  • 浮點型

浮點型有兩種數據類型:float 和 double

float 是單精度浮點型,占用 4 位,1 float = 32 bits,默認值是 0.0f;

double 是雙精度浮點型,占用 8 位,1 double = 64 bits,默認值是 0.0d;

  • 字符型

字符型就是 char,char 類型是一個單一的 16 位 Unicode 字符,最小值是 \u0000 (也就是 0 ),最大值是 \uffff (即為 65535),char 數據類型可以存儲任何字符,例如 char a = 'A'。

  • 布爾型

布爾型指的就是 boolean,boolean 只有兩種值,true 或者是 false,只表示 1 位,默認值是 false。

以上 x 位都指的是在內存中的占用。

基礎語法

  • 大小寫敏感:Java 是對大小寫敏感的語言,例如 Hello 與 hello 是不同的,這其實就是 Java 的字符串表示方式
  • 類名:對於所有的類來說,首字母應該大寫,例如 MyFirstClass
  • 包名:包名應該儘量保證小寫,例如 my.first.package
  • 方法名:方法名首字母需要小寫,後面每個單詞字母都需要大寫,例如 myFirstMethod()

運算符

運算符不只 Java 中有,其他語言也有運算符,運算符是一些特殊的符號,主要用於數學函數、一些類型的賦值語句和邏輯比較方面,我們就以 Java 為例,來看一下運算符。

  • 賦值運算符

賦值運算符使用操作符 = 來表示,它的意思是把 = 號右邊的值複製給左邊,右邊的值可以是任何常數、變量或者表達式,但左邊的值必須是一個明確的,已經定義的變量。比如 int a = 4。

但是對於對象來說,複製的不是對象的值,而是對象的引用,所以如果說將一個對象複製給另一個對象,實際上是將一個對象的引用賦值給另一個對象

  • 算數運算符

算數運算符就和數學中的數值計算差不多,主要有

算數運算符需要注意的就是優先級問題,當一個表達式中存在多個操作符時,操作符的優先級順序就決定了計算順序,最簡單的規則就是先乘除後加減,() 的優先級最高,沒必要記住所有的優先級順序,不確定的直接用 () 就可以了。

  • 自增、自減運算符

這個就不文字解釋了,解釋不如直接看例子明白

int a = 5;
b = ++a;
c = a++;

  • 比較運算符

比較運算符用於程序中的變量之間,變量和自變量之間以及其他類型的信息之間的比較。

比較運算符的運算結果是 boolean 型。當運算符對應的關係成立時,運算的結果為 true,否則為 false。比較運算符共有 6 個,通常作為判斷的依據用於條件語句中。

  • 邏輯運算符

邏輯運算符主要有三種,與、或、非

下面是邏輯運算符對應的 true/false 符號表

  • 按位運算符

按位運算符用來操作整數基本類型中的每個比特位,也就是二進位位。按位操作符會對兩個參數中對應的位執行布爾代數運算,並最終生成一個結果。

如果進行比較的雙方是數字的話,那麼進行比較就會變為按位運算。

按位與:按位進行與運算(AND),兩個操作數中位都為1,結果才為1,否則結果為0。需要首先把比較雙方轉換成二進位再按每個位進行比較

按位或:按位進行或運算(OR),兩個位只要有一個為1,那麼結果就是1,否則就為0。

按位非:按位進行異或運算(XOR),如果位為0,結果是1,如果位為1,結果是0。

按位異或:按位進行取反運算(NOT),兩個操作數的位中,相同則結果為0,不同則結果為1。

  • 移位運算符

移位運算符用來將操作數向某個方向(向左或者右)移動指定的二進位位數。

  • 三元運算符

三元運算符是類似 if...else... 這種的操作符,語法為:條件表達式?表達式 1:表達式 2。問號前面的位置是判斷的條件,判斷結果為布爾型,為 true 時調用表達式 1,為 false 時調用表達式 2。

Java 執行控制流程

Java 中的控制流程其實和 C 一樣,在 Java 中,流程控制會涉及到包括 if-else、while、do-while、for、return、break 以及選擇語句 switch。下面以此進行分析

條件語句

條件語句可根據不同的條件執行不同的語句。包括 if 條件語句與 switch 多分支語句。

if 條件語句

if 語句可以單獨判斷表達式的結果,表示表達的執行結果,例如

int a = 10;
if(a > 10){
  return true;
}
return false;

if...else 條件語句

if 語句還可以與 else 連用,通常表現為 如果滿足某種條件,就進行某種處理,否則就進行另一種處理

int a = 10;
int b = 11;
if(a >= b){
  System.out.println("a >= b");
}else{
  System.out.println("a < b");
}

if 後的 () 內的表達式必須是 boolean 型的。如果為 true,則執行 if 後的複合語句;如果為 false,則執行 else 後的複合語句。

if...else if 多分支語句

上面中的 if...else 是單分支和兩個分支的判斷,如果有多個判斷條件,就需要使用 if...else if

int x = 40;
if(x > 60) {
  System.out.println("x的值大於60");
} else if (x > 30) {
  System.out.println("x的值大於30但小於60");
} else if (x > 0) {
  System.out.println("x的值大於0但小於30");
} else {
  System.out.println("x的值小於等於0");
}

switch 多分支語句

一種比 **if...else if ** 語句更優雅的方式是使用 switch 多分支語句,它的示例如下

switch (week) {
  case 1:
    System.out.println("Monday");
    break;
  case 2:
    System.out.println("Tuesday");
    break;
  case 3:
    System.out.println("Wednesday");
    break;
  case 4:
    System.out.println("Thursday");
    break;
  case 5:
    System.out.println("Friday");
    break;
  case 6:
    System.out.println("Saturday");
    break;
  case 7:
    System.out.println("Sunday");
    break;
  default:
    System.out.println("No Else");
    break;
}

循環語句

循環語句就是在滿足一定的條件下反覆執行某一表達式的操作,直到滿足循環語句的要求。使用的循環語句主要有 **for、do...while() 、 while **,

while 循環語句

while 循環語句的循環方式為利用一個條件來控制是否要繼續反覆執行這個語句。while 循環語句的格式如下

while(布爾值){
  表達式
}

它的含義是,當 (布爾值) 為 true 的時候,執行下面的表達式,布爾值為 false 的時候,結束循環,布爾值其實也是一個表達式,比如

int a = 10;
while(a > 5){
  a--;
}

do...while 循環

while 與 do...while 循環的唯一區別是 do...while 語句至少執行一次,即使第一次的表達式為 false。而在 while 循環中,如果第一次條件為 false,那麼其中的語句根本不會執行。在實際應用中,while 要比 do...while 應用的更廣。它的一般形式如下

int b = 10;
// do···while循環語句
do {
  System.out.println("b == " + b);
  b--;
} while(b == 1);

for 循環語句

for 循環是我們經常使用的循環方式,這種形式會在第一次疊代前進行初始化。它的形式如下

for(初始化; 布爾表達式; 步進){}

每次疊代前會測試布爾表達式。如果獲得的結果是 false,就會執行 for 語句後面的代碼;每次循環結束,會按照步進的值執行下一次循環。

逗號操作符

這裡不可忽略的一個就是逗號操作符,Java 里唯一用到逗號操作符的就是 for 循環控制語句。在表達式的初始化部分,可以使用一系列的逗號分隔的語句;通過逗號操作符,可以在 for 語句內定義多個變量,但它們必須具有相同的類型

for(int i = 1;j = i + 10;i < 5;i++, j = j * 2){}

for-each 語句

在 Java JDK 1.5 中還引入了一種更加簡潔的、方便對數組和集合進行遍歷的方法,即 for-each 語句,例子如下

int array[] = {7, 8, 9};

for (int arr : array) {
     System.out.println(arr);
}

跳轉語句

Java 語言中,有三種跳轉語句: break、continue 和 return

break 語句

break 語句我們在 switch 中已經見到了,它是用於終止循環的操作,實際上 break 語句在for、while、do···while循環語句中,用於強行退出當前循環,例如

for(int i = 0;i < 10;i++){
        if(i == 5){
    break;
  }
}

continue 語句

continue 也可以放在循環語句中,它與 break 語句具有相反的效果,它的作用是用於執行下一次循環,而不是退出當前循環,還以上面的例子為主

for(int i = 0;i < 10;i++){
  
  System.out.printl(" i = " + i );
        if(i == 5){
    System.out.printl("continue ... ");
    continue;
  }
}

return 語句

return 語句可以從一個方法返回,並把控制權交給調用它的語句。

public void getName() {
    return name;
}

面向對象

下面我們來探討面向對象的思想,面向對象的思想已經逐步取代了過程化的思想 --- 面向過程,Java 是面向對象的高級程式語言,面向對象語言具有如下特徵

  • 面向對象是一種常見的思想,比較符合人們的思考習慣;
  • 面向對象可以將複雜的業務邏輯簡單化,增強代碼復用性;
  • 面向對象具有抽象、封裝、繼承、多態等特性。

面向對象的程式語言主要有:C++、Java、C#等。

所以必須熟悉面向對象的思想才能編寫出 Java 程序。

類也是一種對象

現在我們來認識一個面向對象的新的概念 --- 類,什麼是類,它就相當於是一系列對象的抽象,就比如書籍一樣,類相當於是書的封面,大多數面向對象的語言都使用 class 來定義類,它告訴你它裡面定義的對象都是什麼樣的,我們一般使用下面來定義類

class ClassName {
        // body;
}

代碼段中涉及一個新的概念 // ,這個我們後面會說。上面,你聲明了一個 class 類,現在,你就可以使用 new 來創建這個對象

ClassName classname = new ClassName();

一般,類的命名遵循駝峰原則,它的定義如下

駱駝式命名法(Camel-Case)又稱駝峰式命名法,是電腦程式編寫時的一套命名規則(慣例)。正如它的名稱 CamelCase 所表示的那樣,是指混合使用大小寫字母來構成變量和函數的名字。程式設計師們為了自己的代碼能更容易的在同行之間交流,所以多採取統一的可讀性比較好的命名方式。

對象的創建

在 Java 中,萬事萬物都是對象。這句話相信你一定不陌生,儘管一切都看作是對象,但是你操縱的卻是一個對象的 引用(reference)。在這裡有一個很形象的比喻:你可以把車鑰匙和車看作是一組對象引用和對象的組合。當你想要開車的時候,你首先需要拿出車鑰匙點擊開鎖的選項,停車時,你需要點擊加鎖來鎖車。車鑰匙相當於就是引用,車就是對象,由車鑰匙來驅動車的加鎖和開鎖。並且,即使沒有車的存在,車鑰匙也是一個獨立存在的實體,也就是說,你有一個對象引用,但你不一定需要一個對象與之關聯,也就是

Car carKey;

這裡創建的只是引用,而並非對象,但是如果你想要使用 s 這個引用時,會返回一個異常,告訴你需要一個對象來和這個引用進行關聯。一種安全的做法是,在創建對象引用時同時把一個對象賦給它。

Car carKey = new Car();

在 Java 中,一旦創建了一個引用,就希望它能與一個新的對象進行關聯,通常使用 new 操作符來實現這一目的。new 的意思是,給我一個新對象,如果你不想相親,自己 new 一個對象就好了。祝你下輩子幸福。

屬性和方法

類一個最基本的要素就是有屬性和方法。

屬性也被稱為欄位,它是類的重要組成部分,屬性可以是任意類型的對象,也可以是基本數據類型。例如下

class A{
  int a;
  Apple apple;
}

類中還應該包括方法,方法表示的是 做某些事情的方式。方法其實就是函數,只不過 Java 習慣把函數稱為方法。這種叫法也體現了面向對象的概念。

方法的基本組成包括 方法名稱、參數、返回值和方法體, 下面是它的示例

public int getResult(){
  // ...
  return 1;
}

其中,getResult 就是方法名稱、() 裡面表示方法接收的參數、return 表示方法的返回值,注意:方法的返回值必須和方法的參數類型保持一致。有一種特殊的參數類型 --- void 表示方法無返回值。{} 包含的代碼段被稱為方法體。

構造方法

在 Java 中,有一種特殊的方法被稱為 構造方法,也被稱為構造函數、構造器等。在 Java 中,通過提供這個構造器,來確保每個對象都被初始化。構造方法只能在對象的創建時期調用一次,保證了對象初始化的進行。構造方法比較特殊,它沒有參數類型和返回值,它的名稱要和類名保持一致,並且構造方法可以有多個,下面是一個構造方法的示例

class Apple {
  
  int sum;
  String color;
  
  public Apple(){}
  public Apple(int sum){}
  public Apple(String color){}
  public Apple(int sum,String color){}
  
}

上面定義了一個 Apple 類,你會發現這個 Apple 類沒有參數類型和返回值,並且有多個以 Apple 同名的方法,而且各個 Apple 的參數列表都不一樣,這其實是一種多態的體現,我們後面會說。在定義完成構造方法後,我們就能夠創建 Apple 對象了。

class createApple {

    public static void main(String[] args) {
        Apple apple1 = new Apple();
        Apple apple2 = new Apple(1);
        Apple apple3 = new Apple("red");
        Apple apple4 = new Apple(2,"color");

    }
}

如上面所示,我們定義了四個 Apple 對象,並調用了 Apple 的四種不同的構造方法,其中,不加任何參數的構造方法被稱為默認的構造方法,也就是

Apple apple1 = new Apple();

如果類中沒有定義任何構造方法,那麼 JVM 會為你自動生成一個構造方法,如下

class Apple {
  
  int sum;
  String color;
  
}

class createApple {

    public static void main(String[] args) {
        Apple apple1 = new Apple();

    }
}

上面代碼不會發生編譯錯誤,因為 Apple 對象包含了一個默認的構造方法。

默認的構造方法也被稱為默認構造器或者無參構造器。

這裡需要注意一點的是,即使 JVM 會為你默認添加一個無參的構造器,但是如果你手動定義了任何一個構造方法,JVM 就不再為你提供默認的構造器,你必須手動指定,否則會出現編譯錯誤

顯示的錯誤是,必須提供 Apple 帶有 int 參數的構造函數,而默認的無參構造函數沒有被允許使用。

方法重載

在 Java 中一個很重要的概念是方法的重載,它是類名的不同表現形式。我們上面說到了構造函數,其實構造函數也是重載的一種。另外一種就是方法的重載

public class Apple {

    int sum;
    String color;

    public Apple(){}
    public Apple(int sum){}
    
    public int getApple(int num){
        return 1;
    }
    
    public String getApple(String color){
        return "color";
    }

}

如上面所示,就有兩種重載的方式,一種是 Apple 構造函數的重載,一種是 getApple 方法的重載。

但是這樣就涉及到一個問題,要是有幾個相同的名字,Java 如何知道你調用的是哪個方法呢?這裡記住一點即可,每個重載的方法都有獨一無二的參數列表。其中包括參數的類型、順序、參數數量等,滿足一種一個因素就構成了重載的必要條件。

請記住下面重載的條件

  • 方法名稱必須相同。
  • 參數列表必須不同(個數不同、或類型不同、參數類型排列順序不同等)。
  • 方法的返回類型可以相同也可以不相同。
  • 僅僅返回類型不同不足以成為方法的重載。
  • 重載是發生在編譯時的,因為編譯器可以根據參數的類型來選擇使用哪個方法。

方法的重寫

方法的重寫與重載雖然名字很相似,但卻完全是不同的東西。方法重寫的描述是對子類和父類之間的。而重載指的是同一類中的。例如如下代碼

class Fruit {
 
        public void eat(){
    System.out.printl('eat fruit');
  }
}

class Apple extends Fruit{
  
  @Override
  public void eat(){
    System.out.printl('eat apple');
  }
}

上面這段代碼描述的就是重寫的代碼,你可以看到,子類 Apple 中的方法和父類 Fruit 中的方法同名,所以,我們能夠推斷出重寫的原則

  • 重寫的方法必須要和父類保持一致,包括返回值類型,方法名,參數列表 也都一樣。
  • 重寫的方法可以使用 @Override 註解來標識
  • 子類中重寫方法的訪問權限不能低於父類中方法的訪問權限。

初始化

類的初始化

上面我們創建出來了一個 Car 這個對象,其實在使用 new 關鍵字創建一個對象的時候,其實是調用了這個對象無參數的構造方法進行的初始化,也就是如下這段代碼

class Car{
  public Car(){}
}

這個無參數的構造函數可以隱藏,由 JVM 自動添加。也就是說,構造函數能夠確保類的初始化。

成員初始化

Java 會儘量保證每個變量在使用前都會獲得初始化,初始化涉及兩種初始化。

  • 一種是編譯器默認指定的欄位初始化,基本數據類型的初始化一種是其他對象類型的初始化,String 也是一種對象,對象的初始值都為 null ,其中也包括基本類型的包裝類。
  • 一種是指定數值的初始化,例如
int a = 11

也就是說, 指定 a 的初始化值不是 0 ,而是 11。其他基本類型和對象類型也是一樣的。

構造器初始化

可以利用構造器來對某些方法和某些動作進行初始化,確定初始值,例如

public class Counter{
  int i;
  public Counter(){
    i = 11;
  }
}

利用構造函數,能夠把 i 的值初始化為 11。

初始化順序

首先先來看一下有哪些需要探討的初始化順序

  • 靜態屬性:static 開頭定義的屬性
  • 靜態方法塊: static {} 包起來的代碼塊
  • 普通屬性: 非 static 定義的屬性
  • 普通方法塊: {} 包起來的代碼塊
  • 構造函數: 類名相同的方法
  • 方法: 普通方法
public class LifeCycle {
    // 靜態屬性
    private static String staticField = getStaticField();
    // 靜態方法塊
    static {
        System.out.println(staticField);
        System.out.println("靜態方法塊初始化");
    }
    // 普通屬性
    private String field = getField();
    // 普通方法塊
    {
        System.out.println(field);
    }
    // 構造函數
    public LifeCycle() {
        System.out.println("構造函數初始化");
    }

    public static String getStaticField() {
        String statiFiled = "Static Field Initial";
        return statiFiled;
    }

    public static String getField() {
        String filed = "Field Initial";
        return filed;
    }   
    // 主函數
    public static void main(String[] argc) {
        new LifeCycle();
    }
}

這段代碼的執行結果就反應了它的初始化順序

靜態屬性初始化靜態方法塊初始化普通屬性初始化普通方法塊初始化構造函數初始化

數組初始化

數組是相同類型的、用一個標識符名稱封裝到一起的一個對象序列或基本類型數據序列。數組是通過方括號下標操作符 [] 來定義使用。

一般數組是這麼定義的

int[] a1;

//或者

int a1[];

兩種格式的含義是一樣的。

  • 直接給每個元素賦值 : int array[4] = {1,2,3,4};
  • 給一部分賦值,後面的都為 0 : int array[4] = {1,2};
  • 由賦值參數個數決定數組的個數 : int array[] = {1,2};

可變參數列表

Java 中一種數組冷門的用法就是可變參數 ,可變參數的定義如下

public int add(int... numbers){
  int sum = 0;
  for(int num : numbers){
    sum += num;
  }
  return sum;
}

然後,你可以使用下面這幾種方式進行可變參數的調用

add();  // 不傳參數
add(1);  // 傳遞一個參數
add(2,1);  // 傳遞多個參數
add(new Integer[] {1, 3, 2});  // 傳遞數組

對象的銷毀

雖然 Java 語言是基於 C++ 的,但是它和 C/C++ 一個重要的特徵就是不需要手動管理對象的銷毀工作。在著名的一書 《深入理解 Java 虛擬機》中提到一個觀點

在 Java 中,我們不再需要手動管理對象的銷毀,它是由 Java 虛擬機進行管理和銷毀的。雖然我們不需要手動管理對象,但是你需要知道 對象作用域 這個概念。

對象作用域

J多數語言都有作用域(scope) 這個概念。作用域決定了其內部定義的變量名的可見性和生命周期。在 C、C++ 和 Java 中,作用域通常由 {} 的位置來決定,例如

{
  int a = 11;
  {
    int b = 12;
  }
}

a 變量會在兩個 {} 作用域內有效,而 b 變量的值只能在它自己的 {} 內有效。

雖然存在作用域,但是不允許這樣寫

{
  int x = 11;
  {
    int x = 12;
  }
}

這種寫法在 C/C++ 中是可以的,但是在 Java 中不允許這樣寫,因為 Java 設計者認為這樣寫會導致程序混亂。

this 和 super

this 和 super 都是 Java 中的關鍵字

this 表示的當前對象,this 可以調用方法、調用屬性和指向對象本身。this 在 Java 中的使用一般有三種:指向當前對象

public class Apple {

    int i = 0;

    Apple eatApple(){
        i++;
        return this;
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        apple.eatApple().eatApple();
    }
}

這段代碼比較精妙,精妙在哪呢,我一個 eatApple() 方法竟然可以調用多次,你在後面還可以繼續調用,這就很神奇了,為啥呢?其實就是 this 在作祟了,我在 eatApple 方法中加了一個 return this 的返回值,也就是說哪個對象調用 eatApple 方法都能返回對象的自身。

this 還可以修飾屬性,最常見的就是在構造方法中使用 this ,如下所示

public class Apple {

    private int num;
    
    public Apple(int num){
        this.num = num;
    }

    public static void main(String[] args) {
        new Apple(10);
    }
}

main 方法中傳遞了一個 int 值為 10 的參數,它表示的就是蘋果的數量,並把這個數量賦給了 num 全局變量。所以 num 的值現在就是 10。

this 還可以和構造函數一起使用,充當一個全局關鍵字的效果

public class Apple {

    private int num;
    private String color;

    public Apple(int num){
        this(num,"紅色");
    }
    
    public Apple(String color){
        this(1,color);
    }

    public Apple(int num, String color) {
        this.num = num;
        this.color = color;
    }
    
}

你會發現上面這段代碼使用的不是 this, 而是 this(參數)。它相當於調用了其他構造方法,然後傳遞參數進去。這裡注意一點:this() 必須放在構造方法的第一行,否則編譯不通過

如果你把 this 理解為指向自身的一個引用,那麼 super 就是指向父類的一個引用。super 關鍵字和 this 一樣,你可以使用 super.對象 來引用父類的成員,如下

public class Fruit {

    int num;
    String color;

    public void eat(){
        System.out.println("eat Fruit");
    }
}

public class Apple extends Fruit{

    @Override
    public void eat() {
        super.num = 10;
        System.out.println("eat " + num + " Apple");
    }

}

你也可以使用 super(參數) 來調用父類的構造函數,這裡不再舉例子了。

下面為你匯總了 this 關鍵字和 super 關鍵字的比較。

訪問控制權限

訪問控制權限又稱為封裝,它是面向對象三大特性中的一種,我之前在學習過程中經常會忽略封裝,心想這不就是一個訪問修飾符麼,怎麼就是三大特性的必要條件了?後來我才知道,如果你信任的下屬對你隱瞞 bug,你是根本不知道的

訪問控制權限其實最核心就是一點:只對需要的類可見。

Java中成員的訪問權限共有四種,分別是 public、protected、default、private,它們的可見性如下

繼承

繼承是所有 OOP(Object Oriented Programming) 語言和 Java 語言都不可或缺的一部分。只要我們創建了一個類,就隱式的繼承自 Object 父類,只不過沒有指定。如果你顯示指定了父類,那麼你繼承於父類,而你的父類繼承於 Object 類。

繼承的關鍵字是 extends ,如上圖所示,如果使用了 extends 顯示指定了繼承,那麼我們可以說 Father 是父類,而 Son 是子類,用代碼表示如下

class Father{}

class Son extends Father{}

繼承雙方擁有某種共性的特徵

class Father{
  
  public void feature(){
    System.out.println("父親的特徵");
  }
}

class Son extends Father {
}

如果 Son 沒有實現自己的方法的話,那麼默認就是用的是父類的 feature 方法。如果子類實現了自己的 feature 方法,那麼就相當於是重寫了父類的 feature 方法,這也是我們上面提到的重寫了。

多態

多態指的是同一個行為具有多個不同表現形式。是指一個類實例(對象)的相同方法在不同情形下具有不同表現形式。封裝和繼承是多態的基礎,也就是說,多態只是一種表現形式而已。

如何實現多態?多態的實現具有三種充要條件

  • 繼承
  • 重寫父類方法
  • 父類引用指向子類對象

比如下面這段代碼

public class Fruit {

    int num;

    public void eat(){
        System.out.println("eat Fruit");
    }
}

public class Apple extends Fruit{

    @Override
    public void eat() {
        super.num = 10;
        System.out.println("eat " + num + " Apple");
    }

    public static void main(String[] args) {
        Fruit fruit = new Apple();
        fruit.eat();
    }
}

你可以發現 main 方法中有一個很神奇的地方,Fruit fruit = new Apple(),Fruit 類型的對象竟然指向了 Apple 對象的引用,這其實就是多態 -> 父類引用指向子類對象,因為 Apple 繼承於 Fruit,並且重寫了 eat 方法,所以能夠表現出來多種狀態的形式。

組合

組合其實不難理解,就是將對象引用置於新類中即可。組合也是一種提高類的復用性的一種方式。如果你想讓類具有更多的擴展功能,你需要記住一句話多用組合,少用繼承

public class SoccerPlayer {
    
    private String name;
    private Soccer soccer;
    
}

public class Soccer {
    
    private String soccerName;    
}

代碼中 SoccerPlayer 引用了 Soccer 類,通過引用 Soccer 類,來達到調用 soccer 中的屬性和方法。

組合和繼承是有區別的,它們的主要區別如下。

關於繼承和組合孰優孰劣的爭論沒有結果,只要發揮各自的長處和優點即可,一般情況下,組合和繼承也是一對可以連用的好兄弟。

代理

除了繼承和組合外,另外一種值得探討的關係模型稱為 代理。代理的大致描述是,A 想要調用 B 類的方法,A 不直接調用,A 會在自己的類中創建一個 B 對象的代理,再由代理調用 B 的方法。例如如下代碼

public class Destination {

    public void todo(){
        System.out.println("control...");
    }
}

public class Device {

    private String name;
    private Destination destination;
    private DeviceController deviceController;

    public void control(Destination destination){
        destination.todo();
    }

}

public class DeviceController {

    private Device name;
    private Destination destination;

    public void control(Destination destination){
        destination.todo();
    }
}

向上轉型

向上轉型代表了父類與子類之間的關係,其實父類和子類之間不僅僅有向上轉型,還有向下轉型,它們的轉型後的範圍不一樣

  • 向上轉型:通過子類對象(小範圍)轉化為父類對象(大範圍),這種轉換是自動完成的,不用強制。
  • 向下轉型 : 通過父類對象(大範圍)實例化子類對象(小範圍),這種轉換不是自動完成的,需要強制指定。

static

static 是 Java 中的關鍵字,它的意思是 靜態的,static 可以用來修飾成員變量和方法,static 用在沒有創建對象的情況下調用 方法/變量。

  • 用 static 聲明的成員變量為靜態成員變量,也成為類變量。類變量的生命周期和類相同,在整個應用程式執行期間都有效。
static String name = "cxuan";

  • 使用 static 修飾的方法稱為靜態方法,靜態方法能夠直接使用類名.方法名 進行調用。由於靜態方法不依賴於任何對象就可以直接訪問,因此對於靜態方法來說,是沒有 this 關鍵字的,實例變量都會有 this 關鍵字。在靜態方法中不能訪問類的非靜態成員變量和非靜態方法,
static void printMessage(){
  System.out.println("cxuan is writing the article");
}

static 除了修飾屬性和方法外,還有靜態代碼塊 的功能,可用於類的初始化操作。進而提升程序的性能。

public class StaicBlock {
    static{
        System.out.println("I'm A static code block");
    }
}

由於靜態代碼塊隨著類的加載而執行,因此,很多時候會將只需要進行一次的初始化操作放在 static 代碼塊中進行。

final

final 的意思是最後的、最終的,它可以修飾類、屬性和方法。

  • final 修飾類時,表明這個類不能被繼承。final 類中的成員變量可以根據需要設為 final,但是要注意 final 類中的所有成員方法都會被隱式地指定為 final 方法。
  • final 修飾方法時,表明這個方法不能被任何子類重寫,因此,如果只有在想明確禁止該方法在子類中被覆蓋的情況下才將方法設置為 final。
  • final 修飾變量分為兩種情況,一種是修飾基本數據類型,表示數據類型的值不能被修改;一種是修飾引用類型,表示對其初始化之後便不能再讓其指向另一個對象。

接口和抽象類

接口

接口相當於就是對外的一種約定和標準,這裡拿作業系統舉例子,為什麼會有作業系統?就會為了屏蔽軟體的複雜性和硬體的簡單性之間的差異,為軟體提供統一的標準。

在 Java 語言中,接口是由 interface 關鍵字來表示的,比如我們可以向下面這樣定義一個接口

public interface CxuanGoodJob {}

比如我們定義了一個 CxuanGoodJob 的接口,然後你就可以在其內部定義 cxuan 做的好的那些事情,比如 cxuan 寫的文章不錯。

public interface CxuanGoodJob {

    void writeWell();
}

這裡隱含了一些接口的特徵:

  • interface 接口是一個完全抽象的類,他不會提供任何方法的實現,只是會進行方法的定義。
  • 接口中只能使用兩種訪問修飾符,一種是 public,它對整個項目可見;一種是 default 預設值,它只具有包訪問權限。
  • 接口只提供方法的定義,接口沒有實現,但是接口可以被其他類實現。也就是說,實現接口的類需要提供方法的實現,實現接口使用 implements 關鍵字來表示,一個接口可以有多個實現。
class CXuanWriteWell implements CxuanGoodJob{

    @Override
    public void writeWell() {
        System.out.println("Cxuan write Java is vary well");
    }
}

  • 接口不能被實例化,所以接口中不能有任何構造方法,你定義構造方法編譯會出錯。
  • 接口的實現比如實現接口的全部方法,否則必須定義為抽象類,這就是我們下面要說的內容

抽象類

抽象類是一種抽象能力弱於接口的類,在 Java 中,抽象類使用 abstract 關鍵字來表示。如果把接口形容為狗這個物種,那麼抽象類可以說是毛髮是白色、小體的品種,而實現類可以是具體的類,比如說是博美、泰迪等。你可以像下面這樣定義抽象類

public interface Dog {

    void FurColor();

}

abstract class WhiteDog implements Dog{

    public void FurColor(){
        System.out.println("Fur is white");
    }

    abstract void SmallBody();
}

在抽象類中,具有如下特徵

  • 如果一個類中有抽象方法,那麼這個類一定是抽象類,也就是說,使用關鍵字 abstract 修飾的方法一定是抽象方法,具有抽象方法的類一定是抽象類。實現類方法中只有方法具體的實現。
  • 抽象類中不一定只有抽象方法,抽象類中也可以有具體的方法,你可以自己去選擇是否實現這些方法。
  • 抽象類中的約束不像接口那麼嚴格,你可以在抽象類中定義 構造方法、抽象方法、普通屬性、方法、靜態屬性和靜態方法
  • 抽象類和接口一樣不能被實例化,實例化只能實例化具體的類

異常

異常是程序經常會出現的,發現錯誤的最佳時機是在編譯階段,也就是你試圖在運行程序之前。但是,在編譯期間並不能找到所有的錯誤,有一些 NullPointerException 和 ClassNotFoundException 異常在編譯期找不到,這些異常是 RuntimeException 運行時異常,這些異常往往在運行時才能被發現。

我們寫 Java 程序經常會出現兩種問題,一種是 java.lang.Exception ,一種是 java.lang.Error,都用來表示出現了異常情況,下面就針對這兩種概念進行理解。

認識 Exception

Exception 位於 java.lang 包下,它是一種頂級接口,繼承於 Throwable 類,Exception 類及其子類都是 Throwable 的組成條件,是程序出現的合理情況。

在認識 Exception 之前,有必要先了解一下什麼是 Throwable。

什麼是 Throwable

Throwable 類是 Java 語言中所有錯誤(errors)和異常(exceptions)的父類。只有繼承於 Throwable 的類或者其子類才能夠被拋出,還有一種方式是帶有 Java 中的 @throw 註解的類也可以拋出。

在Java規範中,對非受查異常和受查異常的定義是這樣的:

The unchecked exception classes are the run-time exception classes and the error classes.

The checked exception classes are all exception classes other than the unchecked exception classes. That is, the checked exception classes are Throwable and all its subclasses other than RuntimeException and its subclasses and Errorand its subclasses.

也就是說,除了 RuntimeException 和其子類,以及error和其子類,其它的所有異常都是 checkedException。

那麼,按照這種邏輯關係,我們可以對 Throwable 及其子類進行歸類分析

可以看到,Throwable 位於異常和錯誤的最頂層,我們查看 Throwable 類中發現它的方法和屬性有很多,我們只討論其中幾個比較常用的

// 返回拋出異常的詳細信息
public string getMessage();
public string getLocalizedMessage();

//返回異常發生時的簡要描述
public public String toString();
  
// 列印異常信息到標準輸出流上
public void printStackTrace();
public void printStackTrace(PrintStream s);
public void printStackTrace(PrintWriter s)

// 記錄棧幀的的當前狀態
public synchronized Throwable fillInStackTrace();

此外,因為 Throwable 的父類也是 Object,所以常用的方法還有繼承其父類的getClass() 和 getName() 方法。

常見的 Exception

下面我們回到 Exception 的探討上來,現在你知道了 Exception 的父類是 Throwable,並且 Exception 有兩種異常,一種是 RuntimeException ;一種是 CheckedException,這兩種異常都應該去捕獲。

下面列出了一些 Java 中常見的異常及其分類,這塊面試官也可能讓你舉出幾個常見的異常情況並將其分類

RuntimeException

UncheckedException

與 Exception 有關的 Java 關鍵字

那麼 Java 中是如何處理這些異常的呢?在 Java 中有這幾個關鍵字 throws、throw、try、finally、catch 下面我們分別來探討一下

throws 和 throw

在 Java 中,異常也就是一個對象,它能夠被程式設計師自定義拋出或者應用程式拋出,必須藉助於 throws 和 throw 語句來定義拋出異常。

throws 和 throw 通常是成對出現的,例如

static void cacheException() throws Exception{

  throw new Exception();

}

throw 語句用在方法體內,表示拋出異常,由方法體內的語句處理。throws 語句用在方法聲明後面,表示再拋出異常,由該方法的調用者來處理。

throws 主要是聲明這個方法會拋出這種類型的異常,使它的調用者知道要捕獲這個異常。throw 是具體向外拋異常的動作,所以它是拋出一個異常實例。

try 、finally 、catch

這三個關鍵字主要有下面幾種組合方式 try...catch 、try...finally、try...catch...finally

try...catch 表示對某一段代碼可能拋出異常進行的捕獲,如下

static void cacheException() throws Exception{

  try {
    System.out.println("1");
  }catch (Exception e){
    e.printStackTrace();
  }

}

try...finally 表示對一段代碼不管執行情況如何,都會走 finally 中的代碼

static void cacheException() throws Exception{
  for (int i = 0; i < 5; i++) {
    System.out.println("enter: i=" + i);
    try {
      System.out.println("execute: i=" + i);
      continue;
    } finally {
      System.out.println("leave: i=" + i);
    }
  }
}

try...catch...finally 也是一樣的,表示對異常捕獲後,再走 finally 中的代碼邏輯。

什麼是 Error

Error 是程序無法處理的錯誤,表示運行應用程式中較嚴重問題。大多數錯誤與代碼編寫者執行的操作無關,而表示代碼運行時 JVM(Java 虛擬機)出現的問題。這些錯誤是不可檢查的,因為它們在應用程式的控制和處理能力之 外,而且絕大多數是程序運行時不允許出現的狀況,比如 OutOfMemoryError 和 StackOverflowError異常的出現會有幾種情況,這裡需要先介紹一下 Java 內存模型 JDK1.7。

其中包括兩部分,由所有線程共享的數據區和線程隔離的數據區組成,在上面的 Java 內存模型中,只有程序計數器是不會發生 OutOfMemoryError 情況的區域,程序計數器控制著計算機指令的分支、循環、跳轉、異常處理和線程恢復,並且程序計數器是每個線程私有的。

什麼是線程私有:表示的就是各條線程之間互不影響,獨立存儲的內存區域。

如果應用程式執行的是 Java 方法,那麼這個計數器記錄的就是虛擬機字節碼指令的地址;如果正在執行的是 Native 方法,這個計數器值則為空(Undefined)。

除了程序計數器外,其他區域:方法區(Method Area)、虛擬機棧(VM Stack)、本地方法棧(Native Method Stack) 和 堆(Heap) 都是可能發生 OutOfMemoryError 的區域。

  • 虛擬機棧:如果線程請求的棧深度大於虛擬機棧所允許的深度,將會出現 StackOverflowError 異常;如果虛擬機動態擴展無法申請到足夠的內存,將出現 OutOfMemoryError。
  • 本地方法棧和虛擬機棧一樣
  • 堆:Java 堆可以處於物理上不連續,邏輯上連續,就像我們的磁碟空間一樣,如果堆中沒有內存完成實例分配,並且堆無法擴展時,將會拋出 OutOfMemoryError。
  • 方法區:方法區無法滿足內存分配需求時,將拋出 OutOfMemoryError 異常。

在 Java 中,你可以把異常理解為是一種能夠提高你程序健壯性的機制,它能夠讓你在編寫代碼中注意這些問題,也可以說,如果你寫代碼不會注意這些異常情況,你是無法成為一位硬核程式設計師的。

內部類

距今為止,我們了解的都是普通類的定義,那就是直接在 IDEA 中直接新建一個 class 。

新建完成後,你就會擁有一個 class 文件的定義,這種操作太簡單了,時間長了就會枯燥,我們年輕人多需要更新潮和騷氣的寫法,好吧,既然你提到了那就使用 內部類吧,這是一種有用而且騷氣的定義類的方式,內部類的定義非常簡單:可以將一個類的定義放在另一個類的內部,這就是內部類

內部類是一種非常有用的特性,定義在類內部的類,持有外部類的引用,但卻對其他外部類不可見,看起來就像是一種隱藏代碼的機制,就和 弗蘭奇將軍 似的,弗蘭奇可以和弗蘭奇將軍進行通訊,但是外面的敵人卻無法直接攻擊到弗蘭奇本體。

下面我們就來聊一聊創建內部類的方式。

創建內部類

定義內部類非常簡單,就是直接將一個類定義在外圍類的裡面,如下代碼所示

public class OuterClass {
    private String name ;
    private int age;
    
    class InnerClass{
        public InnerClass(){
            name = "cxuan";
            age = 25;
        }
    }
}

在這段代碼中,InnerClass 就是 OuterClass 的一個內部類。也就是說,每個內部類都能獨立地繼承一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。這也是隱藏了內部實現細節。內部類擁有外部類的訪問權

內部類不僅僅能夠定義在類的內部,還可以定義在方法和作用域內部,這種被稱為局部內部類,除此之外,還有匿名內部類、內部類可以實現 Java 中的 多重繼承。下面是定義內部類的方式

  • 一個在方法中定義的類(局部內部類)
  • 一個定義在作用域內的類,這個作用域在方法的內部(成員內部類)
  • 一個實現了接口的匿名類(匿名內部類)
  • 一個匿名類,它擴展了非默認構造器的類
  • 一個匿名類,執行欄位初始化操作
  • 一個匿名類,它通過實例初始化實現構造

由於每個類都會產生一個 .class 文件,其中包含了如何創建該類型的對象的全部信息,那麼,如何表示內部類的信息呢?可以使用 $ 來表示,比如 OuterClass$InnerClass.class

集合

集合在我們的日常開發中所使用的次數簡直太多了,你已經把它們都用的熟透了,但是作為一名合格的程式設計師,你不僅要了解它的基本用法,你還要了解它的源碼;存在即合理,你還要了解它是如何設計和實現的,你還要了解它的衍生過程。

這篇博客就來詳細介紹一下 Collection 這個龐大集合框架的家族體系和成員,讓你了解它的設計與實現。

是時候祭出這張神圖了


泛形

在 Jdk1.5 中,提出了一種新的概念,那就是泛型,那麼什麼是泛型呢?

泛型其實就是一種參數化的集合,它限制了你添加進集合的類型。泛型的本質就是一種參數化類型。多態也可以看作是泛型的機制。一個類繼承了父類,那麼就能通過它的父類找到對應的子類,但是不能通過其他類來找到具體要找的這個類。泛型的設計之處就是希望對象或方法具有最廣泛的表達能力。

下面來看一個例子說明沒有泛型的用法

List arrayList = new ArrayList();
arrayList.add("cxuan");
arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){
    String item = (String)arrayList.get(i);
                System.out.println("test === ", item);
}

這段程序不能正常運行,原因是 Integer 類型不能直接強制轉換為 String 類型

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

如果我們用泛型進行改寫後,示例代碼如下

List<String> arrayList = new ArrayList<String>();

arrayList.add(100);

這段代碼在編譯期間就會報錯,編譯器會在編譯階段就能夠幫我們發現類似這樣的問題。

泛型的使用

泛型的使用有多種方式,下面我們就來一起探討一下。

用泛型表示類

泛型可以加到類上面,來表示這個類的類型

List<Object> list = new ArrayList();
for (Object obj: list){}

用泛型表示接口

泛型接口與泛型類的定義及使用基本相同。

Object[] list = new Object[10];
for (Object obj: list){}

一般泛型接口常用於 生成器(generator) 中,生成器相當於對象工廠,是一種專門用來創建對象的類。

泛型方法

可以使用泛型來表示方法

Iterator<T> iterator();

泛型通配符

List 是泛型類,為了 表示各種泛型 List 的父類,可以使用類型通配符,類型通配符使用問號(?)表示,它的元素類型可以匹配任何類型。例如

public static void main(String[] args) {
    List<String> name = new ArrayList<String>();
    List<Integer> age = new ArrayList<Integer>();
    List<Number> number = new ArrayList<Number>();
    name.add("cxuan");
    age.add(18);
    number.add(314);
    generic(name);
    generic(age);
    generic(number);   
}

public static void generic(List<?> data) {
    System.out.println("Test cxuan :" + data.get(0));
}

下界通配符 : <? extends ClassType> 該通配符為 ClassType 的所有子類型。它表示的是任何類型都是 ClassType 類型的子類。

上界通配符: <? super ClassType> 該通配符為 ClassType 的所有超類型。它表示的是任何類型的父類都是 ClassType。

反射

反射是 Java 中一個非常重要同時也是一個高級特性,基本上 Spring 等一系列框架都是基於反射的思想寫成的。我們首先來認識一下什麼反射。

Java 反射機制是在程序的運行過程中,對於任何一個類,都能夠知道它的所有屬性和方法;對於任意一個對象,都能夠知道調用它的任意屬性和方法,這種動態獲取信息以及動態調用對象方法的功能稱為java語言的反射機制。(來源於百度百科)

Java 反射機制主要提供了以下這幾個功能

  • 在運行時判斷任意一個對象所屬的類
  • 在運行時構造任意一個類的對象
  • 在運行時判斷任意一個類所有的成員變量和方法
  • 在運行時調用任意一個對象的方法

這麼一看,反射就像是一個掌控全局的角色,不管你程序怎麼運行,我都能夠知道你這個類有哪些屬性和方法,你這個對象是由誰調用的,嗯,很屌。

在 Java 中,使用 Java.lang.reflect包實現了反射機制。Java.lang.reflect 所設計的類如下

下面是一個簡單的反射類

for(Iterator it = coll.iterator(); it.hasNext(); ){
    System.out.println(it.next());
}

有一些是比較常用的,有一些是我至今都沒見過怎麼用的,下面進行一個歸類。

反射中,還有一個非常重要的類就是 ClassLoader 類,類裝載器是用來把類(class) 裝載進 JVM 的。ClassLoader 使用的是雙親委託模型來搜索加載類的,這個模型也就是雙親委派模型。ClassLoader 的類繼承圖如下

枚舉

枚舉可能是我們使用次數比較少的特性,在 Java 中,枚舉使用 enum 關鍵字來表示,枚舉其實是一項非常有用的特性,你可以把它理解為具有特定性質的類。enum 不僅僅 Java 有,C 和 C++ 也有枚舉的概念。下面是一個枚舉的例子。

public enum Family {

    FATHER,
    MOTHER,
    SON,
    Daughter;

}

上面我們創建了一個 Family的枚舉類,它具有 4 個值,由於枚舉類型都是常量,所以都用大寫字母來表示。那麼 enum 創建出來了,該如何引用呢?

public class EnumUse {

    public static void main(String[] args) {
        Family s = Family.FATHER;
    }
}

枚舉特性

enum 枚舉這個類比較有意思,當你創建完 enum 後,編譯器會自動為你的 enum 添加 toString() 方法,能夠讓你方便的顯示 enum 實例的具體名字是什麼。除了 toString() 方法外,編譯器還會添加 ordinal() 方法,這個方法用來表示 enum 常量的聲明順序,以及 values() 方法顯示順序的值。

public static void main(String[] args) {

  for(Family family : Family.values()){
    System.out.println(family + ", ordinal" + family.ordinal());
  }
}

enum 可以進行靜態導入包,靜態導入包可以做到不用輸入 枚舉類名.常量,可以直接使用常量,神奇嗎? 使用 ennum 和 static關鍵字可以做到靜態導入包

上面代碼導入的是 Family 中所有的常量,也可以單獨指定常量。

枚舉和普通類一樣

枚舉就和普通類一樣,除了枚舉中能夠方便快捷的定義常量,我們日常開發使用的 public static final xxx 其實都可以用枚舉來定義。在枚舉中也能夠定義屬性和方法,千萬不要把它看作是異類,它和萬千的類一樣。

public enum OrdinalEnum {

    WEST("live in west"),
    EAST("live in east"),
    SOUTH("live in south"),
    NORTH("live in north");

    String description;

    OrdinalEnum(String description){
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public static void main(String[] args) {
        for(OrdinalEnum ordinalEnum : OrdinalEnum.values()){
            System.out.println(ordinalEnum.getDescription());
        }
    }
}

一般 switch 可以和 enum 一起連用,來構造一個小型的狀態轉換機。

enum Signal {
  GREEN, YELLOW, RED
}

public class TrafficLight {
    Signal color = Signal.RED;

    public void change() {
        switch (color) {
        case RED:
            color = Signal.GREEN;
            break;
        case YELLOW:
            color = Signal.RED;
            break;
        case GREEN:
            color = Signal.YELLOW;
            break;
        }
    }
}

是不是代碼頓時覺得優雅整潔了些許呢?

枚舉神秘之處

在 Java 中,萬事萬物都是對象,enum 雖然是個關鍵字,但是它卻隱式的繼承於 Enum 類。我們來看一下 Enum 類,此類位於 java.lang 包下,可以自動引用。

此類的屬性和方法都比較少。你會發現這個類中沒有我們的 values 方法。前面剛說到,values() 方法是你使用枚舉時被編譯器添加進來的 static 方法。可以使用反射來驗證一下

除此之外,enum 還和 Class 類有交集,在 Class 類中有三個關於 Enum 的方法

前面兩個方法用於獲取 enum 常量,isEnum 用於判斷是否是枚舉類型的。

枚舉類

除了 Enum 外,還需要知道兩個關於枚舉的工具類,一個是 EnumSet ,一個是 EnumMap

EnumSet 和 EnumMap

EnumSet 是 JDK1.5 引入的,EnumSet 的設計充分考慮到了速度因素,使用 EnumSet 可以作為 Enum 的替代者,因為它的效率比較高。

EnumMap 是一種特殊的 Map,它要求其中的 key 鍵值是來自一個 enum。因為 EnumMap 速度也很快,我們可以使用 EnumMap 作為 key 的快速查找。

總的來說,枚舉的使用不是很複雜,它也是 Java 中很小的一塊功能,但有時卻能夠因為這一個小技巧,能夠讓你的代碼變得優雅和整潔。

I/O

創建一個良好的 I/O 程序是非常複雜的。JDK 開發人員編寫了大量的類只為了能夠創建一個良好的工具包,想必編寫 I/O 工具包很費勁吧。

IO 類設計出來,肯定是為了解決 IO 相關操作的,最常見的 I/O 讀寫就是網絡、磁碟等。在 Java 中,對文件的操作是一個典型的 I/O 操作。下面我們就對 I/O 進行一個分類。

公號回復 IO獲取思維導圖

I/O 還可以根據操作對象來進行區分:主要分為

註解

Java 註解(Annotation) 又稱為元數據 ,它為我們在代碼中添加信息提供了一種形式化的方法。它是 JDK1.5 引入的,Java 定義了一套註解,共有 7 個,3 個在 java.lang 中,剩下 4 個在 java.lang.annotation 中。

作用在代碼中的註解有三個,它們分別是

  • @Override: 重寫標記,一般用在子類繼承父類後,標註在重寫過後的子類方法上。如果發現其父類,或者是引用的接口中並沒有該方法時,會報編譯錯誤。
  • @Deprecated :用此註解注釋的代碼已經過時,不再推薦使用
  • @SuppressWarnings: 這個註解起到忽略編譯器的警告作用

元註解有四個,元註解就是用來標誌註解的註解。它們分別是

  • @Retention: 標識如何存儲,是只在代碼中,還是編入class文件中,或者是在運行時可以通過反射訪問。

RetentionPolicy.SOURCE:註解只保留在源文件,當 Java 文件編譯成class文件的時候,註解被遺棄;

RetentionPolicy.CLASS:註解被保留到 class 文件,但 jvm 加載 class 文件時候被遺棄,這是默認的生命周期;

RetentionPolicy.RUNTIME:註解不僅被保存到 class 文件中,jvm 加載 class 文件之後,仍然存在;

  • @Documented: 標記這些註解是否包含在 JavaDoc 中。
  • @Target: 標記這個註解說明了 Annotation 所修飾的對象範圍,Annotation 可被用於 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。取值如下
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {...}

  • @Inherited : 標記這個註解是繼承於哪個註解類的。

從 JDK1.7 開始,又添加了三個額外的註解,它們分別是

  • @SafeVarargs :在聲明可變參數的構造函數或方法時,Java 編譯器會報 unchecked 警告。使用 @SafeVarargs 可以忽略這些警告
  • @FunctionalInterface: 表明這個方法是一個函數式接口
  • @Repeatable: 標識某註解可以在同一個聲明上使用多次。

注意:註解是不支持繼承的。

關於思維導圖

我把一些常用的 Java 工具包的思維導圖做了匯總,方便讀者查閱。

Java.IO

Java.lang

Java.math

Java.net

思維導圖持續更新中~~~ 歡迎關注公眾號 程式設計師cxuan 領取超全思維導圖。

關鍵字: