硬肝4.4w字爲你寫成Java開發手冊

先來看一下本篇文章的思惟導圖吧,我會圍繞下面這些內容進行講解。內容很乾,小夥伴們看完還但願不吝轉發。(高清思惟導圖版本關注做者公衆號 Java建設者 回覆 Java666 獲取,其餘思惟導圖獲取方式在文末)。
html

下面開始咱們的文章。java

Java 概述

什麼是 Java?

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

計算平臺是在電腦中運行應用程序(軟件)的環境,包括硬件環境軟件環境。通常系統平臺包括一臺電腦的硬件體系結構、操做系統、運行時庫。程序員

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(){...}
}

而面向對象能夠把學生進行抽象,因此這個例子就會變爲windows

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

能夠不用嚴格按照順序來執行每一個動做。這是特色一。api

  • 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。咱們知道,計算機應用程序和硬件之間會屏蔽不少細節,它們之間依靠操做系統完成調度和協調,大體的體系結構以下

YKwycV.png

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

YKws10.png

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)

YKwgnU.png

咱們能夠認真研究一下這張圖,它幾乎包括了 Java 中全部的概念,我使用的是 jdk1.8,能夠點進去 Description of Java Conceptual Diagram, 能夠發現這裏麪包括了全部關於 Java 的描述

YKw2BF.png

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 位都指的是在內存中的佔用。

YKwR74.png

基礎語法

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

運算符

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

  • 賦值運算符

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

可是對於對象來講,複製的不是對象的值,而是對象的引用,因此若是說將一個對象複製給另外一個對象,其實是將一個對象的引用賦值給另外一個對象

  • 算數運算符

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

YKw4hR.png

算數運算符須要注意的就是優先級問題,當一個表達式中存在多個操做符時,操做符的優先級順序就決定了計算順序,最簡單的規則就是先乘除後加減,() 的優先級最高,不必記住全部的優先級順序,不肯定的直接用 () 就能夠了。

  • 自增、自減運算符

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

int a = 5;
b = ++a;
c = a++;
  • 比較運算符

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

比較運算符的運算結果是 boolean 型。當運算符對應的關係成立時,運算的結果爲 true,不然爲 false。比較運算符共有 6 個,一般做爲判斷的依據用於條件語句中。

YKwfAJ.png

  • 邏輯運算符

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

YKwhN9.png

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

YKwI91.png

  • 按位運算符

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

YKwo1x.png

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

按位與:按位進行與運算(AND),兩個操做數中位都爲1,結果才爲1,不然結果爲0。須要首先把比較雙方轉換成二進制再按每一個位進行比較

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

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

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

  • 移位運算符

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

YKwTc6.png

  • 三元運算符

三元運算符是相似 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 就再也不爲你提供默認的構造器,你必須手動指定,不然會出現編譯錯誤

YKwbnO.png

顯示的錯誤是,必須提供 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 會盡可能保證每一個變量在使用前都會得到初始化,初始化涉及兩種初始化。

  • 一種是編譯器默認指定的字段初始化,基本數據類型的初始化

    YKw7jK.png

    一種是其餘對象類型的初始化,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 虛擬機》中提到一個觀點

YKwqBD.png

在 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() 必須放在構造方法的第一行,不然編譯不經過

YKwLHe.png

若是你把 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 關鍵字的比較。

YKwXAH.png

訪問控制權限

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

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

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

YKwjNd.png

繼承

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

YKw6XT.png

繼承的關鍵字是 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 中的屬性和方法。

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

YKwv4A.png

關於繼承和組合孰優孰劣的爭論沒有結果,只要發揮各自的長處和優勢便可,通常狀況下,組合和繼承也是一對能夠連用的好兄弟。

代理

除了繼承和組合外,另一種值得探討的關係模型稱爲 代理。代理的大體描述是,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 修飾的方法必定是抽象方法,具備抽象方法的類必定是抽象類。實現類方法中只有方法具體的實現。

  • 抽象類中不必定只有抽象方法,抽象類中也能夠有具體的方法,你能夠本身去選擇是否實現這些方法。

  • 抽象類中的約束不像接口那麼嚴格,你能夠在抽象類中定義 構造方法、抽象方法、普通屬性、方法、靜態屬性和靜態方法

  • 抽象類和接口同樣不能被實例化,實例化只能實例化具體的類

異常

異常是程序常常會出現的,發現錯誤的最佳時機是在編譯階段,也就是你試圖在運行程序以前。可是,在編譯期間並不能找到全部的錯誤,有一些 NullPointerExceptionClassNotFoundException 異常在編譯期找不到,這些異常是 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 及其子類進行歸類分析

YKwz9I.png

能夠看到,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

YK0S3t.png

UncheckedException

YK0pgP.png

與 Exception 有關的 Java 關鍵字

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

throws 和 throw

在 Java 中,異常也就是一個對象,它可以被程序員自定義拋出或者應用程序拋出,必須藉助於 throwsthrow 語句來定義拋出異常。

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 虛擬機)出現的問題。這些錯誤是不可檢查的,由於它們在應用程序的控制和處理能力之 外,並且絕大多數是程序運行時不容許出現的情況,好比 OutOfMemoryErrorStackOverflowError異常的出現會有幾種狀況,這裏須要先介紹一下 Java 內存模型 JDK1.7。

YK09jf.png

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

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

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

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

  • 虛擬機棧:若是線程請求的棧深度大於虛擬機棧所容許的深度,將會出現 StackOverflowError 異常;若是虛擬機動態擴展沒法申請到足夠的內存,將出現 OutOfMemoryError

  • 本地方法棧和虛擬機棧同樣

  • 堆:Java 堆能夠處於物理上不連續,邏輯上連續,就像咱們的磁盤空間同樣,若是堆中沒有內存完成實例分配,而且堆沒法擴展時,將會拋出 OutOfMemoryError。

  • 方法區:方法區沒法知足內存分配需求時,將拋出 OutOfMemoryError 異常。

在 Java 中,你能夠把異常理解爲是一種可以提升你程序健壯性的機制,它可以讓你在編寫代碼中注意這些問題,也能夠說,若是你寫代碼不會注意這些異常狀況,你是沒法成爲一位硬核程序員的。

內部類

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

YK0Pu8.png

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

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

YK0iDS.png

下面咱們就來聊一聊建立內部類的方式。

建立內部類

定義內部類很是簡單,就是直接將一個類定義在外圍類的裏面,以下代碼所示

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 這個龐大集合框架的家族體系和成員,讓你瞭解它的設計與實現。

是時候祭出這張神圖了

YK0AEQ.png

首先來介紹的就是列表爺爺輩兒的接口- Iterator

Iterable 接口

實現此接口容許對象成爲 for-each 循環的目標,也就是加強 for 循環,它是 Java 中的一種語法糖

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

除了實現此接口的對象外,數組也能夠用 for-each 循環遍歷,以下:

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

其餘遍歷方式

jdk 1.8以前Iterator只有 iterator 一個方法,就是

Iterator<T> iterator();

實現次接口的方法可以建立一個輕量級的迭代器,用於安全的遍歷元素,移除元素,添加元素。這裏面涉及到一個 fail-fast 機制。

總之一點就是能建立迭代器進行元素的添加和刪除的話,就儘可能使用迭代器進行添加和刪除。

也可使用迭代器的方式進行遍歷

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

頂層接口

Collection 是一個頂層接口,它主要用來定義集合的約定

List 接口也是一個頂層接口,它繼承了 Collection 接口 ,同時也是 ArrayList、LinkedList 等集合元素的父類

Set 接口位於與 List 接口同級的層次上,它同時也繼承了 Collection 接口。Set 接口提供了額外的規定。它對add、equals、hashCode 方法提供了額外的標準。

Queue 是和 List、Set 接口並列的 Collection 的三大接口之一。Queue 的設計用來在處理以前保持元素的訪問次序。除了 Collection 基礎的操做以外,隊列提供了額外的插入,讀取,檢查操做。

SortedSet 接口直接繼承於 Set 接口,使用 Comparable 對元素進行天然排序或者使用 Comparator 在建立時對元素提供定製的排序規則。set 的迭代器將按升序元素順序遍歷集合。

Map 是一個支持 key-value 存儲的對象,Map 不能包含重複的 key,每一個鍵最多映射一個值。這個接口代替了Dictionary 類,Dictionary 是一個抽象類而不是接口。

ArrayList

ArrayList 是實現了 List 接口的可擴容數組(動態數組),它的內部是基於數組實現的。它的具體定義以下:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {...}
  • ArrayList 能夠實現全部可選擇的列表操做,容許全部的元素,包括空值。ArrayList 還提供了內部存儲 list 的方法,它可以徹底替代 Vector,只有一點例外,ArrayList 不是線程安全的容器。
  • ArrayList 有一個容量的概念,這個數組的容量就是 List 用來存儲元素的容量。
  • ArrayList 不是線程安全的容器,若是多個線程中至少有兩個線程修改了 ArrayList 的結構的話就會致使線程安全問題,做爲替代條件可使用線程安全的 List,應使用 Collections.synchronizedList
List list = Collections.synchronizedList(new ArrayList(...))
  • ArrayList 具備 fail-fast 快速失敗機制,可以對 ArrayList 做出失敗檢測。當在迭代集合的過程當中該集合在結構上發生改變的時候,就有可能會發生 fail-fast,即拋出 ConcurrentModificationException 異常。

Vector

Vector 同 ArrayList 同樣,都是基於數組實現的,只不過 Vector 是一個線程安全的容器,它對內部的每一個方法都簡單粗暴的上鎖,避免多線程引發的安全性問題,可是一般這種同步方式須要的開銷比較大,所以,訪問元素的效率要遠遠低於 ArrayList。

還有一點在於擴容上,ArrayList 擴容後的數組長度會增長 50%,而 Vector 的擴容長度後數組會增長一倍。

LinkedList 類

LinkedList 是一個雙向鏈表,容許存儲任何元素(包括 null )。它的主要特性以下:

  • LinkedList 全部的操做均可以表現爲雙向性的,索引到鏈表的操做將遍歷從頭至尾,視哪一個距離近爲遍歷順序。
  • 注意這個實現也不是線程安全的,若是多個線程併發訪問鏈表,而且至少其中的一個線程修改了鏈表的結構,那麼這個鏈表必須進行外部加鎖。或者使用
List list = Collections.synchronizedList(new LinkedList(...))

Stack

堆棧是咱們常說的後入先出(吃了吐)的容器 。它繼承了 Vector 類,提供了一般用的 push 和 pop 操做,以及在棧頂的 peek 方法,測試 stack 是否爲空的 empty 方法,和一個尋找與棧頂距離的 search 方法。

第一次建立棧,不包含任何元素。一個更完善,可靠性更強的 LIFO 棧操做由 Deque 接口和他的實現提供,應該優先使用這個類

Deque<Integer> stack = new ArrayDeque<Integer>()

HashSet

HashSet 是 Set 接口的實現類,由哈希表支持(實際上 HashSet 是 HashMap 的一個實例)。它不能保證集合的迭代順序。這個類容許 null 元素。

  • 注意這個實現不是線程安全的。若是多線程併發訪問 HashSet,而且至少一個線程修改了set,必須進行外部加鎖。或者使用 Collections.synchronizedSet() 方法重寫。
  • 這個實現支持 fail-fast 機制。

TreeSet

TreeSet 是一個基於 TreeMap 的 NavigableSet 實現。這些元素使用他們的天然排序或者在建立時提供的Comparator 進行排序,具體取決於使用的構造函數。

  • 此實現爲基本操做 add,remove 和 contains 提供了 log(n) 的時間成本。
  • 注意這個實現不是線程安全的。若是多線程併發訪問 TreeSet,而且至少一個線程修改了 set,必須進行外部加鎖。或者使用
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...))
  • 這個實現持有 fail-fast 機制。

LinkedHashSet 類

LinkedHashSet 繼承於 Set,先來看一下 LinkedHashSet 的繼承體系:

YK0FHg.png

LinkedHashSet 是 Set 接口的 Hash 表和 LinkedList 的實現。這個實現不一樣於 HashSet 的是它維護着一個貫穿全部條目的雙向鏈表。此鏈表定義了元素插入集合的順序。注意:若是元素從新插入,則插入順序不會受到影響。

  • LinkedHashSet 有兩個影響其構成的參數: 初始容量和加載因子。它們的定義與 HashSet 徹底相同。但請注意:對於 LinkedHashSet,選擇太高的初始容量值的開銷要比 HashSet 小,由於 LinkedHashSet 的迭代次數不受容量影響。
  • 注意 LinkedHashSet 也不是線程安全的,若是多線程同時訪問 LinkedHashSet,必須加鎖,或者經過使用
Collections.synchronizedSet
  • 該類也支持fail-fast機制

PriorityQueue

PriorityQueue 是 AbstractQueue 的實現類,優先級隊列的元素根據天然排序或者經過在構造函數時期提供Comparator 來排序,具體根據構造器判斷。PriorityQueue 不容許 null 元素。

  • 隊列的頭在某種意義上是指定順序的最後一個元素。隊列查找操做 poll,remove,peek 和 element 訪問隊列頭部元素。
  • 優先級隊列是無限制的,但具備內部 capacity,用於控制用於在隊列中存儲元素的數組大小。
  • 該類以及迭代器實現了 Collection、Iterator 接口的全部可選方法。這個迭代器提供了 iterator() 方法不能保證以任何特定順序遍歷優先級隊列的元素。若是你須要有序遍歷,考慮使用 Arrays.sort(pq.toArray())
  • 注意這個實現不是線程安全的,多線程不該該併發訪問 PriorityQueue 實例若是有某個線程修改了隊列的話,使用線程安全的類 PriorityBlockingQueue

HashMap

HashMap 是一個利用哈希表原理來存儲元素的集合,而且容許空的 key-value 鍵值對。HashMap 是非線程安全的,也就是說在多線程的環境下,可能會存在問題,而 Hashtable 是線程安全的容器。HashMap 也支持 fail-fast 機制。HashMap 的實例有兩個參數影響其性能:初始容量 和加載因子。可使用 Collections.synchronizedMap(new HashMap(...)) 來構造一個線程安全的 HashMap。

TreeMap 類

一個基於 NavigableMap 實現的紅黑樹。這個 map 根據 key 天然排序存儲,或者經過 Comparator 進行定製排序。

  • TreeMap 爲 containsKey,get,put 和remove方法提供了 log(n) 的時間開銷。

  • 注意這個實現不是線程安全的。若是多線程併發訪問 TreeMap,而且至少一個線程修改了 map,必須進行外部加鎖。這一般經過在天然封裝集合的某個對象上進行同步來實現,或者使用 SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...))

  • 這個實現持有fail-fast機制。

LinkedHashMap 類

LinkedHashMap 是 Map 接口的哈希表和鏈表的實現。這個實現與 HashMap 不一樣之處在於它維護了一個貫穿其全部條目的雙向鏈表。這個鏈表定義了遍歷順序,一般是插入 map 中的順序。

  • 它提供一個特殊的 LinkedHashMap(int,float,boolean) 構造器來建立 LinkedHashMap,其遍歷順序是其最後一次訪問的順序。

  • 能夠重寫 removeEldestEntry(Map.Entry) 方法,以便在將新映射添加到 map 時強制刪除過時映射的策略。

  • 這個類提供了全部可選擇的 map 操做,而且容許 null 元素。因爲維護鏈表的額外開銷,性能可能會低於HashMap,有一條除外:遍歷 LinkedHashMap 中的 collection-views 須要與 map.size 成正比,不管其容量如何。HashMap 的迭代看起來開銷更大,由於還要求時間與其容量成正比。

  • LinkedHashMap 有兩個因素影響了它的構成:初始容量和加載因子。

  • 注意這個實現不是線程安全的。若是多線程併發訪問LinkedHashMap,而且至少一個線程修改了map,必須進行外部加鎖。這一般經過在天然封裝集合的某個對象上進行同步來實現 Map m = Collections.synchronizedMap(new LinkedHashMap(...))

  • 這個實現持有fail-fast機制。

Hashtable 類

Hashtable 類實現了一個哈希表,可以將鍵映射到值。任何非空對象均可以用做鍵或值。

  • 此實現類支持 fail-fast 機制
  • 與新的集合實現不一樣,Hashtable 是線程安全的。若是不須要線程安全的容器,推薦使用 HashMap,若是須要多線程高併發,推薦使用 ConcurrentHashMap

IdentityHashMap 類

IdentityHashMap 是比較小衆的 Map 實現了。

  • 這個類不是一個通用的 Map 實現!雖然這個類實現了 Map 接口,但它故意違反了 Map 的約定,該約定要求在比較對象時使用 equals 方法,此類僅適用於須要引用相等語義的極少數狀況。
  • 同 HashMap,IdentityHashMap 也是無序的,而且該類不是線程安全的,若是要使之線程安全,能夠調用Collections.synchronizedMap(new IdentityHashMap(...))方法來實現。
  • 支持fail-fast機制

WeakHashMap 類

WeakHashMap 類基於哈希表的 Map 基礎實現,帶有弱鍵。WeakHashMap 中的 entry 當再也不使用時還會自動移除。更準確的說,給定key的映射的存在將不會阻止 key 被垃圾收集器丟棄。

  • 基於 map 接口,是一種弱鍵相連,WeakHashMap 裏面的鍵會自動回收
  • 支持 null 值和 null 鍵。
  • fast-fail 機制
  • 不容許重複
  • WeakHashMap 常常用做緩存

Collections 類

Collections 不屬於 Java 框架繼承樹上的內容,它屬於單獨的分支,Collections 是一個包裝類,它的做用就是爲集合框架提供某些功能實現,此類只包括靜態方法操做或者返回 collections。

同步包裝

同步包裝器將自動同步(線程安全性)添加到任意集合。 六個核心集合接口(Collection,Set,List,Map,SortedSet 和 SortedMap)中的每個都有一個靜態工廠方法。

public static  Collection synchronizedCollection(Collection c);
public static  Set synchronizedSet(Set s);
public static  List synchronizedList(List list);
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m);
public static  SortedSet synchronizedSortedSet(SortedSet s);
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m);

不可修改的包裝

不可修改的包裝器經過攔截修改集合的操做並拋出 UnsupportedOperationException,主要用在下面兩個情景:

  • 構建集合後使其不可變。在這種狀況下,最好不要去獲取返回 collection 的引用,這樣有利於保證不變性
  • 容許某些客戶端以只讀方式訪問你的數據結構。 你保留對返回的 collection 的引用,但分發對包裝器的引用。 經過這種方式,客戶能夠查看但不能修改,同時保持徹底訪問權限。

這些方法是:

public static  Collection unmodifiableCollection(Collection<? extends T> c);
public static  Set unmodifiableSet(Set<? extends T> s);
public static  List unmodifiableList(List<? extends T> list);
public static <K,V> Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m);
public static  SortedSet unmodifiableSortedSet(SortedSet<? extends T> s);
public static <K,V> SortedMap<K, V> unmodifiableSortedMap(SortedMap<K, ? extends V> m);

線程安全的Collections

Java1.5 併發包 (java.util.concurrent) 提供了線程安全的 collections 容許遍歷的時候進行修改,經過設計iterator 爲 fail-fast 並拋出 ConcurrentModificationException。一些實現類是CopyOnWriteArrayListConcurrentHashMapCopyOnWriteArraySet

Collections 算法

此類包含用於集合框架算法的方法,例如二進制搜索,排序,重排,反向等。

集合實現類特徵圖

下圖彙總了部分集合框架的主要實現類的特徵圖,讓你能有清晰明瞭看出每一個實現類之間的差別性

YK0ENj.png

還有一種類型是關於強引用、弱引用、虛引用的文章,請參考

https://mp.weixin.qq.com/s/ZflBpn2TBzTNv_-G-zZxNg

泛形

在 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);

這段代碼在編譯期間就會報錯,編譯器會在編譯階段就可以幫咱們發現相似這樣的問題。

泛型的使用

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

用泛型表示類

泛型能夠加到類上面,來表示這個類的類型

//此處 T 能夠隨便寫爲任意標識,常見的如T、E、K、V等形式的參數經常使用於表示泛型
public class GenericDemo<T>{ 
    //value 這個成員變量的類型爲T,T的類型由外部指定  
    private T value;

    public GenericDemo(T value) {
        this.value = value;
    }

    public T getValue(){ //泛型方法getKey的返回值類型爲T,T的類型由外部指定
        return value;
    }
 
 		public void setValue(T value){
	      this.value = value
    }
}

用泛型表示接口

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

//定義一個泛型接口
public interface Generator<T> {
    public T next();
}

通常泛型接口經常使用於 生成器(generator) 中,生成器至關於對象工廠,是一種專門用來建立對象的類。

泛型方法

可使用泛型來表示方法

public class GenericMethods {
  public <T> void f(T x){
    System.out.println(x.getClass().getName());
  }
}

泛型通配符

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 所設計的類以下

YK0V4s.png

下面是一個簡單的反射類

public class Person {
    public String name;// 姓名
    public int age;// 年齡
 
    public Person() {
        super();
    }
 
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
 
    public String showInfo() {
        return "name=" + name + ", age=" + age;
    }
}

public class Student extends Person implements Study {
    public String className;// 班級
    private String address;// 住址
 
    public Student() {
        super();
    }
 
    public Student(String name, int age, String className, String address) {
        super(name, age);
        this.className = className;
        this.address = address;
    }
 
    public Student(String className) {
        this.className = className;
    }
 
    public String toString() {
        return "姓名:" + name + ",年齡:" + age + ",班級:" + className + ",住址:"
                + address;
    }
 
    public String getAddress() {
        return address;
    }
 
    public void setAddress(String address) {
        this.address = address;
    }
}

public class TestRelect {
 
    public static void main(String[] args) {
        Class student = null;
        try {
            student = Class.forName("com.cxuan.reflection.Student");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
 
        // 獲取對象的全部公有屬性。
        Field[] fields = student.getFields();
        for (Field f : fields) {
            System.out.println(f);
        }
        System.out.println("---------------------");
        // 獲取對象全部屬性,但不包含繼承的。
        Field[] declaredFields = student.getDeclaredFields();
        for (Field df : declaredFields) {
            System.out.println(df);
        }
      
      	// 獲取對象的全部公共方法
        Method[] methods = student.getMethods();
        for (Method m : methods) {
            System.out.println(m);
        }
        System.out.println("---------------------");
        // 獲取對象全部方法,但不包含繼承的
        Method[] declaredMethods = student.getDeclaredMethods();
        for (Method dm : declaredMethods) {
            System.out.println(dm);
        }
				
      	// 獲取對象全部的公共構造方法
        Constructor[] constructors = student.getConstructors();
        for (Constructor c : constructors) {
            System.out.println(c);
        }
        System.out.println("---------------------");
        // 獲取對象全部的構造方法
        Constructor[] declaredConstructors = student.getDeclaredConstructors();
        for (Constructor dc : declaredConstructors) {
            System.out.println(dc);
        }
      
      	Class c = Class.forName("com.cxuan.reflection.Student");
      	Student stu1 = (Student) c.newInstance();
      	// 第一種方法,實例化默認構造方法,調用set賦值
        stu1.setAddress("河北石家莊");
        System.out.println(stu1);

        // 第二種方法 取得所有的構造函數 使用構造函數賦值
        Constructor<Student> constructor = c.getConstructor(String.class, 
                                                            int.class, String.class, String.class);
        Student student2 = (Student) constructor.newInstance("cxuan", 24, "六班", "石家莊");
        System.out.println(student2);

        /**
        * 獲取方法並執行方法
        */
        Method show = c.getMethod("showInfo");//獲取showInfo()方法
        Object object = show.invoke(stu2);//調用showInfo()方法
      	
 
    }
}

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

與 Java 反射有關的類主要有

Class 類

在 Java 中,你每定義一個 java class 實體都會產生一個 Class 對象。也就是說,當咱們編寫一個類,編譯完成後,在生成的 .class 文件中,就會產生一個 Class 對象,這個 Class 對象用於表示這個類的類型信息。Class 中沒有公共的構造器,也就是說 Class 對象不能被實例化。下面來簡單看一下 Class 類都包括了哪些方法

toString()

public String toString() {
  return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
    + getName();
}

toString() 方法可以將對象轉換爲字符串,toString() 首先會判斷 Class 類型是不是接口類型,也就是說,普通類和接口都可以用 Class 對象來表示,而後再判斷是不是基本數據類型,這裏判斷的都是基本數據類型和包裝類,還有 void 類型。

全部的類型以下

  • java.lang.Boolean : 表明 boolean 數據類型的包裝類
  • java.lang.Character: 表明 char 數據類型的包裝類
  • java.lang.Byte: 表明 byte 數據類型的包裝類
  • java.lang.Short: 表明 short 數據類型的包裝類
  • java.lang.Integer: 表明 int 數據類型的包裝類
  • java.lang.Long: 表明 long 數據類型的包裝類
  • java.lang.Float: 表明 float 數據類型的包裝類
  • java.lang.Double: 表明 double 數據類型的包裝類
  • java.lang.Void: 表明 void 數據類型的包裝類

而後是 getName() 方法,這個方法返回類的全限定名稱。

  • 若是是引用類型,好比 String.class.getName() -> java.lang.String
  • 若是是基本數據類型,byte.class.getName() -> byte
  • 若是是數組類型,new Object[3]).getClass().getName() -> [Ljava.lang.Object

toGenericString()

這個方法會返回類的全限定名稱,並且包括類的修飾符和類型參數信息。

forName()

根據類名得到一個 Class 對象的引用,這個方法會使類對象進行初始化。

例如 Class t = Class.forName("java.lang.Thread") 就可以初始化一個 Thread 線程對象

在 Java 中,一共有三種獲取類實例的方式

  • Class.forName(java.lang.Thread)
  • Thread.class
  • thread.getClass()

newInstance()

建立一個類的實例,表明着這個類的對象。上面 forName() 方法對類進行初始化,newInstance 方法對類進行實例化。

getClassLoader()

獲取類加載器對象。

getTypeParameters()

按照聲明的順序獲取對象的參數類型信息。

getPackage()

返回類的包

getInterfaces()

得到當前類實現的類或是接口,多是有多個,因此返回的是 Class 數組。

Cast

把對象轉換成表明類或是接口的對象

asSubclass(Class clazz)

把傳遞的類的對象轉換成表明其子類的對象

getClasses()

返回一個數組,數組中包含該類中全部公共類和接口類的對象

getDeclaredClasses()

返回一個數組,數組中包含該類中全部類和接口類的對象

getSimpleName()

得到類的名字

getFields()

得到全部公有的屬性對象

getField(String name)

得到某個公有的屬性對象

getDeclaredField(String name)

得到某個屬性對象

getDeclaredFields()

得到全部屬性對象

getAnnotation(Class annotationClass)

返回該類中與參數類型匹配的公有註解對象

getAnnotations()

返回該類全部的公有註解對象

getDeclaredAnnotation(Class annotationClass)

返回該類中與參數類型匹配的全部註解對象

getDeclaredAnnotations()

返回該類全部的註解對象

getConstructor(Class...<?> parameterTypes)

得到該類中與參數類型匹配的公有構造方法

getConstructors()

得到該類的全部公有構造方法

getDeclaredConstructor(Class...<?> parameterTypes)

得到該類中與參數類型匹配的構造方法

getDeclaredConstructors()

得到該類全部構造方法

getMethod(String name, Class...<?> parameterTypes)

得到該類某個公有的方法

getMethods()

得到該類全部公有的方法

getDeclaredMethod(String name, Class...<?> parameterTypes)

得到該類某個方法

getDeclaredMethods()

得到該類全部方法

Field 類

Field 類提供類或接口中單獨字段的信息,以及對單獨字段的動態訪問。

這裏就再也不對具體的方法進行介紹了,讀者有興趣能夠參考官方 API

這裏只介紹幾個經常使用的方法

equals(Object obj)

屬性與obj相等則返回true

get(Object obj)

得到obj中對應的屬性值

set(Object obj, Object value)

設置obj中對應屬性值

Method 類

invoke(Object obj, Object... args)

傳遞object對象及參數調用該對象對應的方法

ClassLoader 類

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

YK0eCn.png

枚舉

枚舉多是咱們使用次數比較少的特性,在 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 關鍵字能夠作到靜態導入包

YK0m3q.pngYK0ng0.png

上面代碼導入的是 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 包下,能夠自動引用。

YK0ng0.png

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

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

YK0uvV.png

前面兩個方法用於獲取 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 進行一個分類。

YK0MuT.png

公號回覆 IO獲取思惟導圖

I/O 還能夠根據操做對象來進行區分:主要分爲

YK0QDU.png

除此以外,I/O 中還有其餘比較重要的類

File 類

File 類是對文件系統中文件以及文件夾進行操做的類,能夠經過面向對象的思想操做文件和文件夾,是否是很神奇?

文件建立操做以下,主要涉及 文件建立、刪除文件、獲取文件描述符等

class FileDemo{
   public static void main(String[] args) {
       File file = new File("D:\\file.txt");
       try{
         f.createNewFile(); // 建立一個文件
         
         // File類的兩個常量
         //路徑分隔符(與系統有關的)<windows裏面是 ; linux裏面是 : >
        System.out.println(File.pathSeparator);  //   ;
        //與系統有關的路徑名稱分隔符<windows裏面是 \ linux裏面是/ >
        System.out.println(File.separator);      //  \
         
         // 刪除文件
         /*
         File file = new File(fileName);
         if(f.exists()){
             f.delete();
         }else{
             System.out.println("文件不存在");
         }   
         */

         
       }catch (Exception e) {
           e.printStackTrace();
       }
    }
}

也能夠對文件夾進行操做

class FileDemo{
  public static void main(String[] args) {
    String fileName = "D:"+ File.separator + "filepackage";
    File file = new File(fileName);
    f.mkdir();
    
		// 列出全部文件
    /*
    String[] str = file.list();
    for (int i = 0; i < str.length; i++) {
      System.out.println(str[i]);
    }
    */
    
    // 使用 file.listFiles(); 列出全部文件,包括隱藏文件
    
    // 使用 file.isDirectory() 判斷指定路徑是不是目錄
  }
}

上面只是舉出來了兩個簡單的示例,實際上,還有一些其餘對文件的操做沒有使用。好比建立文件,就可使用三種方式來建立

File(String directoryPath);
File(String directoryPath, String filename);
File(File dirObj, String filename);

directoryPath 是文件的路徑名,filename 是文件名,dirObj 是一個 File 對象。例如

File file = new File("D:\\java\\file1.txt");  //雙\\是轉義
System.out.println(file);
File file2 = new File("D:\\java","file2.txt");//父路徑、子路徑--能夠適用於多個文件的!
System.out.println(file2);
File parent = new File("D:\\java");
File file3 = new File(parent,"file3.txt");//File類的父路徑、子路徑
System.out.println(file3);

如今對 File 類進行總結

YK0lbF.png

基礎 IO 類和相關方法

雖然. IO 類有不少,可是最基本的是四個抽象類,InputStream、OutputStream、Reader、Writer。最基本的方法也就是 read()write() 方法,其餘流都是上面這四類流的子類,方法也是經過這兩類方法衍生而成的。並且大部分的 IO 源碼都是 native 標誌的,也就是說源碼都是 C/C++ 寫的。這裏咱們先來認識一下這些流類及其方法

InputStream

InputStream 是一個定義了 Java 流式字節輸入模式的抽象類。該類的全部方法在出錯條件下引起一個IOException 異常。它的主要方法定義以下

YK03E4.png

OutputStream

OutputStream 是定義了流式字節輸出模式的抽象類。該類的全部方法返回一個void 值而且在出錯狀況下引起一個IOException異常。它的主要方法定義以下

YK08UJ.png

Reader 類

Reader 是 Java 定義的流式字符輸入模式的抽象類。類中的方法在出錯時引起 IOException 異常。

YK0YCR.png

Writer 類

Writer 是定義流式字符輸出的抽象類。 全部該類的方法都返回一個 void 值並在出錯條件下引起 IOException 異常

YK0t81.png

InputStream 及其子類

FileInputStream 文件輸入流: FileInputStream 類建立一個能從文件讀取字節的 InputStream 類

ByteArrayInputStream 字節數組輸入流 : 把內存中的一個緩衝區做爲 InputStream 使用

PipedInputStream 管道輸入流: 實現了pipe 管道的概念,主要在線程中使用

SequenceInputStream 順序輸入流:把多個 InputStream 合併爲一個 InputStream

FilterOutputStream 過濾輸入流:其餘輸入流的包裝。

ObjectInputStream 反序列化輸入流 : 將以前使用 ObjectOutputStream 序列化的原始數據恢復爲對象,以流的方式讀取對象

**DataInputStream ** : 數據輸入流容許應用程序以與機器無關方式從底層輸入流中讀取基本 Java 數據類型。

PushbackInputStream 推回輸入流: 緩衝的一個新穎的用法是實現推回 (pushback) 。 Pushback 用於輸入流容許字節被讀取而後返回到流。

OutputStream 及其子類

FileOutputStream 文件輸出流: 該類實現了一個輸出流,其數據寫入文件。

ByteArrayOutputStream 字節數組輸出流 :該類實現了一個輸出流,其數據被寫入由 byte 數組充當的緩衝區,緩衝區會隨着數據的不斷寫入而自動增加。

PipedOutputStream 管道輸出流 :管道的輸出流,是管道的發送端。

ObjectOutputStream 基本類型輸出流 :該類將實現了序列化的對象序列化後寫入指定地方。

FilterOutputStream 過濾輸出流:其餘輸出流的包裝。

PrintStream 打印流 經過 PrintStream 能夠將文字打印到文件或者網絡中去。

**DataOutputStream ** : 數據輸出流容許應用程序以與機器無關方式向底層輸出流中寫入基本 Java 數據類型。

Reader 及其子類

FileReader 文件字符輸入流 : 把文件轉換爲字符流讀入

CharArrayReader 字符數組輸入流 : 是一個把字符數組做爲源的輸入流的實現

BufferedReader 緩衝區輸入流 : BufferedReader 類從字符輸入流中讀取文本並緩衝字符,以便有效地讀取字符,數組和行

**PushbackReader **: PushbackReader 類容許一個或多個字符被送回輸入流。

PipedReader 管道輸入流: 主要用途也是在線程間通信,不過這個能夠用來傳輸字符

Writer 及其子類

FileWriter 字符輸出流 : FileWriter 建立一個能夠寫文件的 Writer 類。

CharArrayWriter 字符數組輸出流: CharArrayWriter 實現了以數組做爲目標的輸出流。

BufferedWriter 緩衝區輸出流 : BufferedWriter是一個增長了flush( ) 方法的Writer。 flush( )方法能夠用來確保數據緩衝器確實被寫到實際的輸出流。

**PrintWriter ** : PrintWriter 本質上是 PrintStream 的字符形式的版本。

PipedWriter 管道輸出流: 主要用途也是在線程間通信,不過這個能夠用來傳輸字符

Java 的輸入輸出的流式接口爲複雜而繁重的任務提供了一個簡潔的抽象。過濾流類的組合容許你動態創建客戶端流式接口來配合數據傳輸要求。繼承高級流類 InputStream、InputStreamReader、 Reader 和 Writer 類的 Java 程序在未來 (即便建立了新的和改進的具體類)也能獲得合理運用。

註解

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 enum ElementType {
    TYPE,
    FIELD,
    METHOD,
    PARAMETER,
    CONSTRUCTOR,
    LOCAL_VARIABLE,
    ANNOTATION_TYPE,
    PACKAGE,
    TYPE_PARAMETER,
    TYPE_USE
  • @Inherited : 標記這個註解是繼承於哪一個註解類的。

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

  • @SafeVarargs :在聲明可變參數的構造函數或方法時,Java 編譯器會報 unchecked 警告。使用 @SafeVarargs 能夠忽略這些警告

  • @FunctionalInterface: 代表這個方法是一個函數式接口

  • @Repeatable: 標識某註解能夠在同一個聲明上使用屢次。

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

關於 null 的幾種處理方式

對於 Java 程序員來講,空指針一直是惱人的問題,咱們在開發中常常會受到 NullPointerException 的蹂躪和壁咚。Java 的發明者也認可這是一個巨大的設計錯誤。

那麼關於 null ,你應該知道下面這幾件事情來有效的瞭解 null ,從而避免不少由 null 引發的錯誤。

YK0Ngx.png

大小寫敏感

首先,null 是 Java 中的關鍵字,像是 public、static、final。它是大小寫敏感的,你不能將 null 寫成 Null 或 NULL,編輯器將不能識別它們而後報錯。

YK0Uv6.png

這個問題已經幾乎不會出現,由於 eclipse 和 Idea 編譯器已經給出了編譯器提示,因此你不用考慮這個問題。

null 是任何引用類型的初始值

null 是全部引用類型的默認值,Java 中的任何引用變量都將null做爲默認值,也就是說全部 Object 類下的引用類型默認值都是 null。這對全部的引用變量都適用。就像是基本類型的默認值同樣,例如 int 的默認值是 0,boolean 的默認值是 false。

下面是基本數據類型的初始值

YK0dKK.png

null 只是一種特殊的值

null 既不是對象也不是一種類型,它僅是一種特殊的值,你能夠將它賦予任何類型,你能夠將 null 轉換爲任何類型

public static void main(String[] args) {
  String str = null;
  Integer itr = null;
  Double dou = null;

  Integer integer = (Integer) null;
  String string = (String)null;

  System.out.println("integer = " + integer);
  System.out.println("string = " + string);
}

你能夠看到在編譯期和運行期內,將 null 轉換成任何的引用類型都是可行的,而且不會拋出空指針異常。

null 只能賦值給引用變量,不能賦值給基本類型變量

持有 null 的包裝類在進行自動拆箱的時候,不能完成轉換,會拋出空指針異常,而且 null 也不能和基本數據類型進行對比

public static void main(String[] args) {
  int i = 0;
  Integer itr = null;
  System.out.println(itr == i);
}

使用了帶有 null 值的引用類型變量,instanceof 操做會返回 false

public static void main(String[] args) {
  Integer isNull = null;
  // instanceof = isInstance 方法
  if(isNull instanceof Integer){
    System.out.println("isNull is instanceof Integer");
  }else{
    System.out.println("isNull is not instanceof Integer");
  }
}

這是 instanceof 操做符一個很重要的特性,使得對類型強制轉換檢查頗有用

靜態變量爲 null 調用靜態方法不會拋出 NullPointerException。由於靜態方法使用了靜態綁定

使用 Null-Safe 方法

你應該使用 null-safe 安全的方法,java 類庫中有不少工具類都提供了靜態方法,例如基本數據類型的包裝類,Integer , Double 等。例如

public class NullSafeMethod {

    private static String number;

    public static void main(String[] args) {
        String s = String.valueOf(number);
        String string = number.toString();
        System.out.println("s = " + s);
        System.out.println("string = " + string);
    }
}

number 沒有賦值,因此默認爲null,使用String.value(number) 靜態方法沒有拋出空指針異常,可是使用 toString() 卻拋出了空指針異常。因此儘可能使用對象的靜態方法。

null 判斷

你可使用 == 或者 != 操做來比較 null 值,可是不能使用其餘算法或者邏輯操做,例如小於或者大於。跟SQL不同,在Java中 null == null 將返回 true,以下所示:

public class CompareNull {

    private static String str1;
    private static String str2;

    public static void main(String[] args) {
        System.out.println("str1 == str2 ? " + str1 == str2);
        System.out.println(null == null);
    }
}

關於思惟導圖

我把一些經常使用的 Java 工具包的思惟導圖作了彙總,方便讀者查閱。

Java.IO

YK0wDO.png

Java.lang

YK00bD.png

Java.math

YK0DVe.png

Java.net

YK0rUH.png

思惟導圖持續更新中~~~ 歡迎關注公衆號 程序員cxuan 領取超全思惟導圖。

相關文章
相關標籤/搜索