1. 程式人生 > 實用技巧 >Burp啟動指令碼配置-隱藏CMD視窗

Burp啟動指令碼配置-隱藏CMD視窗

Java基礎知識

1、開發前言

1.1、 Java語言概述

什麼是Java語言

Java語言發展歷史

  • 1995年sun公司釋出Java1.0版本
  • 1997年釋出Java1.1版本
  • 1998年釋出Java1.2版本
  • 2000年釋出Java1.3版本
  • 2002年釋出Java1.4版本
  • 2004年釋出Java1.5版本
  • 2006年釋出Java1.6版本
  • 2009年Oracle甲骨文公司收購sun公司,並於2011年釋出Java1.7版本
  • 2014年釋出Java1.8版本
  • 2017年釋出Java9.0版本
1.2、計算機基礎知識

1)二進位制

計算機中的資料不同於人們生活中的資料,人們生活採用十進位制數,而計算機中全部採用二進位制數表示,它只包含0、1兩個數,逢二進1,每一個0或者每個1,叫做一個bit(位元)

十進位制轉成二進位制:

二進位制轉成十進位制:

2)位元組

位元組是我們常見的計算機中最小儲存單元。計算機儲存任何的資料,都是以位元組的形式儲存。

3)常用DOS命令

2、 Java語言開發環境搭建

2.1、Java虛擬機器-JVM
  • JVM(Java virtual machine):Java虛擬機器,簡稱JVM,是執行所有Java程式的假象計算機,是Java程式的執行環境,是Java最具吸引力的特性之一。我們編寫的Java程式碼,都執行在JVM之上

  • 跨平臺:任何軟體的執行,都必須要執行在操縱系統之上,而我們用Java編寫的軟體可以執行在任何的作業系統之上,這個特性稱為Java語言的跨平臺特性,該特性是由JVM實現的,我們編寫的程式執行在JVM上,而JVM執行在作業系統之上。

2.2、 JRE和JDK
  • JRE(Java runtime environment)是Java程式的執行時環境,包含JVM和執行時所需要的核心類庫。
  • JDK(Java development kit)是Java程式開發工具包,包含JRE和開發人員使用的工具。

如果執行一個已有的Java程式,那麼只需要安裝JRE即可;

如果想要開發一個全新的Java程式,那麼必須安裝JDK;

2.3、JDK安裝後環境變數的配置

配置環境變數的原因,就是如果沒配置環境遍歷給,那麼你要到JDK安裝的目錄下去執行Java程式,如果配置了環境,那麼就不用每次跑到安裝程式的目錄下去跑程式;

配置步驟:

然後新增變數名輸入JAVA_HOME,變數值輸入JDK安裝的目錄;

然後選中Path環境變數,點選編輯

在變數值的最前面,加入 %JAVA_HOME%\bin;分號必須要寫,必須是英文格式;

配置完成後,在DOS命令列,在任意目錄下輸入Java命令,執行成功

3、 HelloWorld入門程式

3.1、程式開發步驟

建立一個為java檔案

public class HelloWorld{
    pulbic static void main(String[] ags){
        System.out.println("Hello World");
    }
}

注意檔名必須和類名一樣;

3.2、編譯Java原始檔

在DOS命令列中,進入Java原始檔的目錄,使用javac命令進行編譯

javac java檔名.字尾名

編譯成功後,檔案目錄下會有一個Java名.class的檔案

然後執行檔案【特別注意執行的檔案是沒有後綴的】

Java java檔名

3.3、入門程式說明

編譯二號執行時兩回事

  • 編譯:是指我們編寫的Java原始檔翻譯成JVM認識的class檔案,在這個過程中,javac編譯器會檢查我們所寫的程式是否有錯誤,有錯誤就會提示出來;
  • 執行:是指將class檔案交給JVM去執行,此時JVM就會去執行我們編寫的程式了

關於main方法

  • main方法:稱為主方法。寫法是固定格式不可以更改。main方法是程式的入口點或起始點,無論我們編寫多少程式,JVM在執行的時候,都會從main方法這裡開始執行;
3.4、識別符號
  • 識別符號:是指在程式中,我們自己定義內容。比如類的名字、方法的名字和變數的名字等待。

  • 命名規則:

    1)識別符號可以包含英文字母26個(區分大小寫)、0-9數字、$和_(下劃線);

    2)識別符號不能以數字開頭;

    3)識別符號不能是關鍵字;

  • 命名規範:

    1)類名規範:首字母大寫,後面每個單詞首字母大寫(大駝峰式);

    2)方法名規範:首字母小寫,後面每個單詞首字母大寫(小駝峰式);

    3)變數名規範:全部小寫;

4、常量

4.1、概述

常量:是指在Java程式中固定不變的資料。

4.2、分類

5、變數和資料型別

5.1、變數概述
  • 變數:常量式固定不變的資料,那麼在程式中可以變化的量稱為變數
5.2、資料型別

1)資料型別分類

Java的資料型別分為兩大類:

  • 基本資料型別:包括整數、浮點數、字元、布林。
  • 引用資料型別:包括類、陣列、介面。

基本資料型別

Java中的預設型別:整數型別是int、浮點型別是double;

5.3、變數的定義

變數定義的格式包括三個要素:資料型別、變數名、資料值。

格式

資料型別  變數名=資料值;

注意:

long型別:建議在資料後面加L表示;

float型別:建議在資料後加F表示;

5.4、注意事項
  • 變數名稱:在同一個大括號範圍內,變數的名字不可以相同;
  • 變數賦值:定義的變數,不賦值不能使用;

6、資料型別轉換

Java程式中要求參與的計算的資料,必須要保證資料型別的一致性,如果資料型別不一致將發生型別的轉換。

6.1、自動轉換

特點:

  1. 特點:程式碼不需要進行特殊處理,自動完成。
  2. 規則:資料範圍從小到大。

一個 int 型別變數和一個 byte 型別變數進行加法運算, 結果會是什麼資料型別?

int i = 1;
byte b = 2;

運算結果,變數的型別將是 int 型別,這就是出現了資料型別的自動型別轉換現象。

  • 自動轉換:將取值範圍小的型別 自動提升為取值範圍大的型別 。
public static void main(String[] args) {
int i = 1;
byte b = 2;
// byte x = b + i; // 報錯
//int型別和byte型別運算,結果是int型別
int j = b + i;
System.out.println(j);
}

轉換原理圖解

byte 型別記憶體佔有1個位元組,在和 int 型別運算時會提升為 int 型別 ,自動補充3個位元組,因此計算後的結果還是 int 型別。

同樣道理,當一個 int 型別變數和一個 double 變數運算時, int 型別將會自動提升為 double 型別進行運算。

public static void main(String[] args) {
int i = 1;
double d = 2.5;
//int型別和double型別運算,結果是double型別
//int型別會提升為double型別
double e = d+i;
System.out.println(e);
}

轉換規則

範圍小的型別向範圍大的型別提升, byte、short、char 運算時直接提升為 int 。

byte、short、char‐‐>int‐‐>long‐‐>float‐‐>double

例子:

/*
對於byte/short/char三種類型來說,如果右側賦值的數值沒有超過範圍,
那麼javac編譯器將會自動隱含地為我們補上一個(byte)(short)(char)。

1. 如果沒有超過左側的範圍,編譯器補上強轉。
2. 如果右側超過了左側範圍,那麼直接編譯器報錯。
*/
public class Demo12Notice {
	public static void main(String[] args) {
		// 右側確實是一個int數字,但是沒有超過左側的範圍,就是正確的。
		// int --> byte,不是自動型別轉換
		byte num1 = /*(byte)*/ 30; // 右側沒有超過左側的範圍
		System.out.println(num1); // 30
		
		// byte num2 = 128; // 右側超過了左側的範圍
		
		// int --> char,沒有超過範圍
		// 編譯器將會自動補上一個隱含的(char)
		char zifu = /*(char)*/ 65;
		System.out.println(zifu); // A
	}
}

編譯器的常量優化

/*
在給變數進行賦值的時候,如果右側的表示式當中全都是常量,沒有任何變數,
那麼編譯器javac將會直接將若干個常量表達式計算得到結果。
short result = 5 + 8; // 等號右邊全都是常量,沒有任何變數參與運算
編譯之後,得到的.class位元組碼檔案當中相當於【直接就是】:
short result = 13;
右側的常量結果數值,沒有超過左側範圍,所以正確。

這稱為“編譯器的常量優化”。

但是注意:一旦表示式當中有變數參與,那麼就不能進行這種優化了。
*/
public class Demo13Notice {
	public static void main(String[] args) {
		short num1 = 10; // 正確寫法,右側沒有超過左側的範圍,
		
		short a = 5;
		short b = 8;
		// short + short --> int + int --> int
		// short result = a + b; // 錯誤寫法!左側需要是int型別
		
		// 右側不用變數,而是採用常量,而且只有兩個常量,沒有別人
		short result = 5 + 8;
		System.out.println(result);
		
		short result2 = 5 + a + 8; // 18
	}
}

6.2、強制轉換

強制型別轉換

1. 特點:程式碼需要進行特殊的格式處理,不能自動完成。
2. 格式:範圍小的型別 範圍小的變數名 = (範圍小的型別) 原本範圍大的資料;

格式

資料型別 變數名 = (資料型別)被轉資料值;

例子:

public class Demo02DataType {
	public static void main(String[] args) {
		// 左邊是int型別,右邊是long型別,不一樣
		// long --> int,不是從小到大
		// 不能發生自動型別轉換!
		// 格式:範圍小的型別 範圍小的變數名 = (範圍小的型別) 原本範圍大的資料;
		int num = (int) 100L;
		System.out.println(num);
		
		// long強制轉換成為int型別
		int num2 = (int) 6000000000L;
		System.out.println(num2); // 1705032704
		
		// double --> int,強制型別轉換
		int num3 = (int) 3.99;
		System.out.println(num3); // 3,這並不是四捨五入,所有的小數位都會被捨棄掉
		
		char zifu1 = 'A'; // 這是一個字元型變數,裡面是大寫字母A
		System.out.println(zifu1 + 1); // 66,也就是大寫字母A被當做65進行處理
		// 計算機的底層會用一個數字(二進位制)來代表字元A,就是65
		// 一旦char型別進行了數學運算,那麼字元就會按照一定的規則翻譯成為一個數字
		
		byte num4 = 40; // 注意!右側的數值大小不能超過左側的類型範圍
		byte num5 = 50;
		// byte + byte --> int + int --> int
		int result1 = num4 + num5;
		System.out.println(result1); // 90
		
		short num6 = 60;
		// byte + short --> int + int --> int
		// int強制轉換為short:注意必須保證邏輯上真實大小本來就沒有超過short範圍,否則會發生資料溢位
		short result2 = (short) (num4 + num6);
		System.out.println(result2); // 100
	}
}

轉換原理圖解:

注意事項:

1. 強制型別轉換一般不推薦使用,因為有可能發生精度損失、資料溢位。
   2. byte/short/char這三種類型都可以發生數學運算,例如加法“+”.
   3. byte/short/char這三種類型在運算的時候,都會被首先提升成為int型別,然後再計算。
   4. boolean型別不能發生資料型別轉換

7、運算子

7.1、算數運算子

運算子:進行特定操作的符號。例如:+
表示式:用運算子連起來的式子叫做表示式。例如:20 + 5。又例如:a + b

四則運算:
加:+
減:-
乘:*
除:/

取模(取餘數):%

首先計算得到表示式的結果,然後再列印輸出這個結果。
複習一下小學一年級的除法公式:
被除數 / 除數 = 商 ... 餘數

對於一個整數的表示式來說,除法用的是整除,整數除以整數,結果仍然是整數。只看商,不看餘數。
只有對於整數的除法來說,取模運算子才有餘數的意義。

注意事項:

1. 一旦運算當中有不同型別的資料,那麼結果將會是資料類型範圍大的那種。
public class Demo04Operator {
	public static void main(String[] args) {
		// 兩個常量之間可以進行數學運算
		System.out.println(20 + 30);
		
		// 兩個變數之間也可以進行數學運算
		int a = 20;
		int b = 30;
		System.out.println(a - b); // -10
		
		// 變數和常量之間可以混合使用
		System.out.println(a * 10); // 200
		
		int x = 10;
		int y = 3;
		
		int result1 = x / y;
		System.out.println(result1); // 3
		
		int result2 = x % y;
		System.out.println(result2); // 餘數,模,1
		
		// int + double --> double + double --> double
		double result3 = x + 2.5;
		System.out.println(result3); // 12.5
	}
}

四則運算當中的加號“+”有常見的三種用法:

  1. 對於數值來說,那就是加法。
  2. 對於字元char型別來說,在計算之前,char會被提升成為int,然後再計算。
    char型別字元,和int型別數字,之間的對照關係表:ASCII、Unicode
  3. 對於字串String(首字母大寫,並不是關鍵字)來說,加號代表字串連線操作。
    任何資料型別和字串進行連線的時候,結果都會變成字串
public class Demo05Plus {
	public static void main(String[] args) {
		// 字串型別的變數基本使用
		// 資料型別 變數名稱 = 資料值;
		String str1 = "Hello";
		System.out.println(str1); // Hello
		
		System.out.println("Hello" + "World"); // HelloWorld
		
		String str2 = "Java";
		// String + int --> String
		System.out.println(str2 + 20); // Java20
		
		// 優先順序問題
		// String + int + int
		// String		+ int
		// String
		System.out.println(str2 + 20 + 30); // Java2030
		
		System.out.println(str2 + (20 + 30)); // Java50
	}
}

自增運算子:++
自減運算子:--

基本含義:讓一個變數漲一個數字1,或者讓一個變數降一個數字1
使用格式:寫在變數名稱之前,或者寫在變數名稱之後。例如:++num,也可以num++
使用方式:

1. 單獨使用:不和其他任何操作混合,自己獨立成為一個步驟。
   2. 混合使用:和其他操作混合,例如與賦值混合,或者與列印操作混合,等。
      使用區別:
   3. 在單獨使用的時候,前++和後++沒有任何區別。也就是:++num;和num++;是完全一樣的。
   4. 在混合的時候,有【重大區別】
      A. 如果是【前++】,那麼變數【立刻馬上+1】,然後拿著結果進行使用。	【先加後用】
      B. 如果是【後++】,那麼首先使用變數本來的數值,【然後再讓變數+1】。	【先用後加】

注意事項:
只有變數才能使用自增、自減運算子。常量不可發生改變,所以不能用。

public class Demo06Operator {
	public static void main(String[] args) {
		int num1 = 10;
		System.out.println(num1); // 10
		++num1; // 單獨使用,前++
		System.out.println(num1); // 11
		num1++; // 單獨使用,後++
		System.out.println(num1); // 12
		System.out.println("=================");
		
		// 與列印操作混合的時候
		int num2 = 20;
		// 混合使用,先++,變數立刻馬上變成21,然後列印結果21
		System.out.println(++num2); // 21
		System.out.println(num2); // 21
		System.out.println("=================");
		
		int num3 = 30;
		// 混合使用,後++,首先使用變數本來的30,然後再讓變數+1得到31
		System.out.println(num3++); // 30
		System.out.println(num3); // 31
		System.out.println("=================");
		
		int num4 = 40;
		// 和賦值操作混合
		int result1 = --num4; // 混合使用,前--,變數立刻馬上-1變成39,然後將結果39交給result1變數
		System.out.println(result1); // 39
		System.out.println(num4); // 39
		System.out.println("=================");
		
		int num5 = 50;
		// 混合使用,後--,首先把本來的數字50交給result2,然後我自己再-1變成49
		int result2 = num5--;
		System.out.println(result2); // 50
		System.out.println(num5); // 49
		System.out.println("=================");
		
		int x = 10;
		int y = 20;
		// 11 + 20 = 31
		int result3 = ++x + y--;
		System.out.println(result3); // 31
		System.out.println(x); // 11
		System.out.println(y); // 19
		
		// 30++; // 錯誤寫法!常量不可以使用++或者--
	}
}

7.2、賦值運算子

賦值運算子分為:

基本賦值運算子:就是一個等號“=”,代表將右側的資料交給左側的變數。
int a = 30;

複合賦值運算子:
+= a += 3 相當於 a = a + 3
-= b -= 4 相當於 b = b - 4
*= c *= 5 相當於 c = c * 5
/= d /= 6 相當於 d = d / 6
%= e %= 7 相當於 e = e % 7

注意事項:

1. 只有變數才能使用賦值運算子,常量不能進行賦值。
   2. 複合賦值運算子其中隱含了一個強制型別轉換。
public class Demo07Operator {
	public static void main(String[] args) {
		int a = 10;
		// 按照公式進行翻譯:a = a + 5
		// a = 10 + 5;
		// a = 15;
		// a本來是10,現在重新賦值得到15
		a += 5; 
		System.out.println(a); // 15
		
		int x = 10;
		// x = x % 3;
		// x = 10 % 3;
		// x = 1;
		// x本來是10,現在重新賦值得到1
		x %= 3;
		System.out.println(x); // 1
		
		// 50 = 30; // 常量不能進行賦值,不能寫在賦值運算子的左邊。錯誤寫法!
		
		byte num = 30;
		// num = num + 5;
		// num = byte + int
		// num = int + int
		// num = int
		// num = (byte) int
		num += 5;
		System.out.println(num); // 35
	}
}

7.3、比較運算子

比較運算子:
大於: >
小於: <
大於等於: >=
小於等於: <=
相等: == 【兩個等號連寫才是相等,一個等號代表的是賦值】
不相等: !=

注意事項:

  1. 比較運算子的結果一定是一個boolean值,成立就是true,不成立就是false
  2. 如果進行多次判斷,不能連著寫。
    數學當中的寫法,例如:1 < x < 3
    程式當中【不允許】這種寫法。
public class Demo08Operator {
	public static void main(String[] args) {
		System.out.println(10 > 5); // true
		int num1 = 10;
		int num2 = 12;
		System.out.println(num1 < num2); // true
		System.out.println(num2 >= 100); // false
		System.out.println(num2 <= 100); // true
		System.out.println(num2 <= 12); // true
		System.out.println("===============");
		
		System.out.println(10 == 10); // true
		System.out.println(20 != 25); // true
		System.out.println(20 != 20); // false
		
		int x = 2;
		// System.out.println(1 < x < 3); // 錯誤寫法!編譯報錯!不能連著寫。
	}
}

7.4、邏輯運算子

與(並且) && 全都是true,才是true;否則就是false
或(或者) || 至少一個是true,就是true;全都是false,才是false
非(取反) ! 本來是true,變成false;本來是false,變成true

與“&&”,或“||”,具有短路效果:如果根據左邊已經可以判斷得到最終結果,那麼右邊的程式碼將不再執行,從而節省一定的效能。

注意事項:

  1. 邏輯運算子只能用於boolean值。
  2. 與、或需要左右各自有一個boolean值,但是取反只要有唯一的一個boolean值即可。
  3. 與、或兩種運算子,如果有多個條件,可以連續寫。
    兩個條件:條件A && 條件B
    多個條件:條件A && 條件B && 條件C

TIPS:
對於1 < x < 3的情況,應該拆成兩個部分,然後使用與運算子連線起來:
int x = 2;
1 < x && x < 3

public class Demo09Logic {
	public static void main(String[] args) {
		System.out.println(true && false); // false
		// true && true --> true
		System.out.println(3 < 4 && 10 > 5); // true
		System.out.println("============");
		
		System.out.println(true || false); // true
		System.out.println(true || true); // true
		System.out.println(false || false); // false
		System.out.println("============");
		
		System.out.println(true); // true
		System.out.println(!true); // false	
		System.out.println("============");
		
		int a = 10;
		// false && ...
		System.out.println(3 > 4 && ++a < 100); // false
		System.out.println(a); // 10
		System.out.println("============");
		
		int b = 20;
		// true || ...
		System.out.println(3 < 4 || ++b < 100); // true
		System.out.println(b); // 20
	}
}

7.5、三元運算子

一元運算子:只需要一個數據就可以進行操作的運算子。例如:取反!、自增++、自減--
二元運算子:需要兩個資料才可以進行操作的運算子。例如:加法+、賦值=
三元運算子:需要三個資料才可以進行操作的運算子。

格式:
資料型別 變數名稱 = 條件判斷 ? 表示式A : 表示式B;

流程:
首先判斷條件是否成立:
如果成立為true,那麼將表示式A的值賦值給左側的變數;
如果不成立為false,那麼將表示式B的值賦值給左側的變數;
二者選其一。

注意事項:

  1. 必須同時保證表示式A和表示式B都符合左側資料型別的要求。
  2. 三元運算子的結果必須被使用。
public class Demo10Operator {
	public static void main(String[] args) {
		int a = 10;
		int b = 20;
		
		// 資料型別 變數名稱 = 條件判斷 ? 表示式A : 表示式B;
		// 判斷a > b是否成立,如果成立將a的值賦值給max;如果不成立將b的值賦值給max。二者選其一
		int max = a > b ? a : b; // 最大值的變數
		System.out.println("最大值:" + max); // 20
		
		// int result = 3 > 4 ? 2.5 : 10; // 錯誤寫法!
		
		System.out.println(a > b ? a : b); // 正確寫法!
		
		// a > b ? a : b; // 錯誤寫法!
	}
}

8、方法入門

8.1、概述

我們在學習運算子的時候,都為每個運算子單獨的建立一個新的類和main方法,我們會發現這樣編寫程式碼非常的繁瑣,而且重複的程式碼過多。能否避免這些重複的程式碼呢,就需要使用方法來實現。

  • 方法:就是將一個功能抽取出來,把程式碼單獨定義在一個大括號內,形成一個單獨的功能。

當我們需要這個功能的時候,就可以去呼叫。這樣即實現了程式碼的複用性,也解決了程式碼冗餘的現象。

8.2、方法的定義
  • 定義格式
修飾符 返回值型別 方法名 (引數列表){
程式碼...
return ;
}

8.3、方法的呼叫

方法在定義完畢後,方法不會自己執行,必須被呼叫才能執行,我們可以在主方法main中來呼叫我們自己定義好的方法。在主方法中,直接寫要呼叫的方法名字就可以呼叫了。

public static void main(String[] args) {
//呼叫定義的方法method
method();
}
//定義方法,被main方法呼叫
public static void method() {
System.out.println("自己定義的方法,需要被main呼叫執行");
}

8.4、注意事項
  • 方法定義注意事項:

    1、方法必須定義在一類中方法外

    2、方法不能定義在另一個方法的裡面

9、流程控制

9.1、概述

在一個程式執行的過程中,各條語句的執行順序對程式的結果是有直接影響的。也就是說,程式的流程對執行結果有直接的影響。所以,我們必須清楚每條語句的執行流程。而且,很多時候我們要通過控制語句的執行順序來實現我們要完成的功能。

9.2、順序結構
public class Demo01Sequence {
	public static void main(String[] args) {
		System.out.println("今天天氣不錯");
		System.out.println("挺風和日麗的");
		System.out.println("我們下午沒課");
		System.out.println("這的確挺爽的");
	}
}

9.3、判定語句
9.3.1、判斷語句 if

格式:

if(關係表示式){
語句體;
}

例子:

public class Demo02If {
	public static void main(String[] args) {
		System.out.println("今天天氣不錯,正在壓馬路……突然發現一個快樂的地方:網咖");
		int age = 19;
		if (age >= 18) {
			System.out.println("進入網咖,開始high!");
			System.out.println("遇到了一群豬隊友,開始罵街。");
			System.out.println("感覺不爽,結賬走人。");
		}
		System.out.println("回家吃飯");
	}
}

9.3.2、判斷語句 if...else...

格式

if(關係表示式) {
語句體1;
}else {
語句體2;
}

例子:

// 標準的if-else語句
public class Demo03IfElse {
	public static void main(String[] args) {
		int num = 666;
		
		if (num % 2 == 0) { // 如果除以2能夠餘數為0,說明是偶數
			System.out.println("偶數");
		} else {
			System.out.println("奇數");
		}
	}
}

9.3.3、判斷語句 if...else if ....else..

格式

if (判斷條件1) {
執行語句1;
} else if (判斷條件2) {
執行語句2;
}
...
}else if (判斷條件n) {
執行語句n;
} else {
執行語句n+1;
}

例子

// x和y的關係滿足如下:
// 如果x >= 3,那麼y = 2x + 1;
// 如果-1 < x < 3,那麼y = 2x;
// 如果x <= -1,那麼y = 2x – 1;
public class Demo04IfElseExt {
	public static void main(String[] args) {
		int x = -10;
		int y;
		if (x >= 3) {
			y = 2 * x + 1;
		} else if (-1 < x && x < 3) {
			y = 2 * x;
		} else {
			y = 2 * x - 1;
		}
		System.out.println("結果是:" + y);
	}
}

9.4、選擇語句
9.4.1、switch語句

語法:

switch(表示式) {
case 常量值1:
語句體1;
break;
case 常量值2:
語句體2;
break;
...
default:
語句體n+1;
break;

例子:

public class Demo07Switch {
	public static void main(String[] args) {
		int num = 10;
		
		switch (num) {
			case 1:
				System.out.println("星期一");
				break;
			case 2:
				System.out.println("星期二");
				break;
			case 3:
				System.out.println("星期三");
				break;
			case 4:
				System.out.println("星期四");
				break;
			case 5:
				System.out.println("星期五");
				break;
			case 6:
				System.out.println("星期六");
				break;
			case 7:
				System.out.println("星期日");
				break;
			default:
				System.out.println("資料不合理");
				break; // 最後一個break語句可以省略,但是強烈推薦不要省略
		}
	}
}

switch語句中,表示式的資料型別,可以是byte,short,int,char,enum(列舉),JDK7後可以接收字串。

  • case的穿透性

在switch語句中,如果case的後面不寫break,將出現穿透現象,也就是不會在判斷下一個case的值,直接向後執行,直到遇到break,或者整體switch結束。

public class Demo08SwitchNotice {
	public static void main(String[] args) {
		int num = 2;
		switch (num) {
			case 1:
				System.out.println("你好");
	
			case 2:
				System.out.println("我好");

			case 3:
				System.out.println("大家好");
	
			default:
				System.out.println("他好,我也好。");
				break;
		} // switch
	}
}

像這種沒有break的,就會一直直接到最後一條語句;
switch語句使用的注意事項:

  1. 多個case後面的數值不可以重複。

  2. switch後面小括號當中只能是下列資料型別:
    基本資料型別:byte/short/char/int
    引用資料型別:String字串、enum列舉

  3. switch語句格式可以很靈活:前後順序可以顛倒,而且break語句還可以省略。
    “匹配哪一個case就從哪一個位置向下執行,直到遇到了break或者整體結束為止。”

9.5、迴圈語句

迴圈概述

迴圈語句可以在滿足迴圈條件的情況下,反覆執行某一段程式碼,這段被重複執行的程式碼被稱為迴圈體語句,當反覆執行這個迴圈體時,需要在合適的時候把迴圈判斷條件修改為false,從而結束迴圈,否則迴圈將一直執行下去,形成死迴圈。

9.5.1、for語句

語法:

for(初始化表示式①; 布林表示式②; 步進表示式④){
迴圈體③
}

例子:

/*
迴圈結構的基本組成部分,一般可以分成四部分:

1. 初始化語句:在迴圈開始最初執行,而且只做唯一一次。
2. 條件判斷:如果成立,則迴圈繼續;如果不成立,則迴圈退出。
3. 迴圈體:重複要做的事情內容,若干行語句。
4. 步進語句:每次迴圈之後都要進行的掃尾工作,每次迴圈結束之後都要執行一次。
*/
public class Demo09For {
	public static void main(String[] args) {
		for (int i = 1; i <= 100; i++) {
			System.out.println("我錯啦!原諒我吧!" + i);
		}
		System.out.println("程式停止");
	}
}

9.5.2、while語句

語法:

初始化表示式①
while(布林表示式②){
迴圈體③
步進表示式④
}

例子:

while迴圈有一個標準格式,還有一個擴充套件格式。

標準格式:
while (條件判斷) {
	迴圈體
}

擴充套件格式:

初始化語句;
while (條件判斷) {
	迴圈體;
	步進語句;
}
*/
public class Demo10While {
	public static void main(String[] args) {
		for (int i = 1; i <= 10; i++) {
			System.out.println("我錯啦!" + i);
		}
		System.out.println("=================");
		
		int i = 1; // 1. 初始化語句
		while (i <= 10) { // 2. 條件判斷
			System.out.println("我錯啦!" + i); // 3. 迴圈體
			i++; // 4. 步進語句
		}
	}
}

9.5.3、do...while..語句

語法:

初始化表示式①
do{
迴圈體③
步進表示式④
}while(布林表示式②);

例子:

do-while迴圈的標準格式:

do {
	迴圈體
} while (條件判斷);

擴充套件格式:

初始化語句
do {
	迴圈體
	步進語句
} while (條件判斷);
*/
public class Demo11DoWhile {
	public static void main(String[] args) {
		for (int i = 1; i <= 10; i++) {
			System.out.println("原諒你啦!起來吧!地上怪涼!" + i);
		}
		System.out.println("===============");
		
		int i = 1; // 1. 初始化語句
		do {
			System.out.println("原諒你啦!起來吧!地上怪涼!" + i); // 3. 迴圈體
			i++; // 4. 步進語句
		} while (i <= 10); // 2. 條件判斷
	}
}

三種迴圈的區別。

  1. 如果條件判斷從來沒有滿足過,那麼for迴圈和while迴圈將會執行0次,但是do-while迴圈會執行至少一次。
  2. for迴圈的變數在小括號當中定義,只有迴圈內部才可以使用。while迴圈和do-while迴圈初始化語句本來就在外面,所以出來迴圈之後還可以繼續使用。
9.6、跳出語句

break關鍵字的用法有常見的兩種:

  1. 可以用在switch語句當中,一旦執行,整個switch語句立刻結束。
  2. 還可以用在迴圈語句當中,一旦執行,整個迴圈語句立刻結束。打斷迴圈。

關於迴圈的選擇,有一個小建議:
凡是次數確定的場景多用for迴圈;否則多用while迴圈。

public class Demo14Break {
	public static void main(String[] args) {
		for (int i = 1; i <= 10; i++) {
			// 如果希望從第4次開始,後續全都不要了,就要打斷迴圈
			if (i == 4) { // 如果當前是第4次
				break; // 那麼就打斷整個迴圈
			}
			System.out.println("Hello" + i);
		}
	}
}

continue

另一種迴圈控制語句是continue關鍵字。
一旦執行,立刻跳過當前次迴圈剩餘內容,馬上開始下一次迴圈。

public class Demo15Continue {
	public static void main(String[] args) {
		for (int i = 1; i <= 10; i++) {
			if (i == 4) { // 如果當前是第4層
				continue; // 那麼跳過當前次迴圈,馬上開始下一次(第5層)
			}
			System.out.println(i + "層到了。");
		}
	}
}

9.7、擴充套件知識
9.7.1、死迴圈:

也就是迴圈中的條件永遠為true,死迴圈的是永不結束的迴圈。例如:while(true){}。

應用:

在後期的開發中,會出現使用死迴圈的場景,例如:我們需要讀取使用者輸入的輸入,但是使用者輸入多少資料我們並不清楚,也只能使用死迴圈,當用戶不想輸入資料了,就可以結束迴圈了,如何去結束一個死迴圈呢,就需要使用到跳出語句了。

9.7.8、巢狀迴圈

所謂巢狀迴圈,是指一個迴圈的迴圈體是另一個迴圈。比如for迴圈裡面還有一個for迴圈,就是巢狀迴圈。總共的迴圈次數=外迴圈次數*內迴圈次數

格式:

for(初始化表示式①; 迴圈條件②; 步進表示式⑦) {
for(初始化表示式③; 迴圈條件④; 步進表示式⑥) {
執行語句⑤;
	}
}

巢狀迴圈執行流程 :

​ 執行順序:①②③④⑤⑥>④⑤⑥>⑦②③④⑤⑥>④⑤⑥ ;

​ 外迴圈一次,內迴圈多次 ;

​ 比如跳繩:一共跳5組,每組跳10個。5組就是外迴圈,10個就是內迴圈;

10、方法詳細

10.1、回顧--方法的定義和呼叫

前面的課程中,使用過巢狀迴圈輸出矩形,控制檯打印出矩形就可以了,因此將方法定義為 void ,沒有返回值。在主方法 main 中直接被呼叫。

呼叫格式:
方法名稱();

注意事項:

  1. 方法定義的先後順序無所謂。
  2. 方法定義必須是挨著的,不能在一個方法的內部定義另外一個方法。
  3. 方法定義之後,自己不會執行的;如果希望執行,一定要進行方法的呼叫。
public class Demo01Method {

    public static void main(String[] args) {
        printMethod();
    }

    public static void printMethod() {
        for (int j = 0; j < 5; j++) {
            for (int i = 0; i < 20; i++) {
                System.out.print("*");
            }
            System.out.println();
        }
    }

}

10.2、定義方法的格式詳解
修飾符 返回值型別 方法名(引數列表){
//程式碼省略...
return 結果;
}

修飾符:現階段的固定寫法,public static
返回值型別:也就是方法最終產生的資料結果是什麼型別
方法名稱:方法的名字,規則和變數一樣,小駝峰
引數型別:進入方法的資料是什麼型別
引數名稱:進入方法的資料對應的變數名稱
PS:引數如果有多個,使用逗號進行分隔
方法體:方法需要做的事情,若干行程式碼
return:兩個作用,第一停止當前方法,第二將後面的返回值還給呼叫處
返回值:也就是方法執行後最終產生的資料結果

10.3、呼叫方法的流程圖解

10.4、無參和有參

有引數:小括號當中有內容,當一個方法需要一些資料條件,才能完成任務的時候,就是有引數。
例如兩個數字相加,必須知道兩個數字是各自多少,才能相加。

無引數:小括號當中留空。一個方法不需要任何資料條件,自己就能獨立完成任務,就是無引數。
例如定義一個方法,列印固定10次HelloWorld。

public class Demo03MethodParam {

    public static void main(String[] args) {
        method1(10, 20);
        System.out.println("==============");
        method2();
    }

    // 兩個數字相乘,做乘法,必須知道兩個數字各自是多少,否則無法進行計算
    // 有引數
    public static void method1(int a, int b) {
        int result = a * b;
        System.out.println("結果是:" + result);
    }

    // 例如列印輸出固定10次文字字串
    public static void method2() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Hello, World!" + i);
        }
    }

}


10.5、定義方法的注意事項

定義位置,類中方法外面。

返回值型別,必須要和 return 語句返回的型別相同,否則編譯失敗 。

//	返回值型別要求是int
public static int getSum() {
return 5;// 正確,int型別
return 1.2;// 錯誤,型別不匹配
return true;// 錯誤,型別不匹配
}

  • 不能在 return 後面寫程式碼, return 意味著方法結束,所有後面的程式碼永遠不會執行,屬於無效程式碼。
public static int getSum(int a,int b) {
return a + b;
System.out.println("Hello");// 錯誤,return已經結束,這裡不會執行,無效程式碼
}

  • 賦值呼叫:呼叫方法,在方法前面定義變數,接收方法返回值
public static void main(String[] args) {
int sum = getSum(5,6);
System.out.println(sum);
}
public static int getSum(int a,int b) {
return a + b;
}

  • 輸出語句呼叫:

    在輸出語句中呼叫方法, System.out.println(方法名()) 。

    public static void main(String[] args) {
    System.out.println(getSum(5,6));
    }
    public static int getSum(int a,int b) {
    return a + b;
    }
    
    

    不能用輸出語句呼叫 void 型別的方法。因為方法執行後沒有結果,也就打印不出任何內容

    public static void main(String[] args) {
    System.out.println(printHello());// 錯誤,不能輸出語句呼叫void型別方法
    }
    public static void printHello() {
    System.out.println("Hello");
    }
    
    
10.6、方法過載

對於功能類似的方法來說,因為引數列表不一樣,卻需要記住那麼多不同的方法名稱,太麻煩。

方法的過載(Overload):多個方法的名稱一樣,但是引數列表不一樣。
好處:只需要記住唯一一個方法名稱,就可以實現類似的多個功能。

方法過載與下列因素相關:

  1. 引數個數不同
  2. 引數型別不同
  3. 引數的多型別順序不同

方法過載與下列因素無關:

  1. 與引數的名稱無關
  2. 與方法的返回值型別無關
public class Demo01MethodOverload {

    public static void main(String[] args) {
        /*System.out.println(sumTwo(10, 20)); // 30
        System.out.println(sumThree(10, 20, 30)); // 60
        System.out.println(sumFour(10, 20, 30, 40)); // 100*/

        System.out.println(sum(10, 20)); // 兩個引數的方法
        System.out.println(sum(10, 20, 30)); // 三個引數的方法
        System.out.println(sum(10, 20, 30, 40)); // 四個引數的方法
//        System.out.println(sum(10, 20, 30, 40, 50)); // 找不到任何方法來匹配,所以錯誤!

        sum(10, 20);
    }

    public static int sum(int a, double b) {
        return (int) (a + b);
    }

    public static int sum(double a, int b) {
        return (int) (a + b);
    }

    public static int sum(int a, int b) {
        System.out.println("有2個引數的方法執行!");
        return a + b;
    }

    // 錯誤寫法!與方法的返回值型別無關
//    public static double sum(int a, int b) {
//        return a + b + 0.0;
//    }

    // 錯誤寫法!與引數的名稱無關
//    public static int sum(int x, int y) {
//        return x + y;
//    }

    public static int sum(double a, double b) {
        return (int) (a + b);
    }

    public static int sum(int a, int b, int c) {
        System.out.println("有3個引數的方法執行!");
        return a + b + c;
    }

    public static int sum(int a, int b, int c, int d) {
        System.out.println("有4個引數的方法執行!");
        return a + b + c + d;
    }

}

例題:

public class Demo03OverloadJudge {

    /*
    public static void open(){} // 正確過載
    public static void open(int a){} // 正確過載
    static void open(int a,int b){} // 程式碼錯誤:和第8行衝突
    public static void open(double a,int b){} // 正確過載
    public static void open(int a,double b){} // 程式碼錯誤:和第6行衝突
    public void open(int i,double d){} // 程式碼錯誤:和第5行衝突
    public static void OPEN(){} // 程式碼正確不會報錯,但是並不是有效過載
    public static void open(int i,int j){} // 程式碼錯誤:和第3行衝突
    */
}

11、陣列

11.1、陣列的介紹

陣列的概念:是一種容器,可以同時存放多個數據值。

陣列的特點:

  1. 陣列是一種引用資料型別
  2. 陣列當中的多個數據,型別必須統一
  3. 陣列的長度在程式執行期間不可改變

陣列的初始化:在記憶體當中建立一個數組,並且向其中賦予一些預設值。

兩種常見的初始化方式:

  1. 動態初始化(指定長度)
  2. 靜態初始化(指定內容)

動態初始化陣列的格式:
資料型別[] 陣列名稱 = new 資料型別[陣列長度];

解析含義:
左側資料型別:也就是陣列當中儲存的資料,全都是統一的什麼型別
左側的中括號:代表我是一個數組
左側陣列名稱:給陣列取一個名字
右側的new:代表建立陣列的動作
右側資料型別:必須和左邊的資料型別保持一致
右側中括號的長度:也就是陣列當中,到底可以儲存多少個數據,是一個int數字

public class Demo01Array {

    public static void main(String[] args) {
        // 建立一個數組,裡面可以存放300個int資料
        // 格式:資料型別[] 陣列名稱 = new 資料型別[陣列長度];
        int[] arrayA = new int[300];

        // 建立一個數組,能存放10個double型別的資料
        double[] arrayB = new double[10];

        // 建立一個數組,能存放5個字串
        String[] arrayC = new String[5];
    }

}

11.2、陣列的初始化

動態初始化(指定長度):在建立陣列的時候,直接指定陣列當中的資料元素個數。
靜態初始化(指定內容):在建立陣列的時候,不直接指定資料個數多少,而是直接將具體的資料內容進行指定。

靜態初始化基本格式:
資料型別[] 陣列名稱 = new 資料型別[] { 元素1, 元素2, ... };

省略格式:
資料型別[] 陣列名稱 = { 元素1, 元素2, ... };

注意事項:

  1. 靜態初始化沒有直接指定長度,但是仍然會自動推算得到長度。
  2. 靜態初始化標準格式可以拆分成為兩個步驟。
  3. 動態初始化也可以拆分成為兩個步驟。
  4. 靜態初始化一旦使用省略格式,就不能拆分成為兩個步驟了。

使用建議:
如果不確定陣列當中的具體內容,用動態初始化;否則,已經確定了具體的內容,用靜態初始化。

public class Demo03Array {

    public static void main(String[] args) {
        // 省略格式的靜態初始化
        int[] arrayA = { 10, 20, 30 };

        // 靜態初始化的標準格式,可以拆分成為兩個步驟
        int[] arrayB;
        arrayB = new int[] { 11, 21, 31 };

        // 動態初始化也可以拆分成為兩個步驟
        int[] arrayC;
        arrayC = new int[5];

        // 靜態初始化的省略格式,不能拆分成為兩個步驟。
//        int[] arrayD;
//        arrayD = { 10, 20, 30 };
    }

}

動態陣列的使用:

使用動態初始化陣列的時候,其中的元素將會自動擁有一個預設值。規則如下:
如果是整數型別,那麼預設為0;
如果是浮點型別,那麼預設為0.0;
如果是字元型別,那麼預設為'\u0000';
如果是布林型別,那麼預設為false;
如果是引用型別,那麼預設為null。

注意事項:
靜態初始化其實也有預設值的過程,只不過系統自動馬上將預設值替換成為了大括號當中的具體數值。

public class Demo05ArrayUse {

    public static void main(String[] args) {
        // 動態初始化一個數組
        int[] array = new int[3];

        System.out.println(array); // 記憶體地址值
        System.out.println(array[0]); // 0
        System.out.println(array[1]); // 0
        System.out.println(array[2]); // 0
        System.out.println("=================");

        // 將資料123賦值交給陣列array當中的1號元素
        array[1] = 123;
        System.out.println(array[0]); // 0
        System.out.println(array[1]); // 123
        System.out.println(array[2]); // 0
    }

}


11.3、陣列的訪問

索引: 每一個儲存到陣列的元素,都會自動的擁有一個編號,從0開始,這個自動編號稱為陣列索引(index),可以通過陣列的索引訪問到陣列中的元素。

格式:

陣列名[索引]

陣列的長度屬性: 每個陣列都具有長度,而且是固定的,Java中賦予了陣列的一個屬性,可以獲取到陣列的長度,語句為: 陣列名.length ,屬性length的執行結果是陣列的長度,int型別結果。由次可以推斷出,陣列的最大索引值為 陣列名.length-1 。

public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5};
//列印陣列的屬性,輸出結果是5
System.out.println(arr.length);
}

索引訪問陣列中的元素:

​ 1、陣列名[索引]=數值,為陣列中的元素賦值

​ 2、變數=陣列名[索引],獲取出陣列中的元素

public static void main(String[] args) {
//定義儲存int型別陣列,賦值元素1,2,3,4,5
int[] arr = {1,2,3,4,5};
//為0索引元素賦值為6
arr[0] = 6;
//獲取陣列0索引上的元素
int i = arr[0];
System.out.println(i);
//直接輸出陣列0索引元素
System.out.println(arr[0]);
}

11.4、陣列原理記憶體圖

11.3.1、記憶體概述

記憶體是計算機中的重要原件,臨時儲存區域,作用是執行程式。我們編寫的程式是存放在硬碟中的,在硬碟中的程式是不會執行的,必須放進記憶體中才能執行,執行完畢後會清空記憶體。

Java虛擬機器要執行程式,必須要對記憶體進行空間的分配和管理。

11.2.2、Java虛擬機器記憶體劃分

為了提高運算效率,就對空間進行了不同區域的劃分,因為每一片區域都有特定的處理資料方式和記憶體管理方式 。

11.3.3、陣列在記憶體中的儲存

只有一個數組的記憶體圖

兩個陣列的記憶體圖:

說明,可以看到arrayA存的是陣列的首個地址,特別注意的是陣列new出來之後,它的長度是不能改變的,否則報錯;

將一個數組賦值給另一個數組的記憶體圖:

一個數組名建立兩次的記憶體圖:

11.4、陣列的常見操作
11.4.1、陣列越界異常
public static void main(String[] args) {
int[] arr = {1,2,3};
System.out.println(arr[3]);//超出陣列的範圍
}

執行結果:

11.4.2、陣列空指標異常
public static void main(String[] args) {
int[] arr = {1,2,3};
arr = null;
System.out.println(arr[0]);
}

執行結果:

arr = null 這行程式碼,意味著變數arr將不會在儲存陣列的記憶體地址,也就不允許再運算元組了,因此執行的時候會丟擲 NullPointerException 空指標異常。在開發中,陣列的越界異常是不能出現的,一旦出現了,就必須要修改我們編寫的程式碼。

11.5、陣列遍歷【重點】
  • 陣列遍歷: 就是將陣列中的每個元素分別獲取出來,就是遍歷。遍歷也是陣列操作中的基石。
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
System.out.println(arr[3]);
System.out.println(arr[4]);
}

以上程式碼是可以將陣列中每個元素全部遍歷出來,但是如果陣列元素非常多,這種寫法肯定不行,因此我們需要改造成迴圈的寫法。陣列的索引是 0 到 lenght-1 ,可以作為迴圈的條件出現。

public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
	}
}

11.6、陣列獲取最大值元素
public class Demo05ArrayMax {

    public static void main(String[] args) {
        int[] array = { 5, 15, 30, 20, 10000, 30, 35 };

        int max = array[0]; // 比武擂臺
        for (int i = 1; i < array.length; i++) {
            // 如果當前元素,比max更大,則換人
            if (array[i] > max) {
                max = array[i];
            }
        }
        // 誰最後最厲害,就能在max當中留下誰的戰鬥力
        System.out.println("最大值:" + max);
    }

}

11.7、陣列反轉
public class Demo07ArrayReverse {

    public static void main(String[] args) {
        int[] array = { 10, 20, 30, 40, 50 };

        // 遍歷列印陣列本來的樣子
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
        System.out.println("============");

        /*
        初始化語句:int min = 0, max = array.length - 1
        條件判斷:min < max
        步進表示式:min++, max--
        迴圈體:用第三個變數倒手
         */
        for (int min = 0, max = array.length - 1; min < max; min++, max--) {
            int temp = array[min];
            array[min] = array[max];
            array[max] = temp;
        }

        // 再次列印遍歷輸出陣列後來的樣子
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
    }

}


11.8、陣列作為方法引數和返回值
11.8.1、陣列作為方法引數

以前的方法中我們學習了方法的引數和返回值,但是使用的都是基本資料型別。那麼作為引用型別的陣列能否作為方法的引數進行傳遞呢,當然是可以的。

  • 陣列作為方法引數傳遞,傳遞的引數是陣列記憶體的地址
public static void main(String[] args) {
int[] arr = { 1, 3, 5, 7, 9 };
//呼叫方法,傳遞陣列
printArray(arr);
}
/*
建立方法,方法接收陣列型別的引數
進行陣列的遍歷
*/
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
	}
}

11.8.2、 陣列作為方法返回值
  • 陣列作為方法的返回值,返回的是陣列的記憶體地址
public static void main(String[] args) {
//呼叫方法,接收陣列的返回值
//接收到的是陣列的記憶體地址
int[] arr = getArray();  //呼叫下面的方法
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
	}
}
/*
建立方法,返回值是陣列型別
return返回陣列的地址
*/
public static int[] getArray() {
int[] arr = { 1, 3, 5, 7, 9 };
//返回陣列的地址,返回到呼叫者
return arr;
}


12、面向物件思想

12.1、面向物件思想概述

概述

Java語言是一種面向物件的程式設計語言,而面向物件思想是一種程式設計思想,我們在面向物件思想的指引下,使用Java語言去設計、開發計算機程式。 這裡的物件泛指現實中一切事物,每種事物都具備自己的屬性和行為。面向物件思想就是在計算機程式設計過程中,參照現實中事物,將事物的屬性特徵、行為特徵抽象出來,描述成計算機事件的設計思想。 它區別於面向過程思想,強調的是通過呼叫物件的行為來實現功能,而不是自己一步一步的去操作實現。

面向過程和麵向物件的區別:

  • 面向過程:當需要實現一個功能的時候,每一個具體的步驟都要親力親為,詳細處理每一個細節。
  • 面向物件:面向物件:當需要實現一個功能的時候,不關心具體的步驟,而是找一個已經具有該功能的人,來幫我做事兒。

特點:

面向物件思想是一種更符合我們思考習慣的思想,它可以將複雜的事情簡單化,並將我們從執行者變成了指揮者。面向物件的語言中,包含了三大基本特徵,即封裝、繼承和多型。

12.2、類和物件

什麼是類:

類:是一組相關屬性和行為的集合。可以看成是一類事物的模板,使用事物的屬性特徵和行為特徵來描述該類事物

​ 屬性:就是該事物的狀態資訊。

​ 行為:就是該事物能夠做什麼。

什麼是物件:

物件:是一類事物的具體體現。物件是類的一個例項(物件並不是找個女朋友),必然具備該類事物的屬性和行為。

類和物件的關係:

  • 類是對一類事物的描述,是抽象的

  • 物件是一類事物的例項,是具體的

  • 類是物件的模板,物件是類的實體。

12.3、類的定義

格式

public class ClassName {
//成員變數
//成員方法
}

  • 定義類:就是定義類的成員,包括成員變數和成員方法。

  • 成員變數:和以前定義變數幾乎是一樣的。只不過位置發生了改變。在類中,方法外。

  • 成員方法:和以前定義方法幾乎是一樣的。只不過把static去掉,static的作用在面向物件後面課程中再詳細講解。

注意事項:
1. 成員變數是直接定義在類當中的,在方法外邊。
2. 成員方法不要寫static關鍵字。
public class Student {

    // 成員變數
    String name; // 姓名
    int age; // 姓名

    // 成員方法
    public void eat() {
        System.out.println("吃飯飯!");
    }

    public void sleep() {
        System.out.println("睡覺覺!");
    }

    public void study() {
        System.out.println("學習!");
    }

}

12.4、物件的使用

格式:

類名 物件名 = new 類名();

使用物件訪問類中的成員:

物件名.成員變數;
物件名.成員方法();

例子:

public class Demo02Student {

    public static void main(String[] args) {
        // 1. 導包。
        // 我需要使用的Student類,和我自己Demo02Student位於同一個包下,所以省略導包語句不寫

        // 2. 建立,格式:
        // 類名稱 物件名 = new 類名稱();
        // 根據Student類,建立了一個名為stu的物件
        Student stu = new Student();

        // 3. 使用其中的成員變數,格式:
        // 物件名.成員變數名
        System.out.println(stu.name); // null
        System.out.println(stu.age); // 0
        System.out.println("=============");

        // 改變物件當中的成員變數數值內容
        // 將右側的字串,賦值交給stu物件當中的name成員變數
        stu.name = "趙麗穎";
        stu.age = 18;
        System.out.println(stu.name); // 趙麗穎
        System.out.println(stu.age); // 18
        System.out.println("=============");

        // 4. 使用物件的成員方法,格式:
        // 物件名.成員方法名()
        stu.eat();
        stu.sleep();
        stu.study();
    }

}

成員變數的預設值

12.5、物件記憶體圖
  • 一個物件,呼叫一個方法記憶體圖

  • 兩個物件使用同一個方法的記憶體圖

  • 兩個引用指向同一個物件的記憶體圖

說明:

首先成員變數和成員方法都是在方法區中,其中成員方法是有一個儲存地址,成員變數在被new後會有賦值(預設),然後對應一個儲存地址,而成員方法還是用方法區的地址,當棧中用到屬性或方法時,就會去找這些地址;

  • 使用物件型別作為方法引數:

  • 使用物件型別作為方法的返回值

12.6、成員變數和區域性變數區別

變數根據定義位置的不同,我們給變數起了不同的名字。如下圖所示:

區域性變數和成員變數

  1. 定義的位置不一樣【重點】
    區域性變數:在方法的內部
    成員變數:在方法的外部,直接寫在類當中

  2. 作用範圍不一樣【重點】
    區域性變數:只有方法當中才可以使用,出了方法就不能再用
    成員變數:整個類全都可以通用。

  3. 預設值不一樣【重點】
    區域性變數:沒有預設值,如果要想使用,必須手動進行賦值
    成員變數:如果沒有賦值,會有預設值,規則和陣列一樣

  4. 記憶體的位置不一樣(瞭解)
    區域性變數:位於棧記憶體
    成員變數:位於堆記憶體

  5. 生命週期不一樣(瞭解)
    區域性變數:隨著方法進棧而誕生,隨著方法出棧而消失

public class Demo01VariableDifference {

    String name; // 成員變數

    public void methodA() {
        int num = 20; // 區域性變數
        System.out.println(num);
        System.out.println(name);
    }

    public void methodB(int param) { // 方法的引數就是區域性變數
        // 引數在方法呼叫的時候,必然會被賦值的。
        System.out.println(param);

        int age; // 區域性變數
//        System.out.println(age); // 沒賦值不能用

//        System.out.println(num); // 錯誤寫法!
        System.out.println(name);
    }

}

13、封裝

13.1、封裝概述
概述

面向物件程式語言是對客觀世界的模擬,客觀世界裡成員變數都是隱藏在物件內部的,外界無法直接操作和修改。封裝可以被認為是一個保護屏障,防止該類的程式碼和資料被其他類隨意訪問。要訪問該類的資料,必須通過指定的方式。適當的封裝可以讓程式碼更容易理解與維護,也加強了程式碼的安全性。

原則:

將屬性隱藏起來,若需要訪問某個屬性,提供公共方法對其訪問。

13.2、封裝的步驟
  1. 使用 private 關鍵字來修飾成員變數。

2.對需要訪問的成員變數,提供對應的一對 getXxx 方法 、 setXxx 方法。

13.3、private關鍵字
  1. private是一個許可權修飾符,代表最小許可權。

  2. 可以修飾成員變數和成員方法。

  3. 被private修飾後的成員變數和成員方法,只在本類中才能訪問。

private的使用格式:

private 資料型別 變數名 ;

  1. 使用 private 修飾成員變數,程式碼如下:
public class Student {
private String name;
private int age;
}

  1. 提供 getXxx 方法 / setXxx 方法,可以訪問成員變數,程式碼如下:
public class Student {
    private String name;
    private int age;
    public void setName(String n) {
        name = n;
    }
    public String getName() {
        return name;
    }
    public void setAge(int a) {
        age = a;
    }
    public int getAge() {
        return age;
    }
}


這裡要注意的是:Boolean的Getter方法一定要寫成isXxx的形式,而setXxx規則不變。
    eg:

    public void setMale(boolean b) {
        male = b;
    }

    public boolean isMale() {
        return male;
    }


13.4、this關鍵字

this的含義:

this代表所在類的當前物件的引用(地址值),即物件自己的引用。

【記住 :方法被哪個物件呼叫,方法中的this就代表那個物件。即誰在呼叫,this就代表誰。】

this使用格式:

this.成員變數名;

問題:

public class Person {

    String name; // 我自己的名字

    // 引數name是對方的名字
    // 成員變數name是自己的名字
    public void sayHello(String name) {
       // System.out.println(name + ",你好。我是" + name);
        System.out.println(name + ",你好。我是" + this.name);
        System.out.println(this);
    }

}
//像方法中的引數和類中的屬性同名,因此name是方法中裡面的遵循就近原則,所以都是隻用傳過來的name,如果我們想在方法中用到類中的name時,就在那麼前加this,這個this指當前類;

public class Demo01Person {

    public static void main(String[] args) {
        Person person = new Person();
        // 設定我自己的名字
        person.name = "王健林";
        person.sayHello("王思聰");

        System.out.println(person); // 地址值
    }

}

結果:都是王健林

13.4、構造方法

當一個物件被建立時候,構造方法用來初始化該物件,給物件的成員變數賦初始值。

【無論你與否自定義構造方法,所有的類都有構造方法,因為Java自動提供了一個無引數構造方法,一旦自己定義了構造方法,Java自動提供的預設無引數構造方法就會失效。】

構造方法的定義格式

修飾符 構造方法名(引數列表){
//	方法體
}

構造方法的寫法上,方法名與它所在的類名相同。它沒有返回值,所以不需要返回值型別,甚至不需要void。使用構造方法後,程式碼如下:

public class Student {
private String name;
private int age;
//	無引數構造方法 public Student() {}
//	有引數構造方法
public Student(String name,int age) {
this.name = name;
this.age = age;
	}
}

注意事項:

  1. 如果你不提供構造方法,系統會給出無引數構造方法。

  2. 如果你提供了構造方法,系統將不再提供無引數構造方法。

  3. 構造方法是可以過載的,既可以定義引數,也可以不定義引數。

13.5、標準程式碼——JavaBean

JavaBean 是 Java語言編寫類的一種標準規範。符合 JavaBean 的類,要求類必須是具體的和公共的,並且具有無引數的構造方法,提供用來操作成員變數的 set 和 get 方法。

public class ClassName{
//成員變數
//構造方法
//無參構造方法【必須】
//有參構造方法【建議】
//成員方法
//getXxx()
//setXxx()
}

編寫符合 JavaBean 規範的類,以學生類為例,標準程式碼如下:

public class Student {
//成員變數
private String name;
private int age;
//構造方法
public Student() {}
public Student(String name,int age) {
this.name = name;
this.age = age;
}
//成員方法
publicvoid setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
publicvoid setAge(int age) {
this.age = age;
}
publicint getAge() {
return age;
	}
}

14、API

概述:

API(Application Programming Interface),應用程式程式設計介面。Java API是一本程式設計師的 字典 ,是JDK中提供給我們使用的類的說明文件。這些類將底層的程式碼實現封裝了起來,我們不需要關心這些類是如何實現的,只需要學習這些類如何使用即可。所以我們可以通過查詢API的方式,來學習Java提供的類,並得知如何使用它們。

15、Scanner類

瞭解了API的使用方式,我們通過Scanner類,熟悉一下查詢API,並使用類的步驟。

15.1、什麼是scanner類

一個可以解析基本型別和字串的簡單文字掃描器。 例如,以下程式碼使使用者能夠從 System.in 中讀取一個數:

Scanner sc = new Scanner(System.in);
int i = sc.nextInt();

備註:System.in 系統輸入指的是通過鍵盤錄入資料。

15.2、引用型別使用步驟

導包

使用import關鍵字導包,在類的所有程式碼之前導包,引入要使用的型別,java.lang包下的所有類無需匯入。 格式:

import 包名.類名;

建立物件:

資料型別	變數名 =  new 資料型別(引數列表);

呼叫方法:

變數名.方法名();

例子:

import java.util.Scanner; // 1. 導包

/*
Scanner類的功能:可以實現鍵盤輸入資料,到程式當中。

引用型別的一般使用步驟:

1. 導包
import 包路徑.類名稱;
如果需要使用的目標類,和當前類位於同一個包下,則可以省略導包語句不寫。
只有java.lang包下的內容不需要導包,其他的包都需要import語句。

2. 建立
類名稱 物件名 = new 類名稱();

3. 使用
物件名.成員方法名()

獲取鍵盤輸入的一個int數字:int num = sc.nextInt();
獲取鍵盤輸入的一個字串:String str = sc.next();
 */
public class Demo01Scanner {

    public static void main(String[] args) {
        // 2. 建立
        // 備註:System.in代表從鍵盤進行輸入
        Scanner sc = new Scanner(System.in);

        // 3. 獲取鍵盤輸入的int數字
        int num = sc.nextInt();
        System.out.println("輸入的int數字是:" + num);

        // 4. 獲取鍵盤輸入的字串
        String str = sc.next();
        System.out.println("輸入的字串是:" + str);
    }

}

16、匿名物件【瞭解】

概念:

建立物件時,只有建立物件的語句,卻沒有把物件地址值賦值給某個變數。雖然是建立物件的簡化寫法,但是應用場景非常有限。

  • 匿名物件:沒有變數名的物件

格式:

new 類名(引數列表);

舉例:

new Scanner(System.in);

例子:

/*
建立物件的標準格式:
類名稱 物件名 = new 類名稱();

匿名物件就是隻有右邊的物件,沒有左邊的名字和賦值運算子。
new 類名稱();

注意事項:匿名物件只能使用唯一的一次,下次再用不得不再建立一個新物件。
使用建議:如果確定有一個物件只需要使用唯一的一次,就可以用匿名物件。
 */
public class Demo01Anonymous {

    public static void main(String[] args) {
        // 左邊的one就是物件的名字
        Person one = new Person();
        one.name = "高圓圓";
        one.showName(); // 我叫高圓圓
        System.out.println("===============");

        // 匿名物件
        new Person().name = "趙又廷";
        new Person().showName(); // 我叫:null
    }

}


例子2:

public class Demo02Anonymous {

    public static void main(String[] args) {
        // 普通使用方式
//        Scanner sc = new Scanner(System.in);
//        int num = sc.nextInt();

        // 匿名物件的方式
//        int num = new Scanner(System.in).nextInt();
//        System.out.println("輸入的是:" + num);

        // 使用一般寫法傳入引數
//        Scanner sc = new Scanner(System.in);
//        methodParam(sc);

        // 使用匿名物件來進行傳參
//        methodParam(new Scanner(System.in));

        Scanner sc = methodReturn();
        int num = sc.nextInt();
        System.out.println("輸入的是:" + num);
    }

    public static void methodParam(Scanner sc) {
        int num = sc.nextInt();
        System.out.println("輸入的是:" + num);
    }

    public static Scanner methodReturn() {
//        Scanner sc = new Scanner(System.in);
//        return sc;
        return new Scanner(System.in);
    }

}

17、Random類

17.1、什麼是Random類

此類的例項用於生成偽隨機數。

例如,以下程式碼使使用者能夠得到一個隨機數:

Random r = new Random();
int i = r.nextInt();

17.2、Random使用步驟

首先檢視類:

java.util.Random :該類需要 import匯入使後使用。

其次檢視構造方法:

public Random() :建立一個新的隨機數生成器。

最後檢視成員方法

public int nextInt(int n) :返回一個偽隨機數,範圍在 0 (包括)和 指定值 n (不包括)之間的int 值。

例子:

public class Demo01Random {

    public static void main(String[] args) {
        Random r = new Random();

        int num = r.nextInt();
        System.out.println("隨機數是:" + num);
    }

}


題目:

根據int變數n的值,來獲取隨機數字,範圍是[1,n],可以取到1也可以取到n。

思路:

  1. 定義一個int變數n,隨意賦值
  2. 要使用Random:三個步驟,導包、建立、使用
  3. 如果寫10,那麼就是09,然而想要的是110,可以發現:整體+1即可。
  4. 列印隨機數字
public class Demo03Random {

    public static void main(String[] args) {
        int n = 5;
        Random r = new Random();

        for (int i = 0; i < 100; i++) {
            // 本來範圍是[0,n),整體+1之後變成了[1,n+1),也就是[1,n]
            int result = r.nextInt(n) + 1;
            System.out.println(result);
        }

    }

}


題目:
用程式碼模擬猜數字的小遊戲。

思路:

  1. 首先需要產生一個隨機數字,並且一旦產生不再變化。用Random的nextInt方法
  2. 需要鍵盤輸入,所以用到了Scanner
  3. 獲取鍵盤輸入的數字,用Scanner當中的nextInt方法
  4. 已經得到了兩個數字,判斷(if)一下:
    如果太大了,提示太大,並且重試;
    如果太小了,提示太小,並且重試;
    如果猜中了,遊戲結束。
  5. 重試就是再來一次,迴圈次數不確定,用while(true)。
public class Demo04RandomGame {

    public static void main(String[] args) {
        Random r = new Random();
        int randomNum = r.nextInt(100) + 1; // [1,100]
        Scanner sc = new Scanner(System.in);

        while (true) {
            System.out.println("請輸入你猜測的數字:");
            int guessNum = sc.nextInt(); // 鍵盤輸入猜測的數字

            if (guessNum > randomNum) {
                System.out.println("太大了,請重試。");
            } else if (guessNum < randomNum) {
                System.out.println("太小了,請重試。");
            } else {
                System.out.println("恭喜你,猜中啦!");
                break; // 如果猜中,不再重試
            }
        }

        System.out.println("遊戲結束。");
    }

18、Arraylist類

18.1、什麼是ArrayList類

java.util.ArrayList 是大小可變的陣列的實現,儲存在內的資料稱為元素。此類提供一些方法來操作內部儲存的元素。 ArrayList 中可不斷新增元素,其大小也自動增長。

18.2、Arraylist類使用步驟
  • 檢視類

java.util.ArrayList :該類需要 import匯入使後使用。

,表示一種指定的資料型別,叫做泛型。 E ,取自Element(元素)的首字母。在出現 E 的地方,我們使用一種引用資料型別將其替換即可,表示我們將儲存哪種引用型別的元素。程式碼如下:

ArrayList<String>,ArrayList<Student>

  • 檢視構造方法
public ArrayList() :構造一個內容為空的集合。

  • 基本格式
ArrayList<String> list = new ArrayList<String>();

  • 在JDK 7後,右側泛型的尖括號之內可以留空,但是<>仍然要寫。簡化格式:

    ArrayList<String> list = new ArrayList<>();
    
    
  • 檢視成員方法

public boolean add(E e) : 將指定的元素新增到此集合的尾部。

引數 E e ,在構造ArrayList物件時, <E> 指定了什麼資料型別,那麼 add(E e) 方法中,只能新增什麼資料型別的物件。

使用ArrayList類,儲存三個字串元素,程式碼如下:

public class Test02StudentArrayList {
public static void main(String[] args) {

    //建立學生陣列
    ArrayList<String> list = new ArrayList<>();

    //建立學生物件
    String s1 = "曹操";
    String s2 = "劉備";
    String s3 = "孫權";

    //列印學生ArrayList集合
    System.out.println(list);

    //把學生物件作為元素新增到集合
    list.add(s1);
    list.add(s2);
    list.add(s3);
    //列印學生ArrayList集合
    System.out.println(list);
	}
}

注意事項:
對於ArrayList集合來說,直接列印得到的不是地址值,而是內容。
如果內容是空,得到的是空的中括號:[]

18.3、常用方法和遍歷

對於元素的操作,基本體現在——增、刪、查。常用的方法有:

public boolean add(E e) :將指定的元素新增到此集合的尾部。

public E remove(int index)  :移除此集合中指定位置上的元素。返回被刪除的元素。

public E get(int index)  :返回此集合中指定位置上的元素。返回獲取的元素。

public int size()  :返回此集合中的元素數。遍歷集合時,可以控制索引範圍,防止越界。


例子:

public class Demo03ArrayListMethod {

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        System.out.println(list); // []

        // 向集合中新增元素:add
        boolean success = list.add("柳巖");
        System.out.println(list); // [柳巖]
        System.out.println("新增的動作是否成功:" + success); // true

        list.add("高圓圓");
        list.add("趙又廷");
        list.add("李小璐");
        list.add("賈乃亮");
        System.out.println(list); // [柳巖, 高圓圓, 趙又廷, 李小璐, 賈乃亮]

        // 從集合中獲取元素:get。索引值從0開始
        String name = list.get(2);
        System.out.println("第2號索引位置:" + name); // 趙又廷

        // 從集合中刪除元素:remove。索引值從0開始。
        String whoRemoved = list.remove(3);
        System.out.println("被刪除的人是:" + whoRemoved); // 李小璐
        System.out.println(list); // [柳巖, 高圓圓, 趙又廷, 賈乃亮]

        // 獲取集合的長度尺寸,也就是其中元素的個數
        int size = list.size();
        System.out.println("集合的長度是:" + size);
    }

}


18.4、儲存基本數型別

ArrayList物件不能儲存基本型別,只能儲存引用型別的資料。類似 不能寫,但是儲存基本資料型別對應的包裝型別是可以的。所以,想要儲存基本型別資料, <> 中的資料型別,必須轉換後才能編寫,轉換寫法如下:

我們發現,只有 Integer 和 Character 需要特殊記憶,其他基本型別只是首字母大寫即可。那麼儲存基本型別資料,程式碼如下:

public class Demo02ArrayListMethod {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
System.out.println(list);
	}
}

從JDK 1.5+開始,支援自動裝箱、自動拆箱。

自動裝箱:基本型別 --> 包裝型別
自動拆箱:包裝型別 --> 基本型別

public class Demo05ArrayListBasic {

    public static void main(String[] args) {
        ArrayList<String> listA = new ArrayList<>();
        // 錯誤寫法!泛型只能是引用型別,不能是基本型別
//        ArrayList<int> listB = new ArrayList<>();

        ArrayList<Integer> listC = new ArrayList<>();
        listC.add(100);
        listC.add(200);
        System.out.println(listC); // [100, 200]

        int num = listC.get(1);   //自動裝箱
        System.out.println("第1號元素是:" + num);
    }

}

題目:
自定義4個學生物件,新增到集合,並遍歷。

思路:

  1. 自定義Student學生類,四個部分。
  2. 建立一個集合,用來儲存學生物件。泛型:
  3. 根據類,建立4個學生物件。
  4. 將4個學生物件新增到集合中:add
  5. 遍歷集合:for、size、get
public class Demo02ArrayListStudent {

    public static void main(String[] args) {
        ArrayList<Student> list = new ArrayList<>();

        Student one = new Student("洪七公", 20);
        Student two = new Student("歐陽鋒", 21);
        Student three = new Student("黃藥師", 22);
        Student four = new Student("段智興", 23);

        list.add(one);
        list.add(two);
        list.add(three);
        list.add(four);

        // 遍歷集合
        for (int i = 0; i < list.size(); i++) {
            Student stu = list.get(i);
            System.out.println("姓名:" + stu.getName() + ",年齡" + stu.getAge());
        }
    }

}


題目:
用一個大集合存入20個隨機數字,然後篩選其中的偶數元素,放到小集合當中。
要求使用自定義的方法來實現篩選。

分析:

  1. 需要建立一個大集合,用來儲存int數字:
  2. 隨機數字就用Random nextInt
  3. 迴圈20次,把隨機數字放入大集合:for迴圈、add方法
  4. 定義一個方法,用來進行篩選。
    篩選:根據大集合,篩選符合要求的元素,得到小集合。
    三要素
    返回值型別:ArrayList小集合(裡面元素個數不確定)
    方法名稱:getSmallList
    引數列表:ArrayList大集合(裝著20個隨機數字)
  5. 判斷(if)是偶數:num % 2 == 0
  6. 如果是偶數,就放到小集合當中,否則不放。
public class Demo04ArrayListReturn {

    public static void main(String[] args) {
        ArrayList<Integer> bigList = new ArrayList<>();
        Random r = new Random();
        for (int i = 0; i < 20; i++) {
            int num = r.nextInt(100) + 1; // 1~100
            bigList.add(num);
        }

        ArrayList<Integer> smallList = getSmallList(bigList);

        System.out.println("偶數總共有多少個:" + smallList.size());
        for (int i = 0; i < smallList.size(); i++) {
            System.out.println(smallList.get(i));
        }
    }

    // 這個方法,接收大集合引數,返回小集合結果
    public static ArrayList<Integer> getSmallList(ArrayList<Integer> bigList) {
        // 建立一個小集合,用來裝偶數結果
        ArrayList<Integer> smallList = new ArrayList<>();
        for (int i = 0; i < bigList.size(); i++) {
            int num = bigList.get(i);
            if (num % 2 == 0) {
                smallList.add(num);
            }
        }
        return smallList;
    }

}


19、String類

19.1、String類概述

概述

java.lang.String 類代表字串。Java程式中所有的字串文字(例如 "abc" )都可以被看作是實現此類的例項。

類 String 中包括用於檢查各個字串的方法,比如用於比較字串,搜尋字串,提取子字串以及建立具有翻譯為大寫或小寫的所有字元的字串的副本。

字串的特點:

  1. 字串的內容永不可變。【重點】
  2. 正是因為字串不可改變,所以字串是可以共享使用的。
  3. 字串效果上相當於是char[]字元陣列,但是底層原理是byte[]位元組陣列。

例如:

1、
String s1 = "abc";
s1 += "d";
System.out.println(s1); // "abcd"
//	記憶體中有"abc","abcd"兩個物件,s1從指向"abc",改變指向,指向了"abcd"。

2、
String s1 = "abc";
String s2 = "abc";
//	記憶體中只有一個"abc"物件被建立,同時被s1和s2共享。

3、
例如:
String str = "abc";
相當於:
char data[] = {'a', 'b', 'c'};
String str = new String(data);
//	String底層是靠字元陣列實現的。

建立字串的常見3+1種方式。
三種構造方法:
public String():建立一個空白字串,不含有任何內容。
public String(char[] array):根據字元陣列的內容,來建立對應的字串。
public String(byte[] array):根據位元組陣列的內容,來建立對應的字串。
一種直接建立:
String str = "Hello"; // 右邊直接用雙引號

注意:直接寫上雙引號,就是字串物件。

public class Demo01String {

    public static void main(String[] args) {
        // 使用空參構造
        String str1 = new String(); // 小括號留空,說明字串什麼內容都沒有。
        System.out.println("第1個字串:" + str1);

        // 根據字元陣列建立字串
        char[] charArray = { 'A', 'B', 'C' };
        String str2 = new String(charArray);
        System.out.println("第2個字串:" + str2);

        // 根據位元組陣列建立字串
        byte[] byteArray = { 97, 98, 99 };
        String str3 = new String(byteArray);
        System.out.println("第3個字串:" + str3);

        // 直接建立
        String str4 = "Hello";
        System.out.println("第4個字串:" + str4);
    }

}


字串常量池:程式當中直接寫上的雙引號字串,就在字串常量池中。

對於基本型別來說,是進行數值的比較。
對於引用型別來說,
是進行【地址值】的比較。

public class Demo02StringPool {

    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";

        char[] charArray = {'a', 'b', 'c'};
        String str3 = new String(charArray);

        System.out.println(str1 == str2); // true
        System.out.println(str1 == str3); // false
        System.out.println(str2 == str3); // false
    }

}


字串常量池

19.2、常用的方法

判斷的方法

public boolean equals (Object anObject) :將此字串與指定物件進行比較。

public boolean equalsIgnoreCase (String anotherString) :將此字串與指定物件進行比較,忽略大小寫

程式碼:

public class String_Demo01 {

public static void main(String[] args) {
//	建立字串物件
String s1 = "hello"; String s2 = "hello"; String s3 = "HELLO";
//	boolean equals(Object obj):比較字串的內容是否相同 System.out.println(s1.equals(s2)); // true System.out.println(s1.equals(s3)); // false System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
//boolean equalsIgnoreCase(String str):比較字串的內容是否相同,忽略大小寫
System.out.println(s1.equalsIgnoreCase(s2)); // true
System.out.println(s1.equalsIgnoreCase(s3)); // true
System.out.println("‐‐‐‐‐‐‐‐‐‐‐");
	}
}


獲取的方法

public int length () :返回此字串的長度。

public String concat (String str) :將指定的字串連線到該字串的末尾。

public char charAt (int index) :返回指定索引處的 char值。

public int indexOf (String str) :返回指定子字串第一次出現在該字串內的索引。

public class Demo02StringGet {

    public static void main(String[] args) {
        // 獲取字串的長度
        int length = "asdasfeutrvauevbueyvb".length();
        System.out.println("字串的長度是:" + length);

        // 拼接字串
        String str1 = "Hello";
        String str2 = "World";
        String str3 = str1.concat(str2);
        System.out.println(str1); // Hello,原封不動
        System.out.println(str2); // World,原封不動
        System.out.println(str3); // HelloWorld,新的字串
        System.out.println("==============");

        // 獲取指定索引位置的單個字元
        char ch = "Hello".charAt(1);
        System.out.println("在1號索引位置的字元是:" + ch);
        System.out.println("==============");

        // 查詢引數字串在本來字串當中出現的第一次索引位置
        // 如果根本沒有,返回-1值
        String original = "HelloWorldHelloWorld";
        int index = original.indexOf("llo");
        System.out.println("第一次索引值是:" + index); // 2

        System.out.println("HelloWorld".indexOf("abc")); // -1
    }

}

字串的擷取方法:

public String substring(int index):擷取從引數位置一直到字串末尾,返回新字串。
public String substring(int begin, int end):擷取從begin開始,一直到end結束,中間的字串。
備註:[begin,end),包含左邊,不包含右邊。

public class Demo03Substring {

    public static void main(String[] args) {
        String str1 = "HelloWorld";
        String str2 = str1.substring(5);
        System.out.println(str1); // HelloWorld,原封不動
        System.out.println(str2); // World,新字串
        System.out.println("================");

        String str3 = str1.substring(4, 7);
        System.out.println(str3); // oWo
        System.out.println("================");

        // 下面這種寫法,字串的內容仍然是沒有改變的
        // 下面有兩個字串:"Hello","Java"
        // strA當中儲存的是地址值。
        // 本來地址值是Hello的0x666,
        // 後來地址值變成了Java的0x999
        String strA = "Hello";
        System.out.println(strA); // Hello
        strA = "Java";
        System.out.println(strA); // Java
    }

}


String當中與轉換相關的常用方法有:

public char[] toCharArray():將當前字串拆分成為字元陣列作為返回值。
public byte[] getBytes():獲得當前字串底層的位元組陣列。
public String replace(CharSequence oldString, CharSequence newString):
將所有出現的老字串替換成為新的字串,返回替換之後的結果新字串。
備註:CharSequence意思就是說可以接受字串型別。

public class Demo04StringConvert {

    public static void main(String[] args) {
        // 轉換成為字元陣列
        char[] chars = "Hello".toCharArray();
        System.out.println(chars[0]); // H
        System.out.println(chars.length); // 5
        System.out.println("==============");

        // 轉換成為位元組陣列
        byte[] bytes = "abc".getBytes();
        for (int i = 0; i < bytes.length; i++) {
            System.out.println(bytes[i]);
        }
        System.out.println("==============");

        // 字串的內容替換
        String str1 = "How do you do?";
        String str2 = str1.replace("o", "*");
        System.out.println(str1); // How do you do?
        System.out.println(str2); // H*w d* y*u d*?
        System.out.println("==============");

        String lang1 = "會不會玩兒呀!你大爺的!你大爺的!你大爺的!!!";
        String lang2 = lang1.replace("你大爺的", "****");
        System.out.println(lang2); // 會不會玩兒呀!****!****!****!!!
    }

}

分割字串的方法:
public String[] split(String regex):按照引數的規則,將字串切分成為若干部分。

注意事項:
split方法的引數其實是一個“正則表示式”,今後學習。
今天要注意:如果按照英文句點“.”進行切分,必須寫"\."(兩個反斜槓)

public class Demo05StringSplit {

    public static void main(String[] args) {
        String str1 = "aaa,bbb,ccc";
        String[] array1 = str1.split(",");
        for (int i = 0; i < array1.length; i++) {
            System.out.println(array1[i]);
        }
        System.out.println("===============");

        String str2 = "aaa bbb ccc";
        String[] array2 = str2.split(" ");
        for (int i = 0; i < array2.length; i++) {
            System.out.println(array2[i]);
        }
        System.out.println("===============");

        String str3 = "XXX.YYY.ZZZ";
        String[] array3 = str3.split("\\.");
        System.out.println(array3.length); // 0
        for (int i = 0; i < array3.length; i++) {
            System.out.println(array3[i]);
        }
    }

}


題目:
定義一個方法,把陣列{1,2,3}按照指定格式拼接成一個字串。格式參照如下:[word1#word2#word3]。

分析:

  1. 首先準備一個int[]陣列,內容是:1、2、3
  2. 定義一個方法,用來將陣列變成字串
    三要素
    返回值型別:String
    方法名稱:fromArrayToString
    引數列表:int[]
  3. 格式:[word1#word2#word3]
    用到:for迴圈、字串拼接、每個陣列元素之前都有一個word字樣、分隔使用的是#、區分一下是不是最後一個
  4. 呼叫方法,得到返回值,並列印結果字串
public class Demo06StringPractise {

    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4};

        String result = fromArrayToString(array); //呼叫下面方法,將陣列形式轉換成string型別
        System.out.println(result);
    }

    public static String fromArrayToString(int[] array) {
        String str = "[";
        for (int i = 0; i < array.length; i++) {
            if (i == array.length - 1) {
                str += "word" + array[i] + "]";
            } else {
                str += "word" + array[i] + "#";
            }
        }
        return str;
    }

}


題目:
鍵盤輸入一個字串,並且統計其中各種字元出現的次數。
種類有:大寫字母、小寫字母、數字、其他

思路:

  1. 既然用到鍵盤輸入,肯定是Scanner
  2. 鍵盤輸入的是字串,那麼:String str = sc.next();
  3. 定義四個變數,分別代表四種字元各自的出現次數。
  4. 需要對字串一個字、一個字檢查,String-->char[],方法就是toCharArray()
  5. 遍歷char[]字元陣列,對當前字元的種類進行判斷,並且用四個變數進行++動作。
  6. 列印輸出四個變數,分別代表四種字元出現次數。
public class Demo07StringCount {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("請輸入一個字串:");
        String input = sc.next(); // 獲取鍵盤輸入的一個字串

        int countUpper = 0; // 大寫字母
        int countLower = 0; // 小寫字母
        int countNumber = 0; // 數字
        int countOther = 0; // 其他字元

        char[] charArray = input.toCharArray();
        for (int i = 0; i < charArray.length; i++) {
            char ch = charArray[i]; // 當前單個字元
            if ('A' <= ch && ch <= 'Z') {
                countUpper++;
            } else if ('a' <= ch && ch <= 'z') {
                countLower++;
            } else if ('0' <= ch && ch <= '9') {
                countNumber++;
            } else {
                countOther++;
            }
        }

        System.out.println("大寫字母有:" + countUpper);
        System.out.println("小寫字母有:" + countLower);
        System.out.println("數字有:" + countNumber);
        System.out.println("其他字元有:" + countOther);
    }

}

20、static關鍵字

20.1、概述

關於 static 關鍵字的使用,它可以用來修飾的成員變數和成員方法,被修飾的成員是屬於類的,而不是單單是屬於某個物件的。也就是說,既然屬於類,就可以不靠建立物件來呼叫了;

20.2、定義和使用格式

類變數

當 static 修飾成員變數時,該變數稱為類變數。該類的每個物件都共享同一個類變數的值。任何物件都可以更改該類變數的值,但也可以在不建立該類的物件的情況下對類變數進行操作。

  • 類變數:使用 static關鍵字修飾的成員變數。

格式

static 資料型別 變數名;

例子:

比如說,基礎班新班開班,學員報到。現在想為每一位新來報到的同學編學號(sid),從第一名同學開始,sid為1,以此類推。學號必須是唯一的,連續的,並且與班級的人數相符,這樣以便知道,要分配給下一名新同學的學號是多少。這樣我們就需要一個變數,與單獨的每一個學生物件無關,而是與整個班級同學數量有關。所以,我們可以這樣定義一個靜態變數numberOfStudent,程式碼如下:

public class Student {
private String name;
private int age;
//	學生的id private int sid;
//	類變數,記錄學生數量,分配學號
public static int numberOfStudent = 0;
public Student(String name, int age){
this.name = name;
this.age = age;
//通過 numberOfStudent 給學生分配學號 
this.sid = ++numberOfStudent;
}
//	列印屬性值
public void show() {
System.out.println("Student : name=" + name + ", age=" + age + ", sid=" + sid );
	}
}
 
public class StuDemo {
public static void main(String[] args) {
    Student s1 = new Student("張三", 23);
    Student s2 = new Student("李四", 24);
    Student s3 = new Student("王五", 25);
    Student s4 = new Student("趙六", 26);
    s1.show(); // Student : name=張三, age=23, sid=1
    s2.show(); // Student : name=李四, age=24, sid=2
    s3.show(); // Student : name=王五, age=25, sid=3
    s4.show(); // Student : name=趙六, age=26, sid=4
	}
}


靜態方法

當 static 修飾成員方法時,該方法稱為類方法 。靜態方法在宣告中有 static ,建議使用類名來呼叫,而不需要建立類的物件。呼叫方式非常簡單。

類方法:使用 static關鍵字修飾的成員方法,習慣稱為靜態方法。

定義格式:

修飾符 static 返回值型別 方法名 (引數列表){
//	執行語句
}

舉例:在Student類中定義靜態方法

public static void showNum() {
System.out.println("num:" +	numberOfStudent);
}

注意事項:

  1. 靜態不能直接訪問非靜態。
    原因:因為在記憶體當中是【先】有的靜態內容,【後】有的非靜態內容。
    “先人不知道後人,但是後人知道先人。”
  2. 靜態方法當中不能用this。
    原因:this代表當前物件,通過誰呼叫的方法,誰就是當前物件。

【 靜態方法只能訪問靜態成員 】

呼叫格式

被static修飾的成員可以並且建議通過類名直接訪問。雖然也可以通過物件名訪問靜態成員,原因即多個物件均屬於一個類,共享使用同一個靜態成員,但是不建議,會出現警告資訊。

格式:

//	訪問類變數
類名.類變數名;

//	呼叫靜態方法
類名.靜態方法名(引數);


例子:

public class MyClass {

    int num; // 成員變數
    static int numStatic; // 靜態變數

    // 成員方法
    public void method() {
        System.out.println("這是一個成員方法。");
        // 成員方法可以訪問成員變數
        System.out.println(num);
        // 成員方法可以訪問靜態變數
        System.out.println(numStatic);
    }

    // 靜態方法
    public static void methodStatic() {
        System.out.println("這是一個靜態方法。");
        // 靜態方法可以訪問靜態變數
        System.out.println(numStatic);
        // 靜態不能直接訪問非靜態【重點】
//        System.out.println(num); // 錯誤寫法!

        // 靜態方法中不能使用this關鍵字。
//        System.out.println(this); // 錯誤寫法!
    }

}


20.3、靜態原理圖解

靜態關鍵字修飾

靜態記憶體圖

20.4、靜態程式碼塊

靜態程式碼塊的格式是:

public class 類名稱 {
static {
// 靜態程式碼塊的內容
}
}

特點:當第一次用到本類時,靜態程式碼塊執行唯一的一次。
靜態內容總是優先於非靜態,所以靜態程式碼塊比構造方法先執行。

靜態程式碼塊的典型用途:
用來一次性地對靜態成員變數進行賦值。

public class Demo04Static {

    public static void main(String[] args) {
        Person one = new Person();
        Person two = new Person();
    }

}

21、Arrays類

21.1、概述

java.util.Arrays 此類包含用來運算元組的各種方法,比如排序和搜尋等。其所有方法均為靜態方法,呼叫起來非常簡單。

21.2、運算元組的方法

public static String toString(陣列):將引數陣列變成字串(按照預設格式:[元素1, 元素2, 元素3...])
public static void sort(陣列):按照預設升序(從小到大)對陣列的元素進行排序。

備註:

  1. 如果是數值,sort預設按照升序從小到大
  2. 如果是字串,sort預設按照字母升序
  3. 如果是自定義的型別,那麼這個自定義的類需要有Comparable或者Comparator介面的支援。(今後學習)
public class Demo01Arrays {

    public static void main(String[] args) {
        int[] intArray = {10, 20, 30};
        // 將int[]陣列按照預設格式變成字串
        String intStr = Arrays.toString(intArray);
        System.out.println(intStr); // [10, 20, 30]

        int[] array1 = {2, 1, 3, 10, 6};
        Arrays.sort(array1);
        System.out.println(Arrays.toString(array1)); // [1, 2, 3, 6, 10]

        String[] array2 = {"bbb", "aaa", "ccc"};
        Arrays.sort(array2);
        System.out.println(Arrays.toString(array2)); // [aaa, bbb, ccc]
    }

}


題目:
請使用Arrays相關的API,將一個隨機字串中的所有字元升序排列,並倒序列印。

public class Demo02ArraysPractise {

    public static void main(String[] args) {
        String str = "asv76agfqwdfvasdfvjh";

        // 如何進行升序排列:sort
        // 必須是一個數組,才能用Arrays.sort方法
        // String --> 陣列,用toCharArray
        char[] chars = str.toCharArray();
        Arrays.sort(chars); // 對字元陣列進行升序排列

        // 需要倒序遍歷
        for (int i = chars.length - 1; i >= 0; i--) {
            System.out.println(chars[i]);
        }
    }

}

22、Math類

22.1、概述

java.lang.Math 類包含用於執行基本數學運算的方法,如初等指數、對數、平方根和三角函式。類似這樣的工具類,其所有方法均為靜態方法,並且不會建立物件,呼叫起來非常簡單。

22.2、基本運算的方法

java.util.Math類是數學相關的工具類,裡面提供了大量的靜態方法,完成與數學運算相關的操作。

public static double abs(double num):獲取絕對值。有多種過載。
public static double ceil(double num):向上取整。
public static double floor(double num):向下取整。
public static long round(double num):四捨五入。

Math.PI代表近似的圓周率常量(double)。

public class Demo03Math {

    public static void main(String[] args) {
        // 獲取絕對值
        System.out.println(Math.abs(3.14)); // 3.14
        System.out.println(Math.abs(0)); // 0
        System.out.println(Math.abs(-2.5)); // 2.5
        System.out.println("================");

        // 向上取整
        System.out.println(Math.ceil(3.9)); // 4.0
        System.out.println(Math.ceil(3.1)); // 4.0
        System.out.println(Math.ceil(3.0)); // 3.0
        System.out.println("================");

        // 向下取整,抹零
        System.out.println(Math.floor(30.1)); // 30.0
        System.out.println(Math.floor(30.9)); // 30.0
        System.out.println(Math.floor(31.0)); // 31.0
        System.out.println("================");

        System.out.println(Math.round(20.4)); // 20
        System.out.println(Math.round(10.5)); // 11
    }

}


題目:
計算在-10.8到5.9之間,絕對值大於6或者小於2.1的整數有多少個?

分析:

  1. 既然已經確定了範圍,for迴圈
  2. 起點位置-10.8應該轉換成為-10,兩種辦法:
    2.1 可以使用Math.ceil方法,向上(向正方向)取整
    2.2 強轉成為int,自動捨棄所有小數位
  3. 每一個數字都是整數,所以步進表示式應該是num++,這樣每次都是+1的。
  4. 如何拿到絕對值:Math.abs方法。
  5. 一旦發現了一個數字,需要讓計數器++進行統計。

備註:如果使用Math.ceil方法,-10.8可以變成-10.0。注意double也是可以進行++的。

public class Demo04MathPractise {

    public static void main(String[] args) {
        int count = 0; // 符合要求的數量

        double min = -10.8;
        double max = 5.9;
        // 這樣處理,變數i就是區間之內所有的整數
        for (int i = (int) min; i < max; i++) {
            int abs = Math.abs(i); // 絕對值
            if (abs > 6 || abs < 2.1) {
                System.out.println(i);
                count++;
            }
        }

        System.out.println("總共有:" + count); // 9
    }

}


23、繼承

23.1、概述

多個類中存在相同屬性和行為時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行為,只要繼承那一個類即可;

定義

繼承:就是子類繼承父類的屬性和行為,使得子類物件具有與父類相同的屬性、相同的行為。子類可以直接訪問父類中的非私有的屬性和行為。

好處:

  1. 提高程式碼的複用性。

  2. 類與類之間產生了關係,是多型的前提。

圖解:

23.2、繼承的格式

通過 extends 關鍵字,可以宣告一個子類繼承另外一個父類,定義格式如下:

class 父類 {
...
}

class 子類 extends 父類 {
...
}

例子:

父類:Employee類
// 定義一個父類:員工
public class Employee {

    public void method() {
        System.out.println("方法執行!");
    }

}

子類:Assistant類
// 定義了員工的另一個子類:助教
public class Assistant extends Employee {
}

子類:Teacher類
// 定義了一個員工的子類:講師
public class Teacher extends Employee {

}

測試類:
public class Demo01Extends {

    public static void main(String[] args) {
        // 建立了一個子類物件
        Teacher teacher = new Teacher();
        // Teacher類當中雖然什麼都沒寫,但是會繼承來自父類的method方法。
        teacher.method();

        // 建立另一個子類助教的物件
        Assistant assistant = new Assistant();
        assistant.method();
    }

}

23.3、繼承後的特點-成員變數

當類之間產生了關係後,其中各類中的成員變數,又產生了哪些影響呢?

  • 成員變數不重名

如果子類父類中出現不重名的成員變數,這時的訪問是沒有影響的。程式碼如下:

父類:
public class Fu {

    int numFu = 10;

    int num = 100;

    public void methodFu() {
        // 使用的是本類當中的,不會向下找子類的
        System.out.println(num);
    }

}

子類:
public class Zi extends Fu {

    int numZi = 20;

    int num = 200;

    public void methodZi() {
        // 因為本類當中有num,所以這裡用的是本類的num
        System.out.println(num);
    }

}

測試類:

/*
在父子類的繼承關係當中,如果成員變數重名,則建立子類物件時,訪問有兩種方式:

直接通過子類物件訪問成員變數:
    等號左邊是誰,就優先用誰,沒有則向上找。
間接通過成員方法訪問成員變數:
    該方法屬於誰,就優先用誰,沒有則向上找。
 */
public class Demo01ExtendsField {

    public static void main(String[] args) {
        Fu fu = new Fu(); // 建立父類物件
        System.out.println(fu.numFu); // 只能使用父類的東西,沒有任何子類內容
        System.out.println("===========");

        Zi zi = new Zi();

        System.out.println(zi.numFu); // 10
        System.out.println(zi.numZi); // 20
        System.out.println("===========");

        // 等號左邊是誰,就優先用誰
        System.out.println(zi.num); // 優先子類,200
//        System.out.println(zi.abc); // 到處都沒有,編譯報錯!
        System.out.println("===========");

        // 這個方法是子類的,優先用子類的,沒有再向上找
        zi.methodZi(); // 200
        // 這個方法是在父類當中定義的,
        zi.methodFu(); // 100
    }

}


  • 成員變數重名

如果子類父類中出現重名的成員變數,這時的訪問是有影響的。程式碼如下:

父類:
public class Fu {

    int num = 10;

}

子類:
public class Zi extends Fu {

    int num = 20;

    public void method() {
        int num = 30;
        System.out.println(num); // 30,區域性變數
        System.out.println(this.num); // 20,本類的成員變數
        System.out.println(super.num); // 10,父類的成員變數
    }

}

測試:
/*
區域性變數:         直接寫成員變數名
本類的成員變數:    this.成員變數名
父類的成員變數:    super.成員變數名
 */
public class Demo01ExtendsField {

    public static void main(String[] args) {
        Zi zi = new Zi();

        zi.method();
    }

}


子父類中出現了同名的成員變數時,在子類中需要訪問父類中非私有成員變數時,需要使用 super 關鍵字,修飾父類成員變數,類似於之前學過的 this 。

使用格式:

super.父類成員變數名

注意:

父類中的成員變數是非私有的,子類中可以直接訪問。若父類中的成員變數私有了,子類是不能直接訪問的。通常編碼時,我們遵循封裝的原則,使用private修飾成員變數,那麼如何訪問父類的私有成員變數呢?對!可以在父類中提供公共的getXxx方法和setXxx方法。

23.4、繼承後的特點-成員方法
  • 成員方法不重名

如果子類父類中出現不重名的成員方法,這時的呼叫是沒有影響的。物件呼叫方法時,會先在子類中查詢有沒有對應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類中相應的方法。

  • 成員方法重名——重寫(Override)

在父子類的繼承關係當中,建立子類物件,訪問成員方法的規則:
建立的物件是誰,就優先用誰,如果沒有則向上找。

注意事項:
無論是成員方法還是成員變數,如果沒有都是向上找父類,絕對不會向下找子類的。

重寫(Override)
概念:在繼承關係當中,方法的名稱一樣,引數列表也一樣。

重寫(Override):方法的名稱一樣,引數列表【也一樣】。覆蓋、覆寫。
過載(Overload):方法的名稱一樣,引數列表【不一樣】。

方法的覆蓋重寫特點:建立的是子類物件,則優先用子類方法。

程式碼:

父類:
public class Fu {

    public void methodFu() {
        System.out.println("父類方法執行!");
    }

    public void method() {
        System.out.println("父類重名方法執行!");
    }

}

子類
public class Zi extends Fu {

    public void methodZi() {
        System.out.println("子類方法執行!");
    }

    public void method() {
        System.out.println("子類重名方法執行!");
    }

}

測試:
public class Demo01ExtendsMethod {

    public static void main(String[] args) {
        Zi zi = new Zi();

        zi.methodFu();
        zi.methodZi();

        // 建立的是new了子類物件,所以優先用子類方法
        zi.method();  //輸出的是子類重寫後的資訊
    }

}


方法覆蓋重寫的注意事項:

  1. 必須保證父子類之間方法的名稱相同,引數列表也相同。
    @Override:寫在方法前面,用來檢測是不是有效的正確覆蓋重寫。
    這個註解就算不寫,只要滿足要求,也是正確的方法覆蓋重寫。

  2. 子類方法的返回值必須【小於等於】父類方法的返回值範圍。
    小擴充套件提示:java.lang.Object類是所有類的公共最高父類(祖宗類),java.lang.String就是Object的子類。

  3. 子類方法的許可權必須【大於等於】父類方法的許可權修飾符。
    小擴充套件提示:public > protected > (default) > private
    備註:(default)不是關鍵字default,而是什麼都不寫,留空。

父類:
public class Fu {

    public String method() {
        return null;
    }

}

子類:
public class Zi extends Fu {

    @Override   //檢驗覆蓋重寫是否正確
    public String method() {
        return null;
    }

}


小貼士:這裡重寫時,用到super.父類成員方法,表示呼叫父類的成員方法 。

23.5、繼承後的特點-構造方法

首先我們要回憶兩個事情,構造方法的定義格式和作用。

  1. 構造方法的名字是與類名一致的。所以子類是無法繼承父類構造方法的。

  2. 構造方法的作用是初始化成員變數的。所以子類的初始化過程中,必須先執行父類的初始化動作。子類的構造方法中預設有一個 super() ,表示呼叫父類的構造方法,父類成員變數初始化後,才可以給子類使用。【先有父親後有兒子】程式碼如下:

父類:
public class Fu {

    public Fu() {
        System.out.println("父類無參構造");
    }

    public Fu(int num) {
        System.out.println("父類有參構造!");
    }

}

子類:
public class Zi extends Fu {

    public Zi() {
        super(); // 在呼叫父類無參構造方法,預設是有
//        super(20); // 在呼叫父類過載的構造方法
        System.out.println("子類構造方法!");
    }

    public void method() {
//        super(); // 錯誤寫法!只有子類構造方法,才能呼叫父類構造方法。
    }

}

測試:
/*
繼承關係中,父子類構造方法的訪問特點:

1. 子類構造方法當中有一個預設隱含的“super()”呼叫,所以一定是先呼叫的父類構造,後執行的子類構造。
2. 子類構造可以通過super關鍵字來呼叫父類過載構造。
3. super的父類構造呼叫,必須是子類構造方法的第一個語句。不能一個子類構造呼叫多次super構造。
總結:
子類必須呼叫父類構造方法,不寫則贈送super();寫了則用寫的指定的super呼叫,super只能有一個,還必須是第一個。
 */
public class Demo01Constructor {

    public static void main(String[] args) {
        Zi zi = new Zi();
    }

}

23.6、super和this

父類空間優先於子類物件產生

在每次建立子類物件時,先初始化父類空間,再建立其子類物件本身。目的在於子類物件中包含了其對應的父類空間,便可以包含其父類的成員,如果父類成員非private修飾,則子類可以隨意使用父類成員。程式碼體現在子類的構造方法呼叫時,一定先呼叫父類的構造方法;

  • super和this的含義

1)super :代表父類的儲存空間標識(可以理解為父親的引用)。

2)this :代表當前物件的引用(誰呼叫就代表誰)。

  • super和this的用法

1、 訪問成員

this.成員變數	‐‐	本類的
super.成員變數	‐‐	父類的

this.成員方法名()	‐‐	本類的
super.成員方法名()	‐‐	父類的

2 、訪問構造方法

this(...)	‐‐	本類的構造方法
super(...)	‐‐	父類的構造方法

子類的每個構造方法中均有預設的super(),呼叫父類的空參構造。手動呼叫父類構造會覆蓋預設的super()。 super() 和 this() 都必須是在構造方法的第一行,所以不能同時出現。

23.7、繼承的特點
  1. Java只支援單繼承,不支援多繼承。
//一個類只能有一個父類,不可以有多個父類。
class C extends A{}	//ok
class C extends A,B...	//error

2.Java支援多層繼承(繼承體系)

class A{}
class B extends A{}
class C extends B{}

頂層父類是Object類。所有的類預設繼承Object,作為父類。

  1. 子類和父類是一種相對的概念。

繼承特點圖解:

24、抽象類

24.1、概述

父類中的方法,被它的子類們重寫,子類各自的實現都不盡相同。那麼父類的方法宣告和方法主體,只有宣告還有意義,而方法主體則沒有存在的意義了。我們把沒有方法主體的方法稱為抽象方法。Java語法規定,包含抽象方法的類就是抽象類。

定義:

抽象方法 : 沒有方法體的方法。

抽象類:包含抽象方法的類。

圖解:

24.2、abstract使用格式

抽象方法

使用 abstract 關鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個方法名,而沒有方法體。

定義格式

修飾符 abstract 返回值型別 方法名 (引數列表);

程式碼舉例:

public abstract void run();

抽象類:

如果一個類包含抽象方法,那麼該類必須是抽象類。

定義格式:

abstract class 類名字 {

}

程式碼舉例:

public abstract class Animal {

public abstract void run();

}

抽象的使用

抽象方法:就是加上abstract關鍵字,然後去掉大括號,直接分號結束。
抽象類:抽象方法所在的類,必須是抽象類才行。在class之前寫上abstract即可。

如何使用抽象類和抽象方法:

  1. 不能直接建立new抽象類物件。
  2. 必須用一個子類來繼承抽象父類。
  3. 子類必須覆蓋重寫抽象父類當中所有的抽象方法。
    覆蓋重寫(實現):子類去掉抽象方法的abstract關鍵字,然後補上方法體大括號。
  4. 建立子類物件進行使用。

程式碼舉例

public abstract class Animal {

    // 這是一個抽象方法,代表吃東西,但是具體吃什麼(大括號的內容)不確定。
    public abstract void eat();

    // 這是普通的成員方法,抽象類裡面可以有普通方法
//    public void normalMethod() {
//
//    }

}

子類:
public class Cat extends Animal {

    @Override	//這裡就一定要覆蓋重寫抽象方法,除非子類也是抽象類
    public void eat() {
        System.out.println("貓吃魚");
    }

}

注意:抽象類是不可以直接new的;
//  Animal animal = new Animal(); // 錯誤寫法!不能直接建立抽象類物件

24.3、注意事項
  1. 抽象類不能建立物件,如果建立,編譯無法通過而報錯。只能建立其非抽象子類的物件。
理解:假設建立了抽象類的物件,呼叫抽象的方法,而抽象方法沒有具體的方法體,沒有意義。

  1. 抽象類中,可以有構造方法,是供子類建立物件時,初始化父類成員使用的。
理解:子類的構造方法中,有預設的super(),需要訪問父類構造方法 

  1. 抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
理解:未包含抽象方法的抽象類,目的就是不想讓呼叫者建立該類物件,通常用於某些特殊的類結構設計


4.抽象類的子類,必須重寫抽象父類中所有的抽象方法,否則,編譯無法通過而報錯。除非該子類也是抽象 類。

理解:假設不重寫所有抽象方法,則類中可能包含抽象方法。那麼建立物件後,呼叫抽象的方法,沒有意義。

24.4、繼承綜合案例

群主發普通紅包。某群有多名成員,群主給成員發普通紅包。普通紅包的規則:

  1. 群主的一筆金額,從群主餘額中扣除,平均分成n等份,讓成員領取。

  2. 成員領取紅包後,儲存到成員餘額中。

請根據描述,完成案例中所有類的定義以及指定類之間的繼承關係,並完成發紅包的操作。

分析圖:

實現程式碼:

User實體類:

package cn.itcast.day09.demo14;

public class User {

    private String name; // 姓名
    private int money; // 餘額,也就是當前使用者擁有的錢數

    public User() {
    }

    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }

    // 展示一下當前使用者有多少錢
    public void show() {
        System.out.println("我叫:" + name + ",我有多少錢:" + money);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }
}


群眾類Manage:

import java.util.ArrayList;

// 群主的類
public class Manager extends User {

    public Manager() {
    }

    public Manager(String name, int money) {
        super(name, money);
    }

    public ArrayList<Integer> send(int totalMoney, int count) {
        // 首先需要一個集合,用來儲存若干個紅包的金額
        ArrayList<Integer> redList = new ArrayList<>();

        // 首先看一下群主自己有多少錢
        int leftMoney = super.getMoney(); // 群主當前餘額
        if (totalMoney > leftMoney) {
            System.out.println("餘額不足");
            return redList; // 返回空集合
        }

        // 扣錢,其實就是重新設定餘額
        super.setMoney(leftMoney - totalMoney);

        // 發紅包需要平均拆分成為count份
        int avg = totalMoney / count;
        int mod = totalMoney % count; // 餘數,也就是甩下的零頭

        // 除不開的零頭,包在最後一個紅包當中
        // 下面把紅包一個一個放到集合當中
        for (int i = 0; i < count - 1; i++) {
            redList.add(avg);
        }

        // 最後一個紅包
        int last = avg + mod;
        redList.add(last);

        return redList;
    }
}


普通使用者類Member:

import java.util.ArrayList;
import java.util.Random;

// 普通成員
public class Member extends User {

    public Member() {
    }

    public Member(String name, int money) {
        super(name, money);
    }

    public void receive(ArrayList<Integer> list) {
        // 從多個紅包當中隨便抽取一個,給我自己。
        // 隨機獲取一個集合當中的索引編號
        int index = new Random().nextInt(list.size());
        // 根據索引,從集合當中刪除,並且得到被刪除的紅包,給我自己
        int delta = list.remove(index);
        // 當前成員自己本來有多少錢:
        int money = super.getMoney();
        // 加法,並且重新設定回去
        super.setMoney(money + delta);
    }
}


測試

public class MainRedPacket {

    public static void main(String[] args) {
        Manager manager = new Manager("群主", 100);

        Member one = new Member("成員A", 0);
        Member two = new Member("成員B", 0);
        Member three = new Member("成員C", 0);

        manager.show(); // 100
        one.show(); // 0
        two.show(); // 0
        three.show(); // 0
        System.out.println("===============");

        // 群主總共發20塊錢,分成3個紅包
        ArrayList<Integer> redList = manager.send(20, 3);
        // 三個普通成員收紅包
        one.receive(redList);
        two.receive(redList);
        three.receive(redList);

        manager.show(); // 100-20=80
        // 6、6、8,隨機分給三個人
        one.show();
        two.show();
        three.show();
    }

}

25、介面

25.1、概述

介面,是Java語言中一種引用型別,是方法的集合,如果說類的內部封裝了成員變數、構造方法和成員方法,那麼介面的內部主要就是封裝了方法,包含抽象方法(JDK 7及以前),預設方法和靜態方法(JDK 8),私有方法(JDK 9)。

介面的定義,它與定義類方式相似,但是使用 interface 關鍵字。它也會被編譯成.class檔案,但一定要明確它並不是類,而是另外一種引用資料型別。

 引用資料型別:陣列,類,介面。

介面的使用,它不能建立物件,但是可以被實現( implements ,類似於被繼承)。一個實現介面的類(可以看做是介面的子類),需要實現介面中所有的抽象方法,建立該類物件,就可以呼叫方法了,否則它必須是一個抽象類。

25.2、定義格式
public interface 介面名稱 {

//	抽象方法
//	預設方法
//	靜態方法
//	私有方法
}

備註:換成了關鍵字interface之後,編譯生成的位元組碼檔案仍然是:.java --> .class。

例子:

先定義一個介面:

public interface LiveAble {
//	定義抽象方法
public abstract void eat();
public abstract void sleep();
   
}

定義實現類:

public class Animal implements LiveAble {
@Override
public void eat() {
System.out.println("吃東西");
}
@Override
public void sleep() {
System.out.println("晚上睡");
	}
}

定義測試類:

public class InterfaceDemo {
    
public static void main(String[] args) {
//	建立子類物件
Animal a = new Animal();
//	呼叫實現後的方法 
    a.eat(); 
    a.sleep();
	}
}

輸出結果:
吃東西
晚上睡

25.3、Java7版本介面功能

如果是Java 7,那麼介面中可以包含的內容有:

1、常量

/*
介面當中也可以定義“成員變數”,但是必須使用public static final三個關鍵字進行修飾。
從效果上看,這其實就是介面的【常量】。
格式:
public static final 資料型別 常量名稱 = 資料值;
備註:
一旦使用final關鍵字進行修飾,說明不可改變。

注意事項:
1. 介面當中的常量,可以省略public static final,注意:不寫也照樣是這樣。
2. 介面當中的常量,必須進行賦值;不能不賦值。
3. 介面中常量的名稱,使用完全大寫的字母,用下劃線進行分隔。(推薦命名規則)
 */
public interface MyInterfaceConst {

    // 這其實就是一個常量,一旦賦值,不可以修改
    public static final int NUM_OF_MY_CLASS = 12;

}

2、抽象方法

public interface InterFaceName {
public abstract void method();	//抽象方法
/*
// 這是一個抽象方法
    public abstract void methodAbs1();

    // 這也是抽象方法
    abstract void methodAbs2();

    // 這也是抽象方法
    public void methodAbs3();

    // 這也是抽象方法
    void methodAbs4();
*/
}

注意事項:

  1. 介面當中的抽象方法,修飾符必須是兩個固定的關鍵字:public abstract
  2. 這兩個關鍵字修飾符,可以選擇性地省略。(今天剛學,所以不推薦。)
  3. 方法的三要素,可以隨意定義。
25.4、Java8新增介面功能

如果是Java 8,還可以額外包含有:

1、預設方法

格式:
public default 返回值型別 方法名稱(引數列表) {
方法體
}

備註:介面當中的預設方法,可以解決介面升級的問題。

例子:

/*如果想在介面中新增加一個方法,但又可以不影響到已經在使用的實現類,我們就可以用介面的預設方法,預設方法是可以有方法體的,而且它不一定要被實現類重寫,也可以被重寫,如果實現類中沒重寫,用到該方法時就是找到介面中
*/
public interface MyInterfaceDefault {

    // 抽象方法
    public abstract void methodAbs();

    // 新添加了一個抽象方法
//    public abstract void methodAbs2();

    // 新新增的方法,改成預設方法
    public default void methodDefault() {
        System.out.println("這是新新增的預設方法");
    }

}


2、靜態方法

格式:

public interface InterFaceName {

public static void method2() {
//	執行語句
	}
}

例子:

/*
介面中的靜態方法也是可以有方法體的,注意的是,實現類不可以覆蓋重寫介面中的靜態方法,如果想用介面中的靜態方法,格式:介面名.方法名
*/
public interface MyInterfacePrivateB {

    public static void methodStatic1() {
        System.out.println("靜態方法1");
        methodStaticCommon();
    }

    public static void methodStatic2() {
        System.out.println("靜態方法2");
        methodStaticCommon();
    }

    private static void methodStaticCommon() {
        System.out.println("AAA");
        System.out.println("BBB");
        System.out.println("CCC");
    }

}

25.5、java9新增介面功能

果是Java 9,還可以額外包含有:

  • 私有方法

1、普通私有方法,解決多個預設方法之間重複程式碼問題
格式:
private 返回值型別 方法名稱(引數列表) {
方法體
}

/*
像解決介面預設方法塊程式碼重複,我們可以將重複的程式碼提取出來分裝成一個方法,但是要保證實現類和物件都訪問到這個方法,我們就要用私有這個特點
*/
public interface MyInterfacePrivateA {

    public default void methodDefault1() {
        System.out.println("預設方法1");
        methodCommon();
    }

    public default void methodDefault2() {
        System.out.println("預設方法2");
        methodCommon();
    }

    private void methodCommon() {	//怎樣?這方法只有這個接口裡面能用,其他都不可以用
        System.out.println("AAA");
        System.out.println("BBB");
        System.out.println("CCC");
    }

}

2、靜態私有方法,解決多個靜態方法之間重複程式碼問題
格式:
private static 返回值型別 方法名稱(引數列表) {
方法體
}

public interface InterFaceName {

private void method() {
//	執行語句
	}
}

例子:私有靜態同樣是解決靜態方法程式碼重複

public interface MyInterfacePrivateB {

    public static void methodStatic1() {
        System.out.println("靜態方法1");
        methodStaticCommon();
    }

    public static void methodStatic2() {
        System.out.println("靜態方法2");
        methodStaticCommon();
    }

    private static void methodStaticCommon() {  //解決方法
        System.out.println("AAA");
        System.out.println("BBB");
        System.out.println("CCC");
    }

}


25.6、介面的多實現

之前學過,在繼承體系中,一個類只能繼承一個父類。而對於介面而言,一個類是可以實現多個介面的,這叫做介面的多實現。並且,一個類能繼承一個父類,同時實現多個介面。

實現格式:

class 類名 [extends 父類名] implements 介面名1,介面名2,介面名3... {

//	重寫介面中抽象方法【必須】
//	重寫介面中預設方法【不重名時可選】
}

[ ]: 表示可選操作。

  • 抽象方法:

介面中,有多個抽象方法時,實現類必須重寫所有抽象方法。如果抽象方法有重名的,只需要重寫一次。程式碼如下:

定義多個介面:

interface A {
public abstract void showA();
public abstract void show();
}

interface B {
public abstract void showB();
public abstract void show();
}

定義實現類:

public class C implements A,B{
@Override
public void showA() {
System.out.println("showA");
}

@Override
public void showB() {
System.out.println("showB");
}

@Override
public void show() {
System.out.println("show");
	}
}

  • 預設方法

介面中,有多個預設方法時,實現類都可繼承使用。如果預設方法有重名的,必須重寫一次。程式碼如下:

定義多個介面:

interface A {
public default void methodA(){}
public default void method(){}
}

interface B {
public default void methodB(){}
public default void method(){}
}


定義實現類 :

public class C implements A,B{

@Override
public void method() {
System.out.println("method");
	}
}


  • 靜態方法

介面中,存在同名的靜態方法並不會衝突,原因是隻能通過各自介面名訪問靜態方法。

優先順序的問題:

優先順序的問題

當一個類,既繼承一個父類,又實現若干個介面時,父類中的成員方法與介面中的預設方法重名,子類就近選擇執行父類的成員方法。程式碼如下:

定義介面:

interface A {

public default void methodA(){
System.out.println("AAAAAAAAAAAA");
	}
}

定義父類:

class D {

public void methodA(){
System.out.println("DDDDDDDDDDDD");
	}
}


定義子類:

class C extends D implements A {
// 未重寫methodA方法
}

定義測試類:

public class Test {

public static void main(String[] args) {
C c = new C();
c.methodA();
	}
}

輸出結果:
DDDDDDDDDDDD

25.7、介面多繼承

一個介面能繼承另一個或者多個介面,這和類之間的繼承比較相似。介面的繼承使用 extends 關鍵字,子介面繼承父介面的方法。如果父介面中的預設方法有重名的,那麼子介面需要重寫一次。程式碼如下:

定義父介面:

interface A {

public default void method(){
System.out.println("AAAAAAAAAAAAAAAAAAA");
	}
}

interface B {
public default void method(){
System.out.println("BBBBBBBBBBBBBBBBBBB");
	}
}

定義子介面:

interface D extends A,B{

@Override
public default void method() {
System.out.println("DDDDDDDDDDDDDD");
	}
}


小貼士:

子介面重寫預設方法時,default關鍵字可以保留。

子類重寫預設方法時,default關鍵字不可以保留。

25.8、其他成員特點
  • 介面中,無法定義成員變數,但是可以定義常量,其值不可以改變,預設使用public static final修飾。

  • 介面中,沒有構造方法,不能建立物件。

  • 介面中,沒有靜態程式碼塊。

25.9、使用介面時注意

使用介面的時候,需要注意:

  1. 介面是沒有靜態程式碼塊或者構造方法的。
  2. 一個類的直接父類是唯一的,但是一個類可以同時實現多個介面。
    格式:
    public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
    // 覆蓋重寫所有抽象方法
    }
  3. 如果實現類所實現的多個介面當中,存在重複的抽象方法,那麼只需要覆蓋重寫一次即可。
  4. 如果實現類沒有覆蓋重寫所有介面當中的所有抽象方法,那麼實現類就必須是一個抽象類。
  5. 如果實現類鎖實現的多個介面當中,存在重複的預設方法,那麼實現類一定要對衝突的預設方法進行覆蓋重寫。
  6. 一個類如果直接父類當中的方法,和介面當中的預設方法產生了衝突,優先用父類當中的方法。

26、多型

26.1、概述

多型是繼封裝、繼承之後,面向物件的第三大特性。

圖解:

定義:

多型: 是指同一行為,具有多個不同表現形式。

前提【重點】

1、類繼承或者介面的實現【二選一】

2、方法的重寫【意義體現:不重寫,無意義】

3、父類引用指向子類物件【格式體現】

26.2、多型的體現

多型體現格式:

父類型別 變數名 = new 子類物件;

變數名.方法名();

父類型別:指子類物件繼承的父類型別,或者實現的父介面型別。

例子:

父類:

public class Fu {

    public void method() {
        System.out.println("父類方法");
    }

    public void methodFu() {
        System.out.println("父類特有方法");
    }

}


子類·:

public class Zi extends Fu {

    @Override
    public void method() {
        System.out.println("子類方法");
    }
}


測試類:

public class Demo01Multi {

    public static void main(String[] args) {
        // 使用多型的寫法
        // 左側父類的引用,指向了右側子類的物件
        Fu obj = new Zi();

        obj.method();
        obj.methodFu();
    }
}


當使用多型方式呼叫方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤;如果有,執行的是子類重寫後方法。

26.3、多型的好處

實際開發的過程中,父類型別作為方法形式引數,傳遞子類物件給方法,進行方法的呼叫,更能體現出多型的擴充套件性與便利。程式碼如下:

父類:

public abstract class Animal {

public abstract void eat();

}


子類:

class Cat extends Animal {
public void eat() {
System.out.println("吃魚");
	}
}

class Dog extends Animal {
public void eat() {
System.out.println("吃骨頭");
	}
}


測試類:

public class Test {

public static void main(String[] args) {
//	多型形式,建立物件
Cat c = new Cat(); 
Dog d = new Dog();
//	呼叫showCatEat showCatEat(c);
//	呼叫showDogEat
showDogEat(d);
/*
以上兩個方法, 均可以被showAnimalEat(Animal a)方法所替代
而執行效果一致
*/
showAnimalEat(c);
showAnimalEat(d);
}

public static void showCatEat (Cat c){
c.eat();
}

public static void showDogEat (Dog d){
d.eat();
}

public static void showAnimalEat (Animal a){//這裡可以傳cat類和dog類過來
a.eat();//下面就執行cat或dog中eat的方法
	}
}


由於多型特性的支援,showAnimalEat方法的Animal型別,是Cat和Dog的父類型別,父類型別接收子類物件,當然可以把Cat物件和Dog物件,傳遞給方法。

當eat方法執行時,多型規定,執行的是子類重寫的方法,那麼效果自然與showCatEat、showDogEat方法一致,所以showAnimalEat完全可以替代以上兩方法。

不僅僅是替代,在擴充套件性方面,無論之後再多的子類出現,我們都不需要編寫showXxxEat方法了,直接使用showAnimalEat都可以完成。

所以,多型的好處,體現在,可以使程式編寫的更簡單,並有良好的擴充套件。

26.4、引用型別轉換

多型的轉型分為向上轉型與向下轉型兩種:

  • 向上轉型

向上轉型:多型本身是子類型別向父類型別向上轉換的過程,這個過程是預設的;

當父類引用指向一個子類物件時,便是向上轉型。

使用格式:

父類型別	變數名 = new 子類型別();

如:Animal a = new Cat();

  • 向下轉型

向下轉型:父類型別向子類型別轉換的過程,這個過程是強制的;

一個已經向上轉型的子類物件,將父類引用轉為子類引用,可以使用強制型別轉換的格式,便是向下轉型。

使用格式:

子類型別 變數名 = (子類型別) 父類變數名;

如:Cat c =(Cat) a;

為什麼要向下轉型:當子類向上成父類時,這時候的物件只能用父類中的方法,如果想用子類中的方法是不可以的,因為父類無法用子類的方法,所以就要將父類強制轉換成父類,叫向下轉型,這個過程是不安全的,出錯會報 ClassCastException 型別轉換異常;

反過來是不是可以說,向上轉型就是去掉子類裡父類沒有的方法和屬性。

向下造型,把抽取出擁有父類共性後的子類再強轉為同一型別的子類,這時子類和普通子類一樣可以使用擴充套件方法和屬性

  • 例子:(上下轉例子)

父類:

public abstract class Animal {

    public abstract void eat();

}


子類:

子類Cat:
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("貓吃魚");
    }

    // 子類特有方法
    public void catchMouse() {
        System.out.println("貓抓老鼠");
    }
}

子類Dog:
public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃SHIT");
    }

    public void watchHouse() {
        System.out.println("狗看家");
    }
}


測試類:

/*
向上轉型一定是安全的,沒有問題的,正確的。但是也有一個弊端:
物件一旦向上轉型為父類,那麼就無法呼叫子類原本特有的內容。

解決方案:用物件的向下轉型【還原】。
 */
public class Demo01Main {

    public static void main(String[] args) {
        // 物件的向上轉型,就是:父類引用指向之類物件。
        Animal animal = new Cat(); // 本來建立的時候是一隻貓【向上轉】
        animal.eat(); // 貓吃魚

//        animal.catchMouse(); // 錯誤寫法!

        // 向下轉型,進行“還原”動作
        Cat cat = (Cat) animal;			//【向下轉】
        cat.catchMouse(); // 貓抓老鼠

        // 下面是錯誤的向下轉型
        // 本來new的時候是一隻貓,現在非要當做狗
        // 錯誤寫法!編譯不會報錯,但是執行會出現異常:
        // java.lang.ClassCastException,類轉換異常
        Dog dog = (Dog) animal;
    }

}

圖解:

轉型的異常

轉型的過程中,一不小心就會遇到這樣的問題,請看如下程式碼:

public class Test {

public static void main(String[] args) {
// 向上轉型
Animal a = new Cat();
a.eat();	// 呼叫的是 Cat 的 eat
// 向下轉型
Dog d = (Dog)a;
d.watchHouse();	// 呼叫的是 Dog 的 watchHouse 【執行報錯】
	}
}

這段程式碼可以通過編譯,但是執行時,卻報出了 ClassCastException ,型別轉換異常!這是因為,明明建立了Cat型別物件,執行時,當然不能轉換成Dog物件的。這兩個型別並沒有任何繼承關係,不符合型別轉換的定義。為了避免ClassCastException的發生,Java提供了 instanceof 關鍵字,給引用變數做型別的校驗,格式如下:

變數名 instanceof 資料型別

如果變數屬於該資料型別,返回true。

如果變數不屬於該資料型別,返回false。


所以,轉換前,我們最好先做一個判斷,程式碼如下

public class Test {

public static void main(String[] args) {
        //	向上轉型
        Animal a = new Cat();
        a.eat();	// 呼叫的是 Cat 的 eat
        //	向下轉型
        if (a instanceof Cat){
        Cat c = (Cat)a;
        c.catchMouse();	// 呼叫的是 Cat 的 catchMouse
        } else if (a instanceof Dog){ Dog d = (Dog)a;
		d.watchHouse();	// 呼叫的是 Dog 的 watchHouse
		}
	}
}

26.5、多型中成員變數和方法的用法

在多型的程式碼當中,成員方法的訪問規則是:
看new的是誰,就優先用誰,沒有則向上找。

口訣:編譯看左邊,執行看右邊。

對比一下:
成員變數:編譯看左邊,執行還看左邊。
成員方法:編譯看左邊,執行看右邊。

例子

父類:

public class Fu /*extends Object*/ {

    int num = 10;

    public void showNum() {
        System.out.println(num);
    }

    public void method() {
        System.out.println("父類方法");
    }

    public void methodFu() {
        System.out.println("父類特有方法");
    }

}


子類:

public class Zi extends Fu {

    int num = 20;	//和父類重名

    int age = 16;

    @Override
    public void showNum() {
        System.out.println(num);
    }

    @Override
    public void method() {
        System.out.println("子類方法");
    }

    public void methodZi() {
        System.out.println("子類特有方法");
    }
}


測試類1:

訪問成員變數的兩種方式:

1. 直接通過物件名稱訪問成員變數:看等號左邊是誰,優先用誰,沒有則向上找。
2. 間接通過成員方法訪問成員變數:看該方法屬於誰,優先用誰,沒有則向上找。
 */
public class Demo01MultiField {

    public static void main(String[] args) {
        // 使用多型的寫法,父類引用指向子類物件
        Fu obj = new Zi();
        System.out.println(obj.num); // 父:10
//        System.out.println(obj.age); // 錯誤寫法!
        System.out.println("=============");

        // 子類沒有覆蓋重寫,就是父:10
        // 子類如果覆蓋重寫,就是子:20
        obj.showNum();
    }

}


測試類2:

public class Demo02MultiMethod {

    public static void main(String[] args) {
        Fu obj = new Zi(); // 多型

        obj.method(); // 父子都有,優先用子
        obj.methodFu(); // 子類沒有,父類有,向上找到父類

        // 編譯看左邊,左邊是Fu,Fu當中沒有methodZi方法,所以編譯報錯。
//        obj.methodZi(); // 錯誤寫法!
    }

}
終結:成員變數就看左邊就可以了,就是一直用父類的,如果是成員方法,就是用子類的,如果物件.方法名報錯就證明父類沒有該方法,此例子只說明向上轉型的情況;

26.6、綜合案例

進行描述筆記本類,實現筆記本使用USB滑鼠、USB鍵盤

1)USB介面,包含開啟功能、關閉功能

2)筆記本類,包含執行功能、關機功能、使用USB裝置功能

3)滑鼠類,要實現USB介面,並具備點選的方法

4)鍵盤類,要實現USB

圖解:

程式碼:

父類:

public interface USB {

    public abstract void open(); // 開啟裝置

    public abstract void close(); // 關閉裝置

}

子類:

// 滑鼠就是一個USB裝置
public class Mouse implements USB {
    @Override
    public void open() {
        System.out.println("開啟滑鼠");
    }

    @Override
    public void close() {
        System.out.println("關閉滑鼠");
    }

    public void click() {
        System.out.println("滑鼠點選");
    }
}


// 鍵盤就是一個USB裝置
public class Keyboard implements USB {
    @Override
    public void open() {
        System.out.println("開啟鍵盤");
    }

    @Override
    public void close() {
        System.out.println("關閉鍵盤");
    }

    public void type() {
        System.out.println("鍵盤輸入");
    }
}


電腦類:

public class Computer {

    public void powerOn() {
        System.out.println("膝上型電腦開機");
    }

    public void powerOff() {
        System.out.println("膝上型電腦關機");
    }

    // 使用USB裝置的方法,使用介面作為方法的引數
    public void useDevice(USB usb) {	//這裡就用到usb介面的功能
        usb.open(); // 開啟裝置
        if (usb instanceof Mouse) { // 一定要先判斷
            Mouse mouse = (Mouse) usb; // 向下轉型
            mouse.click();
        } else if (usb instanceof Keyboard) { // 先判斷
            Keyboard keyboard = (Keyboard) usb; // 向下轉型
            keyboard.type();
        }
        usb.close(); // 關閉裝置
    }

}


測試類:

package cn.itcast.day10.demo07;

public class DemoMain {

    public static void main(String[] args) {
        // 首先建立一個膝上型電腦
        Computer computer = new Computer();
        computer.powerOn();

        // 準備一個滑鼠,供電腦使用
//        Mouse mouse = new Mouse();
        // 首先進行向上轉型
        USB usbMouse = new Mouse(); // 多型寫法
        // 引數是USB型別,我正好傳遞進去的就是USB滑鼠
        computer.useDevice(usbMouse);

        // 建立一個USB鍵盤
        Keyboard keyboard = new Keyboard(); // 沒有使用多型寫法
        // 方法引數是USB型別,傳遞進去的是實現類物件
        computer.useDevice(keyboard); // 正確寫法!也發生了向上轉型
        // 使用子類物件,匿名物件,也可以
//        computer.useDevice(new Keyboard()); // 也是正確寫法

        computer.powerOff();
        System.out.println("==================");

        method(10.0); // 正確寫法,double --> double
        method(20); // 正確寫法,int --> double
        int a = 30;
        method(a); // 正確寫法,int --> double
    }

    public static void method(double num) {
        System.out.println(num);
    }

}


說明:

這個例子實現的是電腦的開機、關機、和使用usb介面的功能,而usb介面又分為滑鼠的使用和鍵盤的使用兩種功能,usb介面中只有開啟裝置和關閉裝置兩種功能,這裡就可以用向上轉型來簡化程式碼,如果想使用對應裝置的功能就要向下轉型;

27、final關鍵字

27.1、概述

學習了繼承後,我們知道,子類可以在父類的基礎上改寫父類內容,比如,方法重寫。那麼我們能不能隨意的繼承API中提供的類,改寫其內容呢?顯然這是不合適的。為了避免這種隨意改寫的情況,Java提供了 final 關鍵字,用於修飾不可改變內容。

  • final: 不可改變。可以用於修飾類、方法和變數。

    類:被修飾的類,不能被繼承。

    方法:被修飾的方法,不能被重寫。

    變數:被修飾的變數,不能被重新賦值。

27.2、使用方式
  • 修飾類

格式:

final class 類名 {

}


查詢API發現像 public final class String 、 public final class Math 、 public final class Scanner等,很多我們學習過的類,都是被final修飾的,目的就是供我們使用,而不讓我們所以改變其內容。

  • 修飾方法

格式如下:

修飾符 final 返回值型別 方法名(引數列表){
//方法體
}

重寫被 final 修飾的方法,編譯時就會報錯。

例子:

父類:

/*
當final關鍵字用來修飾一個方法的時候,這個方法就是最終方法,也就是不能被覆蓋重寫。
格式:
修飾符 final 返回值型別 方法名稱(引數列表) {
    // 方法體
}

注意事項:
對於類、方法來說,abstract關鍵字和final關鍵字不能同時使用,因為矛盾。
 */
public abstract class Fu {

    public final void method() {
        System.out.println("父類方法執行!");
    }

    public abstract /*final*/ void methodAbs() ;

}


子類:

public class Zi extends Fu {
    @Override
    public void methodAbs() {

    }

    // 錯誤寫法!不能覆蓋重寫父類當中final的方法
//    @Override
//    public void method() {
//        System.out.println("子類覆蓋重寫父類的方法!");
//    }
}


  • 修飾變數

1、區域性變數——基本型別

基本型別的區域性變數,被final修飾後,只能賦值一次,不能再更改。程式碼如下:

public class FinalDemo1 {

public static void main(String[] args) {
    //	宣告變數,使用final修飾 
    final int a;
    //	第一次賦值
    a = 10;
    //	第二次賦值
    a = 20; // 報錯,不可重新賦值
    //	宣告變數,直接賦值,使用final修飾 
    final int b = 10;
    //	第二次賦值
    b = 20; // 報錯,不可重新賦值
	}
}

特別例子:

寫法1:
final int c = 0;
for (int i = 0; i < 10; i++) {
c = i;
System.out.println(c);
}			//錯誤寫法

寫法2:
for (int i = 0; i < 10; i++) {
final int c = i;		//每次迴圈都是重新建立
System.out.println(c);
}
			//正確

2、區域性變數——引用型別

引用型別的區域性變數,被final修飾後,只能指向一個物件,地址不能再更改。但是不影響物件內部的成員變數值的修改,程式碼如下:

public class FinalDemo2 {

public static void main(String[] args) {
//	建立 User 物件
final User u = new User();
//	建立 另一個 User物件
u = new User(); // 報錯,指向了新的物件,地址值改變。
// 呼叫setName方法
u.setName("張三"); // 可以修改
	}
}


3、成員變數

成員變數涉及到初始化的問題,初始化方式有兩種,只能二選一:

1)顯示初始化

public class User {
final String USERNAME = "張三";
private int age;
}

2)構造方法初始化

public class User {

final String USERNAME ;
private int age;
public User(String username, int age) {
this.USERNAME = username;
this.age = age;
	}
}

被final修飾的常量名稱,一般都有書寫規範,所有字母都大寫。

28、許可權修飾符

28.1、概述

在Java中提供了四種訪問許可權,使用不同的訪問許可權修飾符修飾時,被修飾的內容會有不同的訪問許可權,

​ public:公共的。

​ protected:受保護的

​ default:預設的

​ private:私有的

28.2、不同許可權的訪問能力
Java中有四種許可權修飾符:
                    public  >   protected   >   (default)   >   private
同一個類(我自己)        YES         YES             YES             YES
同一個包(我鄰居)        YES         YES             YES             NO
不同包子類(我兒子)       YES         YES             NO              NO
不同包非子類(陌生人)      YES         NO              NO             NO

注意事項:(default)並不是關鍵字“default”,而是根本不寫。

不同包:這裡要注意,如果在一個包裡巢狀一個包,這兩個包不能算是同一個包;

可見,public具有最大許可權。private則是最小許可權。

編寫程式碼時,如果沒有特殊的考慮,建議這樣使用許可權:

​ 成員變數使用 private ,隱藏細節。

​ 構造方法使用 public ,方便建立物件

​ 成員方法使用 public,方便呼叫方法。

小貼士:不加許可權修飾符,其訪問能力與default修飾符相同

29、內部類

29.1、概述

什麼是內部類

將一個類A定義在另一個類B裡面,裡面的那個類A就稱為內部類,B則稱為外部類。

成員內部類

  • 成員內部類 :定義在類中方法外的類。

定義格式:

class 外部類 {
	class 內部類{
	}
}


訪問特點:

1、 內部類可以直接訪問外部類的成員,包括私有成員。

2、外部類要訪問內部類的成員,必須要建立內部類的物件。

建立內部類物件格式:

外部類名.內部類名 物件名 = new 外部型別().new 內部型別();

例子:

public class Body { // 外部類

    public class Heart { // 成員內部類

        // 內部類的方法
        public void beat() {
            System.out.println("心臟跳動:蹦蹦蹦!");
            System.out.println("我叫:" + name); // 正確寫法!
        }

    }

    // 外部類的成員變數
    private String name;

    // 外部類的方法
    public void methodBody() {
        System.out.println("外部類的方法");
        new Heart().beat();  //訪問內部類的方法
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

內部類仍然是一個獨立的類,在編譯之後會內部類會被編譯成獨立的.class檔案,但是前面冠以外部類的類名和$符號 。

比如,Person$Heart.class【所以前面名聲不建議$來命名】

  • 如果出現變數重名
// 如果出現了重名現象,那麼格式是:外部類名稱.this.外部類成員變數名
public class Outer {

    int num = 10; // 外部類的成員變數

    public class Inner /*extends Object*/ {

        int num = 20; // 內部類的成員變數

        public void methodInner() {
            int num = 30; // 內部類方法的區域性變數
            System.out.println(num); // 區域性變數,就近原則
            System.out.println(this.num); // 內部類的成員變數
            System.out.println(Outer.this.num); // 外部類的成員變數
        }

    }

}


29.2、匿名內部類【重點】

匿名內部類 :是內部類的簡化寫法。它的本質是一個 帶具體實現的 父類或者父介面的 匿名的 子類物件。

開發中,最常用到的內部類就是匿名內部類了。以介面舉例,當你使用一個介面時,似乎得做如下幾步操作,

  1. 定義子類

  2. 重寫介面中的方法

  3. 建立子類物件

  4. 呼叫重寫後的方法

我們的目的,最終只是為了呼叫方法,那麼能不能簡化一下,把以上四步合成一步呢?匿名內部類就是做這樣的快捷方式。

前提:

匿名內部類必須繼承一個父類或者實現一個父介面。

格式:

new 父類名或者介面名(){
//	方法重寫
@Override
public void method() {
//	執行語句
	}
};

對格式“new 介面名稱() {...}”進行解析:
1. new代表建立物件的動作
2. 介面名稱就是匿名內部類需要實現哪個介面
3. {...}這才是匿名內部類的內容

例子:

介面:

public interface MyInterface {

    void method1(); // 抽象方法

    void method2();

}


測試類:

public class DemoMain {

    public static void main(String[] args) {

        // 使用匿名內部類,但不是匿名物件,物件名稱就叫objA
        MyInterface objA = new MyInterface() {	//內部類,這種就可以new介面
            @Override
            public void method1() {
                System.out.println("匿名內部類實現了方法!111-A");
            }

            @Override
            public void method2() {
                System.out.println("匿名內部類實現了方法!222-A");
            }
        };
        objA.method1();
        objA.method2();
        System.out.println("=================");

        // 使用了匿名內部類,而且省略了物件名稱,也是匿名物件
        new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名內部類實現了方法!111-B");
            }

            @Override
            public void method2() {
                System.out.println("匿名內部類實現了方法!222-B");
            }
        }.method1();
        // 因為匿名物件無法呼叫第二次方法,所以需要再建立一個匿名內部類的匿名物件
        new MyInterface() {
            @Override
            public void method1() {
                System.out.println("匿名內部類實現了方法!111-B");
            }

            @Override
            public void method2() {
                System.out.println("匿名內部類實現了方法!222-B");
            }
        }.method2();
    }

}


另外還要注意幾點問題:

  1. 匿名內部類,在【建立物件】的時候,只能使用唯一一次。
    如果希望多次建立物件,而且類的內容一樣的話,那麼就需要使用單獨定義的實現類了。
  2. 匿名物件,在【呼叫方法】的時候,只能呼叫唯一一次。
    如果希望同一個物件,呼叫多次方法,那麼必須給物件起個名字。
  3. 匿名內部類是省略了【實現類/子類名稱】,但是匿名物件是省略了【物件名稱】
    強調:匿名內部類和匿名物件不是一回事!!!

30、引用型別用法總結

實際的開發中,引用型別的使用非常重要,也是非常普遍的。我們可以在理解基本型別的使用方式基礎上,進一步去掌握引用型別的使用方式。基本型別可以作為成員變數、作為方法的引數、作為方法的返回值,那麼當然引用型別也是可以的。

30.1、class作為成員變數

在定義一個類Role(遊戲角色)時,程式碼如下:

class Role {
int id; // 角色id
int blood; // 生命值
String name; // 角色名稱
}

使用 int 型別表示 角色id和生命值,使用 String 型別表示姓名。此時, String 本身就是引用型別,由於使用的方式類似常量,所以往往忽略了它是引用型別的存在。如果我們繼續豐富這個類的定義,給 Role 增加武器,穿戴裝備等屬性,我們將如何編寫呢?

定義武器類,將增加攻擊能力:

class Weapon {
String name; // 武器名稱
int hurt; // 傷害值
}

定義穿戴盔甲類,將增加防禦能力,也就是提升生命值:

class Armour {
String name;// 裝備名稱
int protect;// 防禦值
}

定義角色類:

class Role {
int id;
int blood;
String name;
//	新增武器屬性
Weapon wp;
//	新增盔甲屬性
Armour ar;
//	提供get/set方法 
public Weapon getWp() {
	return wp;
}
public void setWeapon(Weapon wp) { 
	this.wp = wp;
}
public Armour getArmour() { 
	return ar;
}
public void setArmour(Armour ar) { 
	this.ar = ar;
}
//	攻擊方法
public void attack(){
System.out.println("使用"+ wp.getName() +", 造成"+wp.getHurt()+"點傷害");
}

//	穿戴盔甲
public void wear(){
//	增加防禦,就是增加blood值 this.blood += ar.getProtect();
System.out.println("穿上"+ar.getName()+", 生命值增加"+ar.getProtect());
	}
}

測試類:

public class Test {

public static void main(String[] args) {

    // 建立Weapon 物件
    Weapon wp = new Weapon("屠龍刀" , 999999);

    // 建立Armour 物件
    Armour ar = new Armour("麒麟甲",10000);

    //	建立Role 物件
    Role r = new Role();

    //	設定武器屬性
    r.setWeapon(wp);

    //	設定盔甲屬性
    r.setArmour(ar);
    //	攻擊
    r.attack();
    //	穿戴盔甲 r.wear();
	}
}

輸出結果:
使用屠龍刀,造成999999點傷害
穿上麒麟甲 ,生命值增加10000

30.2、interface作為成員變數

介面是對方法的封裝,對應遊戲當中,可以看作是擴充套件遊戲角色的技能。所以,如果想擴充套件更強大技能,我們在Role 中,可以增加介面作為成員變數,來設定不同的技能。

定義介面

//	法術攻擊	
public interface FaShuSkill {
public abstract void faShuAttack();
}

定義角色類:

public class Role {

FaShuSkill fs;	//介面
public void setFaShuSkill(FaShuSkill fs) {
this.fs = fs;
}

//	法術攻擊
public void faShuSkillAttack(){
System.out.print("發動法術攻擊:");
fs.faShuAttack();
System.out.println("攻擊完畢");
	}
}


定義測試類

public class Test {

public static void main(String[] args) {
    //	建立遊戲角色
    Role role = new Role();
    //	設定角色法術技能
    role.setFaShuSkill(new FaShuSkill() {
        @Override
        public void faShuAttack() {
        System.out.println("縱橫天下");
        }
	});
//	發動法術攻擊
role.faShuSkillAttack();
//	更換技能
role.setFaShuSkill(new FaShuSkill() {
    @Override
    public void faShuAttack() {
    System.out.println("逆轉乾坤");
    }
});
//	發動法術攻擊
role.faShuSkillAttack();
	}
}

輸出結果:
發動法術攻擊:縱橫天下
攻擊完畢
發動法術攻擊:逆轉乾
攻擊完畢

我們使用一個介面,作為成員變數,以便隨時更換技能,這樣的設計更為靈活,增強了程式的擴充套件性。

介面作為成員變數時,對它進行賦值的操作,實際上,是賦給它該介面的一個子類物件。

30.3、 interface作為方法引數和返回值型別

當介面作為方法的引數時,需要傳遞什麼呢?當介面作為方法的返回值型別時,需要返回什麼呢?對,其實都是它的子類物件。 ArrayList 類我們並不陌生,檢視API我們發現,實際上,它是 java.util.List 介面的實現類。所以,當我們看見 List 介面作為引數或者返回值型別時,當然可以將 ArrayList 的物件進行傳遞或返回。

請觀察如下方法:獲取某集合中所有的偶數。

定義方法:

public static List<Integer> getEvenNum(List<Integer> list) {
    //	建立儲存偶數的集合
    ArrayList<Integer> evenList = new ArrayList<>();
    //	遍歷集合list,判斷元素為偶數,就新增到evenList中 
    for (int i = 0; i < list.size(); i++) {
        Integer integer = list.get(i); 
        if (integer % 2 == 0) {
        evenList.add(integer);
		}
	}

    /*
    返回偶數集合
    因為getEvenNum方法的返回值型別是List,而ArrayList是List的子類,
    所以evenList可以返回
    */
    return evenList;
}

呼叫方法:

public class Test {

public static void main(String[] args) {

    // 建立ArrayList集合,並新增數字
    ArrayList<Integer> srcList = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
    srcList.add(i);
    }

    /*
    獲取偶數集合
    因為getEvenNum方法的引數是List,而ArrayList是List的子類,
    所以srcList可以傳遞
    */
    List list = getEvenNum(srcList);
    System.out.println(list);
	}
}

介面作為引數時,傳遞它的子類物件。

介面作為返回值型別時,返回它的子類物件。

31、Object類

30.1、 概述

java.lang.Object類是Java語言中的根類,即所有類的父類。它中描述的所有方法子類都可以使用。在物件例項化的時候,最終找的父類就是Object。

如果一個類沒有特別指定父類, 那麼預設則繼承自Object類。例如:

public class MyClass /*extends Object*/ {
  	// ...
}

根據JDK原始碼及Object類的API文件,Object類當中包含的方法有11個。今天我們主要學習其中的2個:

  • public String toString()`:返回該物件的字串表示。

  • public boolean equals(Object obj)`:指示其他某個物件是否與此物件“相等”。

31.2、toString方法

方法摘要

  • public String toString():返回該物件的字串表示。

toString方法返回該物件的字串表示,其實該字串內容就是物件的型別+@+記憶體地址值。

由於toString方法返回的結果是記憶體地址,而在開發中,經常需要按照物件的屬性得到相應的字串表現形式,因此也需要重寫它。

覆蓋重寫

如果不希望使用toString方法的預設行為,則可以對它進行覆蓋重寫。例如自定義的Person類:

public class Person {  
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    // 省略構造器與Getter Setter
}

小貼士: 在我們直接使用輸出語句輸出物件名的時候,其實通過該物件呼叫了其toString()方法。

快捷鍵:Ctrl+insert,或者右鍵-Generate;

31.2、equals方法

方法摘要

  • public boolean equals(Object obj):指示其他某個物件是否與此物件“相等”。

呼叫成員方法equals並指定引數為另一個物件,則可以判斷這兩個物件是否是相同的。這裡的“相同”有預設和自定義兩種方式。

預設地址比較

如果沒有覆蓋重寫equals方法,那麼Object類中預設進行==運算子的物件地址比較,只要不是同一個物件,結果必然為false。

物件內容比較

如果希望進行物件的內容比較,即所有或指定的部分成員變數相同就判定兩個物件相同,則可以覆蓋重寫equals方法。例如:

import java.util.Objects;

public class Person {	
	private String name;
	private int age;
	
    @Override
    public boolean equals(Object o) {
        // 如果物件地址一樣,則認為相同
        if (this == o)
            return true;
        // 如果引數為空,或者型別資訊不一樣,則認為不同
        if (o == null || getClass() != o.getClass())
            return false;
        // 轉換為當前型別
        Person person = (Person) o;
        // 要求基本型別相等,並且將引用型別交給java.util.Objects類的equals靜態方法取用結果
        return age == person.age && Objects.equals(name, person.name);
    }
}

31.3、Object類

在剛才IDEA自動重寫equals程式碼中,使用到了java.util.Objects類,那麼這個類是什麼呢?

JDK7添加了一個Objects工具類,它提供了一些方法來操作物件,它由一些靜態的實用方法組成,這些方法是null-save(空指標安全的)或null-tolerant(容忍空指標的),用於計算物件的hashcode、返回物件的字串表示形式、比較兩個物件。

在比較兩個物件的時候,Object的equals方法容易丟擲空指標異常,而Objects類中的equals方法就優化了這個問題。方法如下:

  • public static boolean equals(Object a, Object b):判斷兩個物件是否相等。

我們可以檢視一下原始碼,學習一下:

public static boolean equals(Object a, Object b) {  
    return (a == b) || (a != null && a.equals(b));  
}

32、日期時間類

32.1、Date類

java.util.Date類 表示特定的瞬間,精確到毫秒。

繼續查閱Date類的描述,發現Date擁有多個建構函式,只是部分已經過時,但是其中有未過時的建構函式可以把毫秒值轉成日期物件。

  • public Date():分配Date物件並初始化此物件,以表示分配它的時間(精確到毫秒)。
  • public Date(long date):分配Date物件並初始化此物件,以表示自從標準基準時間(稱為“曆元(epoch)”,即1970年1月1日00:00:00 GMT)以來的指定毫秒數。

提示: 由於我們處於東八區,所以我們的基準時間為1970年1月1日8時0分0秒。

簡單來說:使用無參構造,可以自動設定當前系統時間的毫秒時刻;指定long型別的構造引數,可以自定義毫秒時刻。例如:

import java.util.Date;

public class Demo01Date {
    public static void main(String[] args) {
        // 建立日期物件,把當前的時間
        System.out.println(new Date()); // Tue Jan 16 14:37:35 CST 2018
        // 建立日期物件,把當前的毫秒值轉成日期物件
        System.out.println(new Date(0L)); // Thu Jan 01 08:00:00 CST 1970
    }
}

tips:在使用println方法時,會自動呼叫Date類中的toString方法。Date類對Object類中的toString方法進行了覆蓋重寫,所以結果為指定格式的字串。

32.2、常用方法

Date類中的多數方法已經過時,常用的方法有:

  • public long getTime() 把日期物件轉換成對應的時間毫秒值。

例子:

    private static void demo03() {
        Date date=new Date();
        Long time=date.getTime();
        System.out.println(time);
    }

另外一種方式獲取計算當前已經經歷了多少毫秒:

package com.itheima.demo02.Date;
/*
    java.util.Date:表示日期和時間的類
    類 Date 表示特定的瞬間,精確到毫秒。
    毫秒:千分之一秒 1000毫秒=1秒
    特定的瞬間:一個時間點,一剎那時間
    2088-08-08 09:55:33:333 瞬間
    2088-08-08 09:55:33:334 瞬間
    2088-08-08 09:55:33:334 瞬間
    ...
    毫秒值的作用:可以對時間和日期進行計算
    2099-01-03 到 2088-01-01 中間一共有多少天
    可以日期轉換為毫秒進行計算,計算完畢,在把毫秒轉換為日期

    把日期轉換為毫秒:
        當前的日期:2088-01-01
        時間原點(0毫秒):1970 年 1 月 1 日 00:00:00(英國格林威治)
        就是計算當前日期到時間原點之間一共經歷了多少毫秒 (3742767540068L)
    注意:
        中國屬於東八區,會把時間增加8個小時
        1970 年 1 月 1 日 08:00:00

    把毫秒轉換為日期:
        1 天 = 24 × 60 × 60 = 86400 秒  = 86400 x 1000 = 86400000毫秒
 */
public class Demo01Date {
    public static void main(String[] args) {
        System.out.println(System.currentTimeMillis());//獲取當前系統時間到1970 年 1 月 1 日 00:00:00經歷了多少毫秒
    }
}


32.3、DateFormat類

java.text.DateFormat 是日期/時間格式化子類的抽象類,我們通過這個類可以幫我們完成日期和文字之間的轉換,也就是可以在Date物件與String物件之間進行來回轉換。

  • 格式化:按照指定的格式,從Date物件轉換為String物件。
  • 解析:按照指定的格式,從String物件轉換為Date物件。

記住:DateFormat是一個抽象類,不可以直接建立物件;

構造方法

由於DateFormat為抽象類,不能直接使用,所以需要常用的子類java.text.SimpleDateFormat。這個類需要一個模式(格式)來指定格式化或解析的標準。構造方法為:

  • public SimpleDateFormat(String pattern):用給定的模式和預設語言環境的日期格式符號構造SimpleDateFormat。

引數pattern是一個字串,代表日期時間的自定義格式。

格式規則

常用的格式規則為:

標識字母(區分大小寫) 含義
y
M
d
H
m
s

備註:更詳細的格式規則,可以參考SimpleDateFormat類的API文件0。

建立SimpleDateFormat物件的程式碼如:

import java.text.DateFormat;
import java.text.SimpleDateFormat;

public class Demo02SimpleDateFormat {
    public static void main(String[] args) {
        // 對應的日期格式如:2018-01-16 15:06:38
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }    
}

常用方法

DateFormat類的常用方法有:

  • public String format(Date date):將Date物件格式化為字串。
  • public Date parse(String source):將字串解析為Date物件。

format方法

使用format方法的程式碼為:

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
 把Date物件轉換成String
*/
public class Demo03DateFormatMethod {
    public static void main(String[] args) {
        Date date = new Date();
        // 建立日期格式化物件,在獲取格式化物件時可以指定風格
        DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
        String str = df.format(date);
        System.out.println(str); // 2008年1月23日
    }
}

parse方法

使用parse方法的程式碼為:

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
 把String轉換成Date物件
*/
public class Demo04DateFormatMethod {
    public static void main(String[] args) throws ParseException {
        DateFormat df = new SimpleDateFormat("yyyy年MM月dd日");
        String str = "2018年12月11日";
        Date date = df.parse(str);
        System.out.println(date); // Tue Dec 11 00:00:00 CST 2018
    }
}

32.3、練習

請使用日期時間相關的API,計算出一個人已經出生了多少天。

思路:

1.獲取當前時間對應的毫秒值

2.獲取自己出生日期對應的毫秒值

3.兩個時間相減(當前時間– 出生日期)

程式碼:

public static void function() throws Exception {
	System.out.println("請輸入出生日期 格式 YYYY-MM-dd");
	// 獲取出生日期,鍵盤輸入
	String birthdayString = new Scanner(System.in).next();
	// 將字串日期,轉成Date物件
	// 建立SimpleDateFormat物件,寫日期模式
	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
	// 呼叫方法parse,字串轉成日期物件
	Date birthdayDate = sdf.parse(birthdayString);	
	// 獲取今天的日期物件
	Date todayDate = new Date();	
	// 將兩個日期轉成毫秒值,Date類的方法getTime
	long birthdaySecond = birthdayDate.getTime();
	long todaySecond = todayDate.getTime();
	long secone = todaySecond-birthdaySecond;	
	if (secone < 0){
		System.out.println("還沒出生呢");
	} else {
		System.out.println(secone/1000/60/60/24);
	}
}

32.4、Calendar類

java.util.Calendar是日曆類,在Date後出現,替換掉了許多Date的方法。該類將所有可能用到的時間資訊封裝為靜態成員變數,方便獲取。日曆類就是方便獲取各個時間屬性的。

獲取方式

Calendar為抽象類,由於語言敏感性,Calendar類在建立物件時並非直接建立,而是通過靜態方法建立,返回子類物件,如下:

Calendar靜態

  • public static Calendar getInstance():使用預設時區和語言環境獲得一個日曆

例如:

import java.util.Calendar;

public class Demo06CalendarInit {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
    }    
}

常用方法

根據Calendar類的API文件,常用方法有:

  • public int get(int field):返回給定日曆欄位的值。
  • public void set(int field, int value):將給定的日曆欄位設定為給定值。
  • public abstract void add(int field, int amount):根據日曆的規則,為給定的日曆欄位新增或減去指定的時間量。
  • public Date getTime():返回一個表示此Calendar時間值(從曆元到現在的毫秒偏移量)的Date物件。

Calendar類中提供很多成員常量,代表給定的日曆欄位:

欄位值 含義
YEAR
MONTH 月(從0開始,可以+1使用)
DAY_OF_MONTH 月中的天(幾號)
HOUR 時(12小時制)
HOUR_OF_DAY 時(24小時制)
MINUTE
SECOND
DAY_OF_WEEK 週中的天(周幾,週日為1,可以-1使用)

get/set方法

get方法用來獲取指定欄位的值,set方法用來設定指定欄位的值,程式碼使用演示:

import java.util.Calendar;

public class CalendarUtil {
    public static void main(String[] args) {
        // 建立Calendar物件
        Calendar cal = Calendar.getInstance();
        // 設定年 
        int year = cal.get(Calendar.YEAR);
        // 設定月
        int month = cal.get(Calendar.MONTH) + 1;
        // 設定日
        int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日");
    }    
}

set方法:

import java.util.Calendar;

public class Demo07CalendarMethod {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, 2020);
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2020年1月17日
    }
}

add方法

add方法可以對指定日曆欄位的值進行加減操作,如果第二個引數為正數則加上偏移量,如果為負數則減去偏移量。程式碼如:

import java.util.Calendar;

public class Demo08CalendarMethod {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2018年1月17日
        // 使用add方法
        cal.add(Calendar.DAY_OF_MONTH, 2); // 加2天
        cal.add(Calendar.YEAR, -3); // 減3年
        System.out.print(year + "年" + month + "月" + dayOfMonth + "日"); // 2015年1月18日; 
    }
}

getTime方法

Calendar中的getTime方法並不是獲取毫秒時刻,而是拿到對應的Date物件。

import java.util.Calendar;
import java.util.Date;

public class Demo09CalendarMethod {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        Date date = cal.getTime();
        System.out.println(date); // Tue Jan 16 16:03:09 CST 2018
    }
}

小貼士:

​ 西方星期的開始為週日,中國為週一。

​ 在Calendar類中,月份的表示是以0-11代表1-12月。

​ 日期是有大小關係的,時間靠後,時間越大。

33、System類

java.lang.System類中提供了大量的靜態方法,可以獲取與系統相關的資訊或系統級操作,在System類的API文件中,常用的方法有:

  • public static long currentTimeMillis():返回以毫秒為單位的當前時間。
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):將陣列中指定的資料拷貝到另一個數組中。
33.1、currentTimeMillis方法

實際上,currentTimeMillis方法就是 獲取當前系統時間與1970年01月01日00:00點之間的毫秒差值

import java.util.Date;

public class SystemDemo {
    public static void main(String[] args) {
       	//獲取當前時間毫秒值
        System.out.println(System.currentTimeMillis()); // 1516090531144
    }
}

33.2、arraycopy方法
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):將陣列中指定的資料拷貝到另一個數組中。

陣列的拷貝動作是系統級的,效能很高。System.arraycopy方法具有5個引數,含義分別為:

引數序號 引數名稱 引數型別 引數含義
1 src Object 源陣列
2 srcPos int 源陣列索引起始位置
3 dest Object 目標陣列
4 destPos int 目標陣列索引起始位置
5 length int 複製元素個數

例子:

將src陣列中前3個元素,複製到dest陣列的前3個位置上覆制元素前:src陣列元素[1,2,3,4,5],dest陣列元素[6,7,8,9,10]複製元素後:src陣列元素[1,2,3,4,5],dest陣列元素[1,2,3,9,10]

import java.util.Arrays;

public class Demo11SystemArrayCopy {
    public static void main(String[] args) {
        int[] src = new int[]{1,2,3,4,5};
        int[] dest = new int[]{6,7,8,9,10};
        System.arraycopy( src, 0, dest, 0, 3);
        /*程式碼執行後:兩個陣列中的元素髮生了變化
         src陣列元素[1,2,3,4,5]
         dest陣列元素[1,2,3,9,10]
        */
    }
}

34、string類

34.1、字串拼接問題

由於String類的物件內容不可改變,所以每當進行字串拼接時,總是會在記憶體中建立一個新的物件。例如:

public class StringDemo {
    public static void main(String[] args) {
        String s = "Hello";
        s += "World";
        System.out.println(s);
    }
}

在API中對String類有這樣的描述:字串是常量,它們的值在建立後不能被更改。

根據這句話分析我們的程式碼,其實總共產生了三個字串,即"Hello""World""HelloWorld"。引用變數s首先指向Hello物件,最終指向拼接出來的新字串物件,即HelloWord

由此可知,如果對字串進行拼接操作,每次拼接,都會構建一個新的String物件,既耗時,又浪費空間。為了解決這一問題,可以使用java.lang.StringBuilder類。

34.2、StringBuilder概述

查閱java.lang.StringBuilder的API,StringBuilder又稱為可變字元序列,它是一個類似於 String 的字串緩衝區,通過某些方法呼叫可以改變該序列的長度和內容。

原來StringBuilder是個字串的緩衝區,即它是一個容器,容器中可以裝很多字串。並且能夠對其中的字串進行各種操作。

它的內部擁有一個數組用來存放字串內容,進行字串拼接時,直接在陣列中加入新內容。StringBuilder會自動維護陣列的擴容。原理如下圖所示:(預設16字元空間,超過自動擴充)

【這裡就說明了string類和stringbuilder類拼接字串的區別】

34.3、構造方法

根據StringBuilder的API文件,常用構造方法有2個:

  • public StringBuilder():構造一個空的StringBuilder容器。
  • public StringBuilder(String str):構造一個StringBuilder容器,並將字串新增進去。
public class StringBuilderDemo {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder();
        System.out.println(sb1); // (空白)
        // 使用帶參構造
        StringBuilder sb2 = new StringBuilder("itcast");
        System.out.println(sb2); // itcast
    }
}

34.4、常用的方法

StringBuilder常用的方法有2個:

  • public StringBuilder append(...):新增任意型別資料的字串形式,並返回當前物件自身。
  • public String toString():將當前StringBuilder物件轉換為String物件。

append方法

append方法具有多種過載形式,可以接收任意型別的引數。任何資料作為引數都會將對應的字串內容新增到StringBuilder中。例如:

public class Demo02StringBuilder {
	public static void main(String[] args) {
		//建立物件
		StringBuilder builder = new StringBuilder();
		//public StringBuilder append(任意型別)
		StringBuilder builder2 = builder.append("hello");
		//對比一下
		System.out.println("builder:"+builder);
		System.out.println("builder2:"+builder2);
		System.out.println(builder == builder2); //true
	    // 可以新增 任何型別
		builder.append("hello");
		builder.append("world");
		builder.append(true);
		builder.append(100);
		// 在我們開發中,會遇到呼叫一個方法後,返回一個物件的情況。然後使用返回的物件繼續呼叫方法。
        // 這種時候,我們就可以把程式碼現在一起,如append方法一樣,程式碼如下
		//鏈式程式設計
		builder.append("hello").append("world").append(true).append(100);
		System.out.println("builder:"+builder);
	}
}

備註:StringBuilder已經覆蓋重寫了Object當中的toString方法。

toString方法

通過toString方法,StringBuilder物件將會轉換為不可變的String物件。如:

/*
    StringBuilder和String可以相互轉換:
    String->StringBuilder:可以使用StringBuilder的構造方法
    StringBuilder(String str) 構造一個字串生成器,並初始化為指定的字串內容。
    StringBuilder->String:可以使用StringBuilder中的toString方法
    public String toString():將當前StringBuilder物件轉換為String物件。
 */
public class Demo16StringBuilder {
    public static void main(String[] args) {
        // 鏈式建立
        StringBuilder sb = new StringBuilder("Hello").append("World").append("Java");
        // 呼叫方法
        String str = sb.toString();
        System.out.println(str); // HelloWorldJava
    }
}

StringBuffer和Stringbuilder的區別

區別1:執行緒安全

Stringbuffer:執行緒安全,stringbuilder:執行緒不安全。因為stringBuffer的所有公開方法都是synchronized修飾的,而stringBuilder不是;

區別2:緩衝區

stringbuffer中每次獲取toString都會直接使用快取區的toStringCache值來構造一個字串,而stringbuilder則每次都需要複製一次字元陣列,在構造一個字串。

所以,快取區也是堆stringbuffer的一個優化吧;

區別3:效能

stringbuffer是執行緒安全的,它的所有公開方法都是同步的,stringbuilder是沒有對方法加鎖同步的,所以毫無疑問,stringBuilder的效能要遠大於Stringbuffer;

35、包裝類

35.1、概述

Java提供了兩個型別系統,基本型別與引用型別,使用基本型別在於效率,然而很多情況,會建立物件使用,因為物件可以做更多的功能,如果想要我們的基本型別像物件一樣操作,就可以使用基本型別對應的包裝類,如下:

基本型別 對應的包裝類(位於java.lang包中)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
35.2、裝箱與拆箱

基本型別與對應的包裝類物件之間,來回轉換的過程稱為”裝箱“與”拆箱“:

  • 裝箱:從基本型別轉換為對應的包裝類物件。

  • 拆箱:從包裝類物件轉換為對應的基本型別。

用Integer與 int為例:(看懂程式碼即可)

基本數值---->包裝物件(裝箱)

Integer i = new Integer(4);//使用建構函式函式
Integer iii = Integer.valueOf(4);//使用包裝類中的valueOf方法

包裝物件---->基本數值(拆箱)

int num = i.intValue();

例子:

package com.itheima.demo07Integer;
/*
    裝箱:把基本型別的資料,包裝到包裝類中(基本型別的資料->包裝類)
        構造方法:
            Integer(int value) 構造一個新分配的 Integer 物件,它表示指定的 int 值。
            Integer(String s) 構造一個新分配的 Integer 物件,它表示 String 引數所指示的 int 值。
                傳遞的字串,必須是基本型別的字串,否則會丟擲異常 "100" 正確  "a" 拋異常
        靜態方法:
            static Integer valueOf(int i) 返回一個表示指定的 int 值的 Integer 例項。
            static Integer valueOf(String s) 返回儲存指定的 String 的值的 Integer 物件。
    拆箱:在包裝類中取出基本型別的資料(包裝類->基本型別的資料)
        成員方法:
            int intValue() 以 int 型別返回該 Integer 的值。
 */
public class Demo01Integer {
    public static void main(String[] args) {
        //裝箱:把基本型別的資料,包裝到包裝類中(基本型別的資料->包裝類)
        //構造方法
        Integer in1 = new Integer(1);//方法上有橫線,說明方法過時了
        System.out.println(in1);//1 重寫了toString方法

        Integer in2 = new Integer("1");
        System.out.println(in2);//1

        //靜態方法
        Integer in3 = Integer.valueOf(1);
        System.out.println(in3);

        //Integer in4 = Integer.valueOf("a");//NumberFormatException數字格式化異常
        Integer in4 = Integer.valueOf("1");
        System.out.println(in4);

        //拆箱:在包裝類中取出基本型別的資料(包裝類->基本型別的資料)
        int i = in1.intValue();
        System.out.println(i);
    }
}


35.3、自動裝箱和自動拆箱

由於我們經常要做基本型別與包裝類之間的轉換,從Java 5(JDK 1.5)開始,基本型別與包裝類的裝箱、拆箱動作可以自動完成。例如:

Integer i = 4;//自動裝箱。相當於Integer i = Integer.valueOf(4);
i = i + 5;//等號右邊:將i物件轉成基本數值(自動拆箱) i.intValue() + 5;
//加法運算完成後,再次裝箱,把基本數值轉成物件。

35.4、基本型別與字串之間的轉換

1)基本型別轉換為String

基本型別轉換String總共有三種方式,檢視課後資料可以得知,這裡只講最簡單的一種方式:

基本型別直接與””相連線即可;如:34+""

2)string型別轉換成基本型別

  • public static byte parseByte(String s):將字串引數轉換為對應的byte基本型別。
  • public static short parseShort(String s):將字串引數轉換為對應的short基本型別。
  • public static int parseInt(String s):將字串引數轉換為對應的int基本型別。
  • public static long parseLong(String s):將字串引數轉換為對應的long基本型別。
  • public static float parseFloat(String s):將字串引數轉換為對應的float基本型別。
  • public static double parseDouble(String s):將字串引數轉換為對應的double基本型別。
  • public static boolean parseBoolean(String s):將字串引數轉換為對應的boolean基本型別。

程式碼使用(僅以Integer類的靜態方法parseXxx為例)如:

public class Demo18WrapperParse {
    public static void main(String[] args) {
        int num = Integer.parseInt("100");
    }
}

注意:如果字串引數的內容無法正確轉換為對應的基本型別,則會丟擲java.lang.NumberFormatException異常。

36、Collection集合

36.1、集合概述

在前面基礎班我們已經學習過並使用過集合ArrayList ,那麼集合到底是什麼呢?

  • 集合:集合是java中提供的一種容器,可以用來儲存多個數據。

集合和陣列既然都是容器,它們有啥區別呢?

  • 陣列的長度是固定的。集合的長度是可變的。
  • 陣列中儲存的是同一型別的元素,可以儲存基本資料型別值。集合儲存的都是物件。而且物件的型別可以不一致。在開發中一般當物件多的時候,使用集合進行儲存。
36.2、集合框架

JAVASE提供了滿足各種需求的API,在使用這些API前,先了解其繼承與介面操作架構,才能瞭解何時採用哪個類,以及類之間如何彼此合作,從而達到靈活應用。

集合按照其儲存結構可以分為兩大類,分別是單列集合java.util.Collection和雙列集合java.util.Map,今天我們主要學習Collection集合,在day04時講解Map集合。

  • Collection:單列集合類的根介面,用於儲存一系列符合某種規則的元素,它有兩個重要的子介面,分別是java.util.Listjava.util.Set。其中,List的特點是元素有序、元素可重複。Set的特點是元素無序,而且不可重複。List介面的主要實現類有java.util.ArrayListjava.util.LinkedListSet介面的主要實現類有java.util.HashSetjava.util.TreeSet

從上面的描述可以看出JDK中提供了豐富的集合類庫,為了便於初學者進行系統地學習,接下來通過一張圖來描述整個集合類的繼承體系。

36.3、Collection 常用功能

Collection是所有單列集合的父介面,因此在Collection中定義了單列集合(List和Set)通用的一些方法,這些方法可用於操作所有的單列集合。方法如下:

  • public boolean add(E e): 把給定的物件新增到當前集合中 。
  • public void clear() :清空集合中所有的元素。
  • public boolean remove(E e): 把給定的物件在當前集合中刪除。
  • public boolean contains(E e): 判斷當前集合中是否包含給定的物件。
  • public boolean isEmpty(): 判斷當前集合是否為空。
  • public int size(): 返回集合中元素的個數。
  • public Object[] toArray(): 把集合中的元素,儲存到陣列中。

方法演示:

import java.util.ArrayList;
import java.util.Collection;

public class Demo1Collection {
    public static void main(String[] args) {
		// 建立集合物件 
    	// 使用多型形式
    	Collection<String> coll = new ArrayList<String>();
    	// 使用方法
    	// 新增功能  boolean  add(String s)
    	coll.add("小李廣");
    	coll.add("掃地僧");
    	coll.add("石破天");
    	System.out.println(coll);

    	// boolean contains(E e) 判斷o是否在集合中存在
    	System.out.println("判斷  掃地僧 是否在集合中"+coll.contains("掃地僧"));

    	//boolean remove(E e) 刪除在集合中的o元素
    	System.out.println("刪除石破天:"+coll.remove("石破天"));
    	System.out.println("操作之後集合中元素:"+coll);
    	
    	// size() 集合中有幾個元素
		System.out.println("集合中有"+coll.size()+"個元素");

		// Object[] toArray()轉換成一個Object陣列
    	Object[] objects = coll.toArray();
    	// 遍歷陣列
    	for (int i = 0; i < objects.length; i++) {
			System.out.println(objects[i]);
		}

		// void  clear() 清空集合
		coll.clear();
		System.out.println("集合中內容為:"+coll);
		// boolean  isEmpty()  判斷是否為空
		System.out.println(coll.isEmpty());  	
	}
}

tips: 有關Collection中的方法可不止上面這些,其他方法可以自行檢視API學習。

37、迭代器

37.1、Iterator介面

在程式開發中,經常需要遍歷集合中的所有元素。針對這種需求,JDK專門提供了一個介面java.util.IteratorIterator介面也是Java集合中的一員,但它與CollectionMap介面有所不同,Collection介面與Map介面主要用於儲存元素,而Iterator主要用於迭代訪問(即遍歷)Collection中的元素,因此Iterator物件也被稱為迭代器。

想要遍歷Collection集合,那麼就要獲取該集合迭代器完成迭代操作,下面介紹一下獲取迭代器的方法:

  • public Iterator iterator(): 獲取集合對應的迭代器,用來遍歷集合中的元素的。

下面介紹一下迭代的概念:

  • 迭代:即Collection集合元素的通用獲取方式。在取元素之前先要判斷集合中有沒有元素,如果有,就把這個元素取出來,繼續在判斷,如果還有就再取出出來。一直把集合中的所有元素全部取出。這種取出方式專業術語稱為迭代。

Iterator介面的常用方法如下:

  • public E next():返回迭代的下一個元素。
  • public boolean hasNext():如果仍有元素可以迭代,則返回 true。

接下來我們通過案例學習如何使用Iterator迭代集合中元素:

public class IteratorDemo {
  	public static void main(String[] args) {
        // 使用多型方式 建立物件
        Collection<String> coll = new ArrayList<String>();

        // 新增元素到集合
        coll.add("串串星人");
        coll.add("吐槽星人");
        coll.add("汪星人");
        //遍歷
        //使用迭代器 遍歷   每個集合物件都有自己的迭代器
        Iterator<String> it = coll.iterator();  //多型
        //  泛型指的是 迭代出 元素的資料型別
        while(it.hasNext()){ //判斷是否有迭代元素
            String s = it.next();//獲取迭代出的元素
            System.out.println(s);
        }
  	}
}

tips:在進行集合元素取出時,如果集合中已經沒有元素了,還繼續使用迭代器的next方法,將會發生java.util.NoSuchElementException沒有集合元素的錯誤。

37.2、迭代器的實現原理

我們在之前案例已經完成了Iterator遍歷集合的整個過程。當遍歷集合時,首先通過呼叫t集合的iterator()方法獲得迭代器物件,然後使用hashNext()方法判斷集合中是否存在下一個元素,如果存在,則呼叫next()方法將元素取出,否則說明已到達了集合末尾,停止遍歷元素。

Iterator迭代器物件在遍歷集合時,內部採用指標的方式來跟蹤集合中的元素,為了讓初學者能更好地理解迭代器的工作原理,接下來通過一個圖例來演示Iterator物件迭代元素的過程:

在呼叫Iterator的next方法之前,迭代器的索引位於第一個元素之前(index=-1),不指向任何元素,當第一次呼叫迭代器的next方法後,迭代器的索引會向後移動一位,指向第一個元素並將該元素返回,當再次呼叫next方法時,迭代器的索引會指向第二個元素並將該元素返回,依此類推,直到hasNext方法返回false,表示到達了集合的末尾,終止對元素的遍歷。

37.3、增強的for(foreach)

增強for迴圈(也稱for each迴圈)是JDK1.5以後出來的一個高階for迴圈,專門用來遍歷陣列和集合的。它的內部原理其實是個Iterator迭代器,所以在遍歷的過程中,不能對集合中的元素進行增刪操作。

格式:

for(元素的資料型別  變數 : Collection集合or陣列){ 
  	//寫操作程式碼
}

它用於遍歷Collection和陣列。通常只進行遍歷元素,不要在遍歷的過程中對集合元素進行增刪操作。

例子1:遍歷陣列

public class NBForDemo1 {
    public static void main(String[] args) {
		int[] arr = {3,5,6,87};
       	//使用增強for遍歷陣列
		for(int a : arr){//a代表陣列中的每個元素
			System.out.println(a);
		}
	}
}

例子2:遍歷集合

public class NBFor {
    public static void main(String[] args) {        
    	Collection<String> coll = new ArrayList<String>();
    	coll.add("小河神");
    	coll.add("老河神");
    	coll.add("神婆");
    	//使用增強for遍歷
    	for(String s :coll){//接收變數s代表 代表被遍歷到的集合元素
    		System.out.println(s);
    	}
	}
}

tips: 新for迴圈必須有被遍歷的目標。目標只能是Collection或者是陣列。新式for僅僅作為遍歷操作出現。

38、泛型

38.1、泛型概述

在前面學習集合時,我們都知道集合中是可以存放任意物件的,只要把物件儲存集合後,那麼這時他們都會被提升成Object型別。當我們在取出每一個物件,並且進行相應的操作,這時必須採用型別轉換。

大家觀察下面程式碼:

public class GenericDemo {
	public static void main(String[] args) {
		Collection coll = new ArrayList();
		coll.add("abc");
		coll.add("itcast");
		coll.add(5);//由於集合沒有做任何限定,任何型別都可以給其中存放
		Iterator it = coll.iterator();
		while(it.hasNext()){
			//需要列印每個字串的長度,就要把迭代出來的物件轉成String型別
			String str = (String) it.next();
			System.out.println(str.length());
		}
	}
}

程式在執行時發生了問題java.lang.ClassCastException

為什麼會發生型別轉換異常呢?

我們來分析下:由於集合中什麼型別的元素都可以儲存。導致取出時強轉引發執行時 ClassCastException。

解決方法:

Collection雖然可以儲存各種物件,但實際上通常Collection只儲存同一型別物件。例如都是儲存字串物件。因此在JDK5之後,新增了泛型(Generic)語法,讓你在設計API時可以指定類或方法支援泛型,這樣我們使用API的時候也變得更為簡潔,並得到了編譯時期的語法檢查。

  • 泛型:可以在類或方法中預支地使用未知的型別。

tips:一般在建立物件時,將未知的型別確定具體的型別。當沒有指定泛型時,預設型別為Object型別。

38.2、使用泛型的好處

上一節只是講解了泛型的引入,那麼泛型帶來了哪些好處呢?

  • 將執行時期的ClassCastException,轉移到了編譯時期變成了編譯失敗。
  • 避免了型別強轉的麻煩。

程式碼:

public class GenericDemo2 {
	public static void main(String[] args) {
        Collection<String> list = new ArrayList<String>();
        list.add("abc");
        list.add("itcast");
        // list.add(5);//當集合明確型別後,存放型別不一致就會編譯報錯
        // 集合已經明確具體存放的元素型別,那麼在使用迭代器的時候,迭代器也同樣會知道具體遍歷元素型別
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String str = it.next();
            //當使用Iterator<String>控制元素型別後,就不需要強轉了。獲取到的元素直接就是String型別
            System.out.println(str.length());
        }
	}
}

解決object型別向下轉時不安全,所以就用string型別,但是這裡就變得很侷限,如果想用別的型別就要重新改程式碼,因此這裡就要用泛型來解決;

tips:泛型是資料型別的一部分,我們將類名與泛型合併一起看做資料型別。

38.3、泛型的定義與使用

我們在集合中會大量使用到泛型,這裡來完整地學習泛型知識。

泛型,用來靈活地將資料型別應用到不同的類、方法、介面當中。將資料型別作為引數進行傳遞。

定義和使用含有泛型的類

定義格式:

修飾符 class 類名<代表泛型的變數> {  }

例如,API中的ArrayList集合:

class ArrayList<E>{ 
    public boolean add(E e){ }

    public E get(int index){ }
   	....
}

使用泛型: 即什麼時候確定泛型。

定義格式:

修飾符 class 類名<代表泛型的變數> {  }

例如,API中的ArrayList集合:

class ArrayList<E>{ 
    public boolean add(E e){ }

    public E get(int index){ }
   	....
}

使用泛型: 即什麼時候確定泛型。

使用泛型: 即什麼時候確定泛型。

在建立物件的時候確定泛型

例如,ArrayList<String> list = new ArrayList<String>();

此時,變數E的值就是String型別,那麼我們的型別就可以理解為:

class ArrayList<String>{ 
     public boolean add(String e){ }

     public String get(int index){  }
     ...
}

再例如,ArrayList<Integer> list = new ArrayList<Integer>();

此時,變數E的值就是Integer型別,那麼我們的型別就可以理解為:

class ArrayList<Integer> { 
     public boolean add(Integer e) { }

     public Integer get(int index) {  }
     ...
}

舉例自定義泛型類

public class MyGenericClass<T> {
	//沒有MVP型別,在這裡代表 未知的一種資料型別 未來傳遞什麼就是什麼型別
	private T mvp;
     
    public void setMVP(T mvp) {
        this.mvp = mvp;
    }
     
    public T getMVP() {
        return mvp;
    }
}

使用:

public class GenericClassDemo {
  	public static void main(String[] args) {		 
         // 建立一個泛型為String的類
         MyGenericClass<String> my = new MyGenericClass<String>();    	
         // 呼叫setMVP
         my.setMVP("大鬍子登登");
         // 呼叫getMVP
         String mvp = my.getMVP();
         System.out.println(mvp);
         //建立一個泛型為Integer的類
         MyGenericClass<Integer> my2 = new MyGenericClass<Integer>(); 
         my2.setMVP(123);   	  
         Integer mvp2 = my2.getMVP();
    }
}

38.4、含有泛型的方法

定義格式:

修飾符 interface介面名<代表泛型的變數> {  }

例如,

public interface MyGenericInterface<E>{
	public abstract void add(E e);
	
	public abstract E getE();  
}

使用格式:

1、定義類時確定泛型的型別

例如

public class MyImp1 implements MyGenericInterface<String> {
	@Override
    public void add(String e) {
        // 省略...
    }

	@Override
	public String getE() {
		return null;
	}
}

此時,泛型E的值就是String型別。

2、始終不確定泛型的型別,直到建立物件時,確定泛型的型別

例如

public class MyImp2<E> implements MyGenericInterface<E> {
	@Override
	public void add(E e) {
       	 // 省略...
	}

	@Override
	public E getE() {
		return null;
	}
}

確定泛型:

/*
 * 使用
 */
public class GenericInterface {
    public static void main(String[] args) {
        MyImp2<String>  my = new MyImp2<String>();  
        my.add("aa");
    }
}

圖解:

38.5、泛型萬用字元

當使用泛型類或者介面時,傳遞的資料中,泛型型別不確定,可以通過萬用字元<?>表示。但是一旦使用泛型的萬用字元後,只能使用Object類中的共性方法,集合中元素自身方法無法使用。

  • 萬用字元基本使用

泛型的萬用字元:不知道使用什麼型別來接收的時候,此時可以使用?,?表示未知萬用字元。

此時只能接受資料,不能往該集合中儲存資料。

舉個例子大家理解使用即可:

/*
    泛型的萬用字元:
        ?:代表任意的資料型別
    使用方式:
        不能建立物件使用
        只能作為方法的引數使用
 */
public class Demo05Generic {
    public static void main(String[] args) {
        ArrayList<Integer> list01 = new ArrayList<>();
        list01.add(1);
        list01.add(2);

        ArrayList<String> list02 = new ArrayList<>();
        list02.add("a");
        list02.add("b");

        printArray(list01);
        printArray(list02);

        //ArrayList<?> list03 = new ArrayList<?>();
    }

    /*
        定義一個方法,能遍歷所有型別的ArrayList集合
        這時候我們不知道ArrayList集合使用什麼資料型別,可以泛型的萬用字元?來接收資料型別
        注意:
            泛型沒有繼承概念的
     */
    public static void printArray(ArrayList<?> list){
        //使用迭代器遍歷集合
        Iterator<?> it = list.iterator();
        while(it.hasNext()){
            //it.next()方法,取出的元素是Object,可以接收任意的資料型別
            Object o = it.next();
            System.out.println(o);
        }
    }
}

tips:泛型不存在繼承關係 Collection