?寫在前面
- 💖作者簡介:大家好,我是 kitty_Happy,
- 😉正在學習的小白一名,很樂于與大家交流各種技術,共同學習!
- 🐈作者主頁:kitty_Happy
- 🎉點贊 ? 評論 ? 收藏 == 養成習慣😜
- 文章前三章單獨鏈接:4600字的封裝、繼承,多型筆記(推薦收藏)
- 💬 總結:希望你看完之后,能對你有所幫助,不足請指正! 🖊
- ?? 不要被任何人打亂自己的腳步,因為沒有誰會像你一樣清楚和在乎自己的夢想,?
文章目錄
- 🍖1、物件和封裝
- 🍟1.1、構造方法
- 🍟1.2、this
- 🍟1.3、方法多載
- 🍕1.3.1、方法多載的定義
- 🍕1.3.2、方法多載的特點
- 🍕1.3.3、方法多載的使用和優點
- 🍟1.4、使用封裝重構類的屬性和方法
- 🍕1.3.1、封裝的概念
- 🍕1.4.2、封裝的步驟
- 🧀1、修改屬性的可見性:
- 🧀2、創建賦值和取值方法:
- 🧀3、加入對屬性的存取控制陳述句
- 🍟1.5、類和類成員的訪問控制
- 🍟1.6、static 關鍵字
- 🍕1.6.1、用 static 關鍵字修飾屬性和代碼塊
- 🍖2、繼承
- 🍟2.1、繼承的基本概念
- 🍟2.2、繼承的應用
- 🍟2.3、Object
- 🍟2.4、繼承關系中的方法重寫
- 🍟2.5、方法多載和方法重寫的區別
- 🍟2.6、super關鍵字
- 🍟2.7、繼承關系中的構造方法
- 🍖3、多型
- 🍟3.1、子類到父類的轉換(向上轉型)
- 🍟3.2、父類到子類的轉換(向下轉型)
- 🍟3.3、instanceof 運算子
- 🍟3.4、多型的優勢
- 🍖4、三大特性總結
- 🍖5、抽象類和介面
- 🍟5.1、抽象類
- 🍟5.2、抽象方法
- 🍟5.3、final 修飾符
- 🍟5.3、final 與 abstract 修飾符
- 🍟5.4、介面
- 🍕5.4.1、定義和實作介面
- 🍟5.5、面向物件設計原則
- 🍖6、例外
- 🍟6.1、例外處理機制
- 🍟6.2、例外處理結構
- 🍕6.2.1、多重 catch 陳述句
- 🍟6.3、例外繼承結構圖
- 🍟6.4、宣告例外——throws 關鍵字
- 🍟6.5、拋出例外——throw 關鍵字
- 🍟6.6、自定義例外
- 🍟6.7、例外總結
- 🍖7、集合框架
- 🍟7.1、集合結構
- 🍕7.1.2、Collection 介面
- 🍟7.2、List
- 🍕7.2.1、List 概述
- 🍕7.2.2、ArrayList 集合類
- 🍕7.2.3、LinkedList 集合類
- 🍟7.3、Set
- 🍕7.3.1、HashSet 集合
- 🍕7.3.2、TreeSet 集合類
- 🍟7.4、Map
- 🍕7.4.1、HashMap 集合類
- 🍟7.5、集合總結
- 🍖8、包裝類
- 🍟8.1、包裝類和基本資料型別的轉換
- 🍕8.1.1、基本資料型別轉換為包裝類
- 🍕8.1.2、包裝類轉換為基本資料型別
- 🍟8.2、裝箱拆箱
- 🍖9、I / O 流
- 🍟9.1、File 類
- 🍟9.2、I / O 流概述
- 🍕9.2.1、按流向劃分:輸入流和輸入流
- 🍕9.2.2、按處理單元劃分:位元組流和字符流
- 🍕9.2.3、按流的角色劃分:節點流和處理流
- 🍟9.3、位元組流
- 🍕9.3.1、位元組輸出流基類:OutputSteam
- 🍕9.3.2、位元組輸出流 FileOutputStream 類
- 🍕9.3.3、位元組輸入流基類:InputStream
- 🍕9.3.4、位元組輸入流 FileInputStream 類
- 🍟9.4、字符流
- 🍕9.4.1、字符輸出流基類:Writer
- 🍕9.4.2、字符輸出流 FileWriter 類
- 🍕9.4.3、字符輸入流基類:Reader
- 🍕9.4.4、字符輸入流 FileReader
- 🍟9.5、緩沖流
- 🍕9.5.1、字符緩沖輸出流 BufferedWriter 類
- 🍕9.5.2、字符緩沖輸入流 BufferedReader 類
- 🍟9.6、資料操作流
- 🍕9.6.1、DataOutputStream 類和 DataInputStream 類
- 🍟9.7、序列化與反序列化
- 🍕9.7.1、物件輸出流 ObjectOutputStream 實作序列化
- 🍕9.7.2、物件輸入流 ObjectInputStream 實作反序列化
- 🍕9.7.3、transient 關鍵字
- 🍕9.7.4、Java 體系中常用流總結
- 🍖10、多 線 程
- 🍟10.1、了解多執行緒
- 🍕10.1.1、行程與執行緒
- 🍕10.1.2、多執行緒的運行機制
- 🍕10.1.3、多執行緒的優勢
- 🍟10.2、多執行緒編程
- 🍕10.2.1、Thread 類介紹
- 🍕10.2.2、繼承 Thread 類創建執行緒類
- 🍕10.2.3、實作 Runnable 介面創建執行緒類
- 🍟10.3、執行緒的生命周期
- 🍟10.4、執行緒調度
- 🍕10.4.1、執行緒調度相關方法
- 🍕10.4.2、執行緒的優先級
- 🍟10.5、執行緒同步
- 🍕10.5.1、實作執行緒的同步
- 🍕1、同步代碼塊
- 🍕2、同步方法
- 🍟10.6、執行緒同步的特征
🍖1、物件和封裝
🍟1.1、構造方法
語法:
[ 訪問修飾符 ] 方法名 ( [ 引數串列 ] ){
//……省略方法體的代碼
}
-
構造方法的名稱必須和類名相同,但是沒有回傳值型別,在構造方法的定義中引數串列是可選的,因此可以定義無參構造方法,也可以定義帶參構造方法,
-
在java中,每一個類都默認自帶了一個構造方法叫做預設構造器,如有沒有創建,則系統會自動創建一個默認的構造方法,默認構造方法是沒有引數的,而且在方法體中沒有代碼,(無參構造器)
注意: 當我們創建了構造方法之后,Java自帶的無參構造方法就會消失,需要我們自己寫出來
🍟1.2、this
this 的三種用法:
1、使用 this 關鍵字呼叫成員變數,解決成員變數和區域變數的同名沖突
public Strive(String name){
this.name = name; //成員變數和區域變數同名,必須使用 this 關鍵字
}
2、使用 this 關鍵字呼叫成員方法
public Strive(String name){
this.name = name;
this.print(); // 呼叫成員方法
}
3.使用 this 關鍵字呼叫已定義的構造方法
在以下代碼中定義了 Strive 類,并定義了兩個帶參構造方法
public void Strive(){
String name;
String sex;
int age;
String dept;
public Strive(String name, String sex){
this.name = name;
this.sex = sex;
}
public Strive(String name, String sex, int age, String dept){
this(name,sex); //呼叫已定義的構造方法(呼叫上面那個構造方法)
this.age = age;
this.dept = dept;
}
}
以上使用 this 關鍵字呼叫構造方法,并傳入區域變數 name 和 sex 作為引數,執行時,首先執行 Strive(String name,String sex) 構造方法,完成對 name 屬性和 sex 屬性的賦值,然后執行后面的陳述句實作對 age 屬性和 dept 屬性的賦值,
🍟1.3、方法多載
🍕1.3.1、方法多載的定義
方法多載指的是同一個類包含兩個或兩個以上的方法,它們的方法名相同,方法引數個數不同,
方法引數型別不同,成員方法和構造方法都可以進行多載
例如:
System.out.println("good");
System.out.println(true);
System.out.println(100);
這里的方法多載使用的是方法引數型別不同
🍕1.3.2、方法多載的特點
方法多載判斷的依據如下:
- 必須在同一個類中
- 方法名相同
- 引數串列 (方法引數的個數或引數型別) 不同
注意: 方法的回傳值和方法的訪問修飾符不能作為判斷方法之間是否構成多載的依據
🍕1.3.3、方法多載的使用和優點
public int Strive(int a,int b){
return a+b;
}
public double Strive(double a,double b){
return a+b;
}
public String Strive(String s1,String s2){
return s1+s2;
}
public static void main(String[] args) {
Adder adder = new Adder();
int a = 6,b = 8;
System.out.println(adder.Strive(a,b));
double c = 9 , d = 10.9;
System.out.println(adder.Strive(c,d));
String s1 = "Hello",s2 = "World";
System.out.println(adder.Strive(s1,s2));
}
- 由以上代碼可見,方法多載是對原有方法的一種升級,可以根據引數的不同,采用不同的實作方法,而且不需要撰寫多個名稱,簡化了類呼叫方法的代碼
🍟1.4、使用封裝重構類的屬性和方法
🍕1.3.1、封裝的概念
封裝是面向物件的三大特性之一,就算將類的狀態資訊隱藏在類內部,不允許外部程式直接訪問,而通過該類提供的方法實作對隱藏資訊的操作和訪問,
封裝反映了事物的相對獨立性,有效避免了外部錯誤對此物件的影響,并且能夠給用戶由于大意產生的錯誤操作起到預防作用
- 好處: 封裝的好處在于隱藏類的細節,讓用戶只能通過開發人員規定的方法訪問資料,可以方便地加入訪問修飾符來限制不合理操作
🍕1.4.2、封裝的步驟
實作封裝的步驟如下:
1、修改屬性的可見性來限制對屬性的訪問
2、 為每個屬性創建一對賦值(setter)方法和取值(getter)方法,用戶對這些屬性的存取
3、在賦值方法中,可以加入對屬性的存取控制陳述句
🧀1、修改屬性的可見性:
使用 private 訪問修飾符修飾,private 修飾的屬性和方法只能在定義它的類的內部被訪問
🧀2、創建賦值和取值方法:
get 方法取值、 set 方法存值
get 方法讀、 set 方法改
以上看哪一個能理解,意思一致,代碼如下:
private String name; // 私有屬性
public void getName(){ //get方法 讀
return name;
}
public void setName(String name){ //set 改
this.name = name;
}
🧀3、加入對屬性的存取控制陳述句
為了限制不合理賦值,除了設定屬性的私有性,還可以在賦值方法中加入對屬性的存取控制陳述句,
假如性別:只有男,女,年齡:0~100 ;代碼如下:
private grnder; // 性別
private int age; // 年齡
// 省略get 方法
public void setAge(int age){ //控制年齡
if(age < 0 || age > 150){
System.out.println("輸入的年齡為:"+age+",該年齡不合法!");
return;
}else{
this.age = age;
}
}
public void setGender(String gender) { //控制性別
if(gender.equals("男") || gender.equals("女")){
this.gender = gender;
}else{
System.out.println("*** 性別不合法 !***");
}
}
🍟1.5、類和類成員的訪問控制
1、類和訪問修飾符
| 修飾符 | 同一包中 | 非同一包中 |
|---|---|---|
| public | 可以使用 | 可以使用 |
| 默認修飾符 | 可以使用 | 不可以使用 |
省略訪問修飾符,就算默認修飾符
2、類成員的訪問修飾符
| 修飾符 | 同一類中 | 同一包中 | 子類中 | 外部包 |
|---|---|---|---|---|
| private | 可以使用 | 不可以使用 | 不可以使用 | 不可以使用 |
| 默認修飾符 | 可以使用 | 可以使用 | 不可以使用 | 不可以使用 |
| protected | 可以使用 | 可以使用 | 可以使用 | 不可以使用 |
| public | 可以使用 | 可以使用 | 可以使用 | 可以使用 |
🍟1.6、static 關鍵字
static 關鍵字可以修飾類的屬性,方法和代碼塊,使用 static 修飾的屬性和方法不再屬于具體的某個物件,而屬性它所在的類
使用 static 修飾的 屬性或方法可以使用 “ 類名 . ” 的方式呼叫,不需要再消耗資源反復創建物件
使用 static 修飾的方法屬于 靜態方法,使用類名呼叫,static 修飾的變數屬于靜態變數,類加載的時候加載進方法區
🍕1.6.1、用 static 關鍵字修飾屬性和代碼塊
如下靜態代碼塊:
static{
System.out.println("HelloWorld");
}
static 修飾的是類加載的時候執行,創建物件的時候執行,創建物件的時候應先執行 static靜態代碼塊里面的內容
注意:
- 有多個靜態代碼塊的時候,按照靜態代碼塊的前后順序執行
- 在方法里不可以定義 static 變數,也就是靜態變數不能是區域變數
- 在靜態方法中不能直接訪問實體變數和實體方法
- 在實體方法中可以直接呼叫類中定義的靜態變數和靜態方法
🍖2、繼承
🍟2.1、繼承的基本概念
繼承是面向物件的三大特征之一,繼承可以解決編程中代碼冗余的問題,是實作代碼重用的重要手段之一,
語法:
[ 訪問修飾符 ] class <SubClass> extends <SuperClass>{}
其中,SubClass 被稱為子類或派生類,SuperClass 被稱為父類或基類,
Java 繼承規則如下:
1、可以繼承父類中 public 和 protected 修飾的屬性和方法,不論子類和父類是否在同一包中,
2、可以繼承默認訪問修飾符修飾的屬性和方法,但是子類和父類必須在同一包中
3、無法繼承 private 修飾的屬性和方法
4、無法繼承父類的構造方法
注意:
在 Java 中只支持單繼承,即每個類只能有一個直接父類
🍟2.2、繼承的應用
如下:
/**
*動物父類
*/
public class Animal {
public void cry(){
System.out.println("動物叫,,,,");
}
}
/**
* 繼承了動物類的小貓類也有 cry() 方法(子類)
*/
public class Cat extends Animal {
// 有父類的方法與屬性
}
🍟2.3、Object
Object 類屬性超級父類(老祖宗),當一個類沒有任何繼承的時候,默認繼承 Object 類,自帶 Object 類里面的 方法與屬性
注意:
子類被創建物件的時候必是先執行 Object 類的構造方法,因為構造方法第一行中有隱藏的 super() 呼叫父類構造方法,最終的父類一定是 Object 類
🍟2.4、繼承關系中的方法重寫
子類通過繼承可以擁有和父類相同的特征和行為,另外,子類也可以定義自己特有的行為,既沿襲了父類的方法名稱,又重新實作了父類方法,這就是方法重寫,
在子類中可以根據需求對從父類繼承的方法進行重寫撰寫,這被稱為方法重寫或方法覆寫,
方法重寫必須遵守以下規則:
- 重寫方法和被重寫方法必須具有相同的方法名
- 重寫方法和被重寫方法必須具有相同的引數串列
- 重寫方法的回傳值型別必須和被重寫方法的回傳值型別相同或是其子類
- 重寫方法不能縮小被重寫方法的訪問權限
重寫的時候可以在方法上面使用注解:
@Override
@Override 是用 Java 注解的方法表示該方法重寫了父類方法,可以寫也可以不寫,在功能實作上沒有區別,但是通過 @Override 注解,程式更加方便閱讀,另外,編譯器也會幫助驗證 @Override 下面的方法名是否是父類所有的,如果其不符合方法重寫規則,則會報錯,
提示:
Java 注解又被稱為 Java 標注,是 Java 5 引入的一種注解機制,
🍟2.5、方法多載和方法重寫的區別
- 方法多載涉及同一個類中的同名方法,要求方法名相同,引數串列不同,與回傳值型別和訪問修飾符無關
- 方法重寫涉及的是子類和父類之間的同名方法,要求方法名相同,引數串列相同,回傳值型別i相同或是其子類
🍟2.6、super關鍵字
如果想在子類中呼叫父類的被重寫的方法,可以使用 ” super.方法名 “實作
super 關鍵字代表對當前物件的直接父類物件的默認參考,在子類中可以通過 super 關鍵字訪問父類的成員,包括父類的屬性和方法,語法如下:
語法:
訪問父類構造方法 :super(引數)
訪問父類屬性 / 方法 :super.< 父類屬性 / 方法 >
使用 super 關鍵字,需要注意以下幾點:
- super 關鍵字必須出現在子類(子類的方法和構造方法)中,而不允許在其他位置,
- 可以訪問父類的成員,如父類的屬性,方法,構造方法,
- 注意訪問權限的限制,如無法通過 super 關鍵字訪問 private 成員,
注意:
1、在構造方法中如果有 this 陳述句或 super 陳述句,則只能是第一條陳述句,
2、在一個構造方法中,不允許同時使用 this 關鍵字和 super 關鍵字呼叫構造方法(否則就有兩條第一條陳述句),
3、在靜態方法中不允許出現 this 關鍵字或 super 關鍵字,
4、在實體方法中,this 陳述句和 super 陳述句不要求是第一條陳述句,可以共存,
5、子類構造方法中第一行有隱藏的 super( ) 呼叫父類構造方法,最終父類一定是 Object 類
🍟2.7、繼承關系中的構造方法
在 Java 中,一個類的構造方法在如下兩種情況下會被執行:
- 創建該類物件(實體化)
- 創建該類的子類物件(子類的實體化)
子類在實體化時,會首先執行其父類的構造方法,然后才會執行子類的構造方法,
在 Java 語言中,當創建一個物件時,Java 虛擬機(JVM)會按照父類——>子類的順序執行一系列的構造方法,
子類繼承父類時構造方法的呼叫規則如下:
- 如果在類的構造方法中沒有通過 super 關鍵字顯式呼叫父類地帶參構造方法,也沒有通過 this 關鍵字顯式呼叫自身地其他構造方法,則系統會默認先呼叫父類的無參構造方法,在這種情況下,是否寫 “ super( ); ”陳述句,效果是一樣的,
- 如果在子類的構造方法中通過 super 關鍵字顯式地呼叫了父類地帶參構造方法,那么將執行父類相應的構造方法,而不執行父類無參構造方法,
- 如果在子類的構造方法中通過 this 關鍵字顯式地呼叫了自身地其他構造方法,那么在相應構造方法中遵循以上兩條規則,
- 如果存在多級繼承關系,則在創建一個子類物件時,以上規則會多次向上更高一級父類應用,直到執行頂級父類 Object 類的無參構造方法為止,
🍖3、多型
面向物件的三大特性為 封裝、繼承、多型,最后一個特性——多型,它能使同一個操作當作用于不同的物件時,產生不同的執行結果,
使用多型可以提高代碼的可維護性和可擴展性
🍟3.1、子類到父類的轉換(向上轉型)
子類到父類的轉換被稱為向上轉型,(自動型別轉換)
語法:
< 父型別 > < 參考變數名 > = new < 子型別 > ( );
Strive strive1 = new s1();
Strive 為父型別 strive1 為參考變數名 s1 為子型別
- 父型別的參考指向子型別的物件
實作多型的三個條件如下:
1、繼承的存在(繼承是多型的繼承,沒有繼承就沒有多型),
2、子類重寫父類的方法(多型下呼叫子類重寫后的方法),
3、父類參考變數指向子類物件(向上轉型),
🍟3.2、父類到子類的轉換(向下轉型)
父類到子類的轉換被稱為向上轉型,(強制型別轉換)
語法:
< 子型別 > < 參考變數名 > = ( < 子型別 > ) < 父型別的參考變數 >;
Strive strive1 = (Strive)s1;
s1 為 父型別的參考變數,Strive 為子型別,strive1 為參考變數名
🍟3.3、instanceof 運算子
語法:
物件 instanceof 類或介面
- 該運算子用來判斷一個物件是否屬于一個類或實作了一個介面,結果為 true 或 false,
- 在強制型別轉換之前通過 instanceof 運算子檢查物件的真實型別,再進行相應的強制型別轉換,這樣就可以避免型別轉換例外,從而提高代碼的健壯性,
if(Strive instanceof s1){ // 型別判斷
Strive strive1 = (Strive)s1;
}else{
System.out.println("Strive與s1沒有關系");
}
🍟3.4、多型的優勢
- 可替換性:多型對已存在的代碼具有可替換性
- 可擴充性:多型對代碼具有可擴充性,增加新的子類不影響已存在類的多型性,繼承性,以及其他特性的運行和操作,實際上新加子類更容易獲得多型功能,
- 靈活性:在多型的應用中,體現了靈活多樣的操作,提高了使用效率,
- 簡化性:多型簡化了應用軟體的代碼撰寫和修改程序,尤其在處理大量物件的運算和操作時,這個特點尤為突出
多型的使用大多體現在實際開發中,多寫代碼,多用多型,慢慢自然能夠體驗到多型的靈活性以及多型的重要性
🍖4、三大特性總結
面向物件的三大特性:封裝、繼承、多型,
- 封裝就是宣告類的成員變數為私有的,同時提供公有的方法實作對該成員變數的存取操作,
- 繼承是軟體可重用性的一種表現,新類可以在不增加自身代碼的情況下,通過從現有的類中繼承其屬性和方法充實自身內容,這種現象或行為被稱為繼承,
- 多型是具有表現多種形態的能力的特征,在程式設計的術語中,它意味著一個特定型別的變數可以參考不同型別的物件,并且能自動地呼叫參考地物件的方法,也就是根據作用到的不同的物件型別,回應不同的操作,
封裝、繼承、多型三大特性在Java中非常非常非常重要,一定要統統理解
🍖5、抽象類和介面
🍟5.1、抽象類
顧名思義,抽象類就是抽象出來的類,一般來說,具體類有直接對應的物件,而抽象類沒有,它往往表達的是抽象的概念,
例如:
一只貓是具體物件,而貓科動物則是抽象的概念;玉米是具體物件,而作物則是抽象概念,
語法:
< 訪問修飾符 > abstract class < 類名 >{ }
abstract 關鍵字表示該類被定義為抽象類
區別:
抽象類與普通類的最大區別就是:普通類可以被實體化,而抽象類不能被實體化
🍟5.2、抽象方法
當一個類的方法被 abstract 關鍵字修飾時,該方法被稱為抽象方法,
注意:
抽象方法所在的類必須是抽象類,抽象類中可以包含抽象方法,也可以包含普通方法,還可以包含普通類包含的一切成員,
抽象方法:
一個方法被定義為抽象方法,意味著該方法不會有具體的實作(沒有方法體),而在抽象類的子類中通過方法重寫實作,
語法:
[ 訪問修飾符 ] abstract <回傳型別> < 方法名 > ( [ 引數串列 ] )
abstract 關鍵字表示該方法被定義為抽象方法
區別:
抽象方法與普通方法最大的區別是:普通方法有方法體,而抽象方法沒有方法體,
public void print(){ } // 普通方法
public abstract void print(); // 抽象方法
總結:
1、在抽象類中,可以沒有、有一個或多個抽象方法,甚至可以定義全部方法都是抽象方法,
2、抽象方法只有方法宣告,沒有方法實作,有抽象方法的類必須宣告為抽象類,子類必須重寫所有的抽象方法才能實體化;否則子類也必須宣告成抽象類
3、抽象類可以有構造方法,其構造方法可以被本類的其他構造方法呼叫,若此構造方法不是由 private 修飾的,也可以被本類的子類中的構造方法呼叫,
🍟5.3、final 修飾符
final 是 java 關鍵字,表示 ”最后的、最終的、不可變的 “,
1、final 修飾的類:
用 final 修飾的類不能再被繼承,沒有子類
public final class Dog{
}
class Puppy extends Dog{ // 不能被繼承
}
2、final 修飾類的方法
用 final 修飾的類的方法不能被子類重寫
public class Student{
public final void print(){ // 不能被子類重寫
System.out.println("記得快樂!");
}
}
3、final 修飾的變數
final 修飾的變數叫做常量,如定義成員變數,一定需要初始化,不然報錯,如下:
public static final int i = 10;
final 修飾的區域變數只能賦值一次,不然報錯
final int i;
i = 10;
i = 11; // 會報錯,只能賦值一次
🍟5.3、final 與 abstract 修飾符
abstract:
可以用來修飾類和方法,不能用來修飾屬性和構造方法,
final:
final 可以用來修飾類,方法和屬性,不能修飾構造方法
🍟5.4、介面
介面類似一套規范,定義統一的標準,可以約束類的行為,使實作介面的類(或結構)在形式上保持一致
🍕5.4.1、定義和實作介面
介面定義:
介面型別的定義語法如下:
[訪問修飾符] interface 介面名 {
// 介面成員
}
介面實作:
類實作介面的語法如下:
class 類名 implements 介面名{
// 類成員
}
介面中的屬性都會自動加上 public static final 修飾,無論是否寫了此關鍵字,介面的屬性都是全域靜態常量,必須在定義時指定初始值,
int P = 5; // ①
public static final int P = 5; // 效果與 ① 的效果一致
int P; // 沒有初始化會報錯
介面方法:
在介面中可以定義 抽象方法、默認方法、靜態方法,如下:
public interface MyInterface {
// 抽象方法
void function1(); // 抽象方法沒有方法體,非抽象子類必須重寫
// 默認方法
default void function2(){
System.out.println("這是一個默認方法");
}
// 靜態方法
static void function3(){
System.out.println("這是一個靜態方法");
}
}
如下實作類:
public class MyClass implements MyInterface{
@Override
public void function1() { // 重寫抽象方法
System.out.println("實作 MyInterface 介面的 function1()!");
}
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.function1(); //呼叫實作類中重寫的方法
myClass.function2(); // 呼叫介面中的默認方法
MyInterface.function3(); // 呼叫介面中的靜態方法
}
}
效果如下:
介面本身繼承介面:
介面本身也可以繼承介面,如下:
[ 訪問修飾符 ] interface 介面名 extends 父介面1、父介面2、…{
// 常量定義
// 方法定義
}
實作多個介面:
一個普通類只能繼承一個父類,但能同時實作多個介面,也可以同時繼承一個父類并實作多個介面,如下:
class 類名 extends 父類名 implements 介面1、介面2…{
// 類的成員
}
注意:
普通類不管同時實作多少介面,必須把介面中的所有抽象方法全部重寫,
🍟5.5、面向物件設計原則
在實際開發程序中,遵循以下原則會讓代碼更具有靈活性,更能適應變化,
1、摘取代碼中變化的部分,形成介面
2、多用組合,少用繼承
3、面向介面編程,不依賴于具體實作
4、針對擴展開放,針對改變關閉(開閉原則)
–5、面向介面編程,不依賴于具體實作–
總結:
* 抽象類使用 abstract 修飾,不能實體化
* 抽象類中可以用零個到多個抽象方法,抽象方法使用 abstract 關鍵字修飾,沒有方法體,
* 如果非抽象類繼承抽象類,則必須實作(重寫)父類的所有抽象方法,否則子類只能是一個抽象類
* 用 final 關鍵字修飾的類,不能再被繼承,用 final 修飾的方法,不能被子類重寫,用 final 關鍵字修飾的變數將變成常量,只能在初始化時進行賦值,不能在其他地方修改,
* 介面中的屬性都是全域靜態常量,自 JDK 1.8 起,在介面中可以定義的方法包括抽象方法,靜態方法和默認方法
* 類只能繼承一個父類,但是可以實作多個介面,java 通過實作介面可以達到多重繼承的效果,
* 介面表示一種約定,也表示一種能力,介面體現了約定和實作相分離的原則,通過面向介面編程,可以降低代碼間的耦合性,提高代碼的可擴展性和可維護性,
🍖6、例外
例外:
程式在運行程序中發生由于外部問題(如硬體錯誤、輸入錯誤)等導致的程式例外事件,
(在Java等面向物件的編程語言中)例外本身是一個物件,產生例外就是產生了一個例外物件,
🍟6.1、例外處理機制
在程式設計時,必須考慮到可能發生的例外事件并進行相應的處理,這樣才能保證程式正常運行,
🍟6.2、例外處理結構
java 針對例外的處理提供了 try、catch、finally、throws、throw 五個核心關鍵字,其中前三個關鍵字可以組成常用的例外處理結構,
語法:
try{
// 有可能出現例外的陳述句
}catch(例外型別 例外物件){
// 例外處理陳述句
}finally{
// 一定會運行到的陳述句
}
try 陳述句塊用于監聽,把有可能會出現例外的代碼放在里面,當 try 中代碼塊中出現例外則會被 catch 捕捉,跳到 catch 陳述句塊里面處理例外,finally 陳述句塊總會被執行,finally 陳述句塊常用于回收 try 陳述句塊內打開的資源,如資料庫連接,網路連接和磁盤檔案,
常見例外處理格式:
例外格式的常見組合有 try-catch、try-catch-finally、try-finally 三種,
終止 finally 執行:
return 無法終止 finally 陳述句塊執行,唯一能終止 finally 陳述句塊執行的是 System.exit( 0 ) 表示停止 JVM 的執行
Exception 型別的常用方法
| 方法 | 描述 |
|---|---|
| void printStackTrace() | 輸出例外堆疊資訊, |
| String getMessage() | 回傳例外的詳細資訊,該資訊描述例外產生的原因,是 printStackTrace() 方法輸出資訊的一部分 |
🍕6.2.1、多重 catch 陳述句
語法:
try{
// 有可能出現例外的陳述句
}catch(例外型別 例外物件){
// 例外處理陳述句
}catch(例外型別 例外物件){
// 例外處理陳述句
}finally{
// 一定會運行到的陳述句
}
- 當 try 陳述句塊中發生例外時,系統將會按照從上到下的順序依次檢測每個 catch 陳述句,當匹配到某條 catch 陳述句后,后續其他 catch 陳述句塊將不會被執行
- 以 Exception 作為引數的 catch 陳述句必須放在最后的位置,否則所有的例外都會被其捕獲,因為 Exception 是所有能處理例外的子類的父類
🍟6.3、例外繼承結構圖
例外的繼承結構圖如下:
1、Error 類:
java.lang.Error 類包含僅靠程式本身無法恢復的嚴重錯誤,一般指與 JVM 相關的問題,是 java 運行環境的內部錯誤或硬體問題,如記憶體資源不足,JVM 錯誤等,
2、Exception 類:
java.lang.Exception 類是程式本身可以處理的例外,可分為運行時例外(Run Time Exception )與 編譯時例外( Checked )
2.1、運行時例外(Run Time Exception):
可以在程式中避免的例外,這類例外在編譯代碼時不會被編譯器檢測出來,可以正常編譯運行,擔當程式進行時發生例外,會輸出例外堆疊資訊并終止程式運行,如 空指標例外、型別轉換例外、陣列越界例外,這些例外包括 java.long.RuntimeException 類及其子類,可以使用 try-catch 陳述句捕獲這類例外
java 程式中常見的運行時例外:
| 例外 | 說明 |
|---|---|
| ArithmeticeException | 出現算術錯誤時,拋出此例外 |
| ArrayIndexOutOfBoundsException | 當非法索引訪問陣列時,拋出此例外 |
| ClassCastException | 當試圖將物件強制轉換為非本物件型別的子類時,拋出此例外 |
| IllegalArgumentException | 當向方法傳遞了一個不合法或不正確的引數時,拋出此例外 |
| InputMismatchException | 當欲得到的資料型別與實際輸入的型別不匹配時,拋出此例外 |
| NullPointerException | 當應用程式試圖在需要物件的地方使用 null 時,拋出此例外 |
| NumberFormatException | 當試圖將字串轉換成一種數值型別,但該字串不能轉換為適當格式時,拋出此例外 |
2.2、編譯時例外(Checked):
Checked 例外:除運行時例外外的例外,是由用戶錯誤或問題引起的例外,這是程式員無法預見的,常見的 編譯例外有 FileNotFoundException 例外、SQLException 例外等
🍟6.4、宣告例外——throws 關鍵字
throws 關鍵字是方法可能拋出例外的宣告,使用 throws 關鍵字在方法上宣告例外,宣告例外之后,例外由上一級(呼叫者)處理
語法:
public void 方法名() throws 例外型別[,例外型別]{ // 宣告的例外型別可多個
方法體
}
不建議在 main() 方法繼續宣告例外
public static void main(String[] args) throws Exception{
// 方法體
}
main( ) 方法中宣告例外由 JVM 處理,如果程式出現了錯誤,會導致程式中斷執行,
🍟6.5、拋出例外——throw 關鍵字
可以使用 throw 關鍵字手動拋出例外,使用 new 關鍵字創建例外類的物件,通過 throw 關鍵字拋出,
語法:
throw new 例外名( [ 引數串列 ] );
// 例如:
throw new Exception();
注意:
使用 throw 關鍵字拋出的只能是 Throwable 類或其子類的物件
🍟6.6、自定義例外
當 java 例外體系中提供的例外型別不能滿足程式的需要時,可以自定義例外類,使用自定義例外一般有如下三個步驟:
(1)定義 例外類,繼承 Exception 類或 RuntimeException 類,
(2)撰寫例外類的構造方法,并繼承父類的實作,
(3)實體化自定義例外物件,并使用 throw 關鍵字拋出,
如下自定義例外:
/**
* 年齡例外
*/
public class AgeException extends Exception{
public AgeException(){
super();
}
public AgeException(String message){
super(message);
}
public AgeException(String message, Throwable cause) {
super(message, cause);
}
}
上面自定義例外的 Test 類
public class ThrowTest {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("請錄入您的年齡:");
int age = input.nextInt();
try{
ThrowTest test = new ThrowTest();
System.out.println(test.ShowTicketPrice(age));
}catch (Exception ex){
System.out.println( ex.getMessage());
}
}
/**
* 根據年齡顯示票價資訊
* @param age
* @return
* @throws Exception
*/
private String ShowTicketPrice(int age) throws Exception{
if(age < 3){
throw new Exception("您錄入的年齡有誤,3歲以下兒童暫不能觀影!"); // 拋出自定義例外
}else if(age >= 60 || age <= 6){
return "您可以購買半價票 25 元!";
}else{
return "您需要購買全票價 50 元!";
}
}
}
🍟6.7、例外總結
1、例外是由 java 應用程式拋出和處理的非嚴重錯誤,它可以分為運行時例外和 Checked 例外兩大類,
2、Checked 例外必須捕獲或宣告拋出,否則無法通過編譯,運行時例外不要求必須捕獲或宣告拋出,
3、java 例外處理是通過五個關鍵字實作的:try、catch、finally、throw、throws,功能如下:
* try:用戶監聽,將可能拋出例外的代碼放在 try 陳述句塊之內,當 try 陳述句塊內發生例外時,會跳到 catch 陳述句塊內執行,
* catch:用戶捕獲例外,catch 陳述句用來捕獲 try 陳述句塊中發生的例外,
* finally:finally 陳述句塊總是會被執行,它主要用于回收在 try 陳述句塊里打開的資源(如資料庫連接)
* throw:用于拋出例外
* throws:用于宣告方法中可能拋出的例外,在該方法中可以不強制進行例外處理,如果出現了例外,則交給(上層)呼叫者進行處理,
4、可以在一個 try 塊后跟隨多個 catch 塊,分別處理不同的例外,但排列順序必須是從特殊到一般,最后一個一般為 Exception 類,
🍖7、集合框架
Java 集合位于 java.util 包中,集合中的元素全部是物件,及 Object 實體,不同的集合類有不同的功能和特點,適合不同的場合,
🍟7.1、集合結構
集合的繼承結構:
ps:上圖源自于網路
Java 的集合類主要由兩個介面派生而成:Collection 介面和 Map 介面,Collection 介面和 Map 介面是 Java 集合框架的根介面,其中,Collection 介面常用的子介面包含 List 介面和 Set 介面;
Map 介面是獨立的
Java 集合框架常用介面:
| 名稱 | 描述 |
|---|---|
| Collection | * 單值集合的根介面,是最基本的集合介面, * 一個 Collection 代表一組 Object,Java 不提供實作類,只提供子介面(如 List 介面和 Set 介面), * Collection 介面存盤一組可重復的無序物件, |
| Set | * 繼承 Collection 介面,存盤一組不可重復的無序物件(無序不重復), |
| List | * 繼承 Collection 介面,儲存一組可重復的有序物件, * 元素順序以元素插入的次序來放置元素,不會重新排序, * 通過索引訪問陣列元素,索引從0開始, * List 介面常用的實作類有 ArrayList 類和 LinkedList 類, |
| Map | * 存盤成對資料的根介面,可存盤一組 “鍵-值對” 物件, * 提供鍵(key)到值(value)的映射,Map 中的鍵不要求有序,不允許重復,值同樣不要求有序,但允許重復, * 可以通過鍵找到對應的值, |
| Iterator | 集合迭代器,能夠遍歷集合元素的介面 |
| Collections | * 與 Collection 是不同的概念,它提供了對集合物件進行基本操作的通用介面方法, * 包含各種有關集合操作的靜態方法, * 一個工具類,不能實體化, |
🍕7.1.2、Collection 介面
Collection 介面是 List 介面、Set 介面的父介面,該介面中定義的方法即可用于操作 Set 集合,也可用戶操作 List 集合,
Collection 介面常用方法:

ps:詳細自行查找 API
注意:
Java 集合中的元素全部是物件,是參考型別,不能存盤基本資料型別元素,如果強制添加,則會自動封裝成物件(自動裝箱,自動拆箱),
🍟7.2、List
List 集合是一類存盤元素有序,可重復的集合,集合中每個元素都有其對應的索引,List 集合允許使用重復元素,通過索引訪問元素,
🍕7.2.1、List 概述
List 介面作為 Collection 介面的子介面,可以使用 Collection 介面定義的全部方法,
List 介面在 Collection 介面方法的基礎上,另外擴展了一些根據索引操作集合元素的方法,
List 介面擴展的方法:

🍕7.2.2、ArrayList 集合類
繼承 List 介面,底層陣列,可通過索引訪問元素
ArrayList 集合的基本使用如下:
import java.util.ArrayList;
import java.util.Iterator;
/*
1.1、每個集合物件的創建(new)
1.2、向集合中添加元素
1.3、從集合中取出某個元素
1.4、遍歷集合
*/
public class ArrayListTest {
public static void main(String[] args) {
// 創建ArrayList集合
ArrayList<String> arrayList = new ArrayList<>();
// 向集合添加元素
arrayList.add("zhangsan");
arrayList.add("lisi");
arrayList.add("wangwu");
arrayList.add("zhaoliu");
// 從集合中取出某個元素
String i = arrayList.get(1);
System.out.println(i);
System.out.println("===============分割線============");
// 遍歷集合 獲取迭代器進行集合遍歷
Iterator<String> it = arrayList.iterator();
while(it.hasNext()){
System.out.print(it.next()+" ");
}
System.out.println("\n===============分割線============");
// 遍歷集合 通過迭代器配合for回圈遍歷
for(Iterator<String> its = arrayList.iterator() ;its.hasNext();){
System.out.println("====>"+its.next());
}
System.out.println("===============分割線=============");
// 遍歷集合 通過增強for
for(String fors : arrayList){
System.out.print(fors+" ");
}
System.out.println("\n===============分割線=============");
// 遍歷集合 通過獲取長度加for的方式
for(int j = 0;j < arrayList.size();j++){
System.out.print(arrayList.get(j)+" ");
}
}
}
運行結果:
總結:
ArrayList 集合底層采用陣列的資料結構
ArrayList 集合是非執行緒安全的
1、ArrayList 集合初始化容量是 10
2、ArrayList 集合底層是 Object 型別的陣列 Object[ ]
3、擴容到原容量的 1.5 倍
4、建議給定一個預估計的初始化容量,減少陣列的擴容次數,這是 ArrayList 集合比較重要的優化策略,
5、陣列的優點:檢索效率比較高
6、陣列的缺點:隨機增刪元素效率比較低
7、需要注意的是:向陣列末尾添加元素,效率還是很高的
🍕7.2.3、LinkedList 集合類
- ArrayList集合 采用了和陣列相同的存盤方式,在記憶體中分配連續的空間,當添加和洗掉非尾部元素時會導致后面所有元素的移動,性能低下,所以在插入,洗掉操作比較頻繁時,可以考慮使用LinkedList 集合提高效率,
- LinkedList 類是 List 介面的實作類,這就意味著 LinkedList 集合和 ArrayList 集合都是一個 List 集合,可以根據所以隨機訪問集合中的元素,
- LinkedList 集合具有 雙向鏈表結構,更加方便實作添加和洗掉操作,
LinkedList 集合方法如下:

LinkedList 集合存盤資料與遍歷:
import java.util.LinkedList;
import java.util.List;
/**
* 使用 LinkedList 集合存盤資料
*/
public class LinkedListTest {
public static void main(String[] args) {
// 1.創建集合及元素物件
LinkedList fruits = new LinkedList();
Fruit fruit1 = new Fruit("香水梨",2.5);
Fruit fruit2 = new Fruit("蘋果梨",2.0);
Fruit fruit3 = new Fruit("富士蘋果",3.5);
Fruit fruit4 = new Fruit("金帥蘋果",3.0);
//2.向集合中添加元素
fruits.add(fruit1);
fruits.add(fruit2);
fruits.add(fruit3);
fruits.add(fruit4);
System.out.println("集合中包含水果資訊如下:");
shoData(fruits);
// 3.查看集合中第一條水果資訊
Fruit firstFruit = (Fruit) ((LinkedList) fruits).getFirst();
System.out.println("集合中第一類水果:"+firstFruit.getBrand());
// 5.洗掉集合中第一條和最后一條資訊
fruits.removeFirst();
fruits.removeLast();
System.out.println("洗掉部分資訊后還有"+fruits.size()+"條水果資訊,如下:");
shoData(fruits);
}
/**
* for 回圈遍歷集合
* @param list
*/
private static void shoData(List list) {
for (int i = 0; i < list.size(); i++) {
Fruit fruit = (Fruit) list.get(i);
fruit.show();
}
}
}
水果類基本結構:
/**
* 水果類
*/
public class Fruit {
private String brand; // 水果品種
private double price; // 價格
// 省略 setter and getter
public Fruit() {
}
public Fruit(String brand, double price) {
this.brand = brand;
this.price = price;
}
// 輸出資訊
public void show(){
System.out.println(this.brand+",每斤"+this.price+"元,");
}
}
運行結果:
總結:
LinkedList 集合底層采用雙向鏈表資料結構
1、LinkedList 集合是雙向鏈表,
2、對于鏈表資料結構來說,隨機增刪效率較高,檢索效率較低,
3、鏈表中的元素在空間存盤上,記憶體地址不連續,
ArrayList 集合與 LinkedList 集合:
ArrayList 集合底層是陣列,
- 優點:基于陣列實作,讀取操作效率高,
- 缺點:不適合頻繁進行插入和洗掉操作,因為每次執行該類操作都需要頻繁移動其中的元素,
LinkedList 集合由雙向鏈表實作,從任意一個節點開始,都可以很方便地訪問它地前驅節點和后繼節點,(雙向鏈表的頭節點和尾節點)
- 優點:增加、洗掉操作只需修改鏈表節點指標,無需進行頻繁的移動,
- 缺點:遍歷效率較低,
🍟7.3、Set
Set 介面和 List 介面一樣,都是 Collection 的子介面,Set 集合中不允許包含重復元素,沒有順序,
🍕7.3.1、HashSet 集合
HashSet 類是 Set 介面的典型實作,使用 HashSet 集合可以實作對無序不重復資料的存盤,具有很好的存取好查找性能,
特征:
- 不允許存盤重復的元素,
- 沒有索引,沒有包含索引的方法,不能使用索引遍歷,
- 是無序集合,存盤元素和取出元素的順序可能不一致,
如下案例:
import java.util.HashSet;
import java.util.Set;
/**
* 使用 HashSet 集合存盤資料
*/
public class HashSetTest {
public static void main(String[] args) {
// 1.創建一個 Set 集合和多條水果資料
Set fruits = new HashSet();
Fruit fruit1 = new Fruit("金帥蘋果",2.5);
Fruit fruit2 = new Fruit("富士蘋果",2.0);
// 2.向集合中添加元素
fruits.add(fruit1);
fruits.add(fruit2);
System.out.println("添加重復元素,是否成功::"+fruits.add(fruit1));
// 3.遍歷集合
for (Object o : fruits) {
Fruit fruit = (Fruit) o;
fruit.show();
}
}
}
運行結果:
總結:
通過原始碼發現,實際上 HashSet 集合在 new 的時候,底層事件上 new 了一個 HashMap 集合,向 HashSet 集合中存盤元素,實際上是存盤到 HashMap 集合中,
HashMap 集合是一個哈希表資料結構
HashSet 集合初始化容量是 16 初始化容量建議是 2 的倍數,
擴容:擴容之后是原容量2倍
🍕7.3.2、TreeSet 集合類
TreeMap 集合的創建、添加、取出、遍歷
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
/*
1.1、每個集合物件的創建(new)
1.2、向集合中添加元素
1.3、從集合中取出某個元素
1.4、遍歷集合
1.5、測驗TreeSet集合中的元素是可排序的,
1.6、測驗TreeSet集合中存盤的型別是自定義的,
1.7、測驗實作Comparable介面的方式
1.8、測驗實作Comparator介面的方式(最好測驗以下匿名內部類的方式)
*/
public class TreeSetTest {
public static void main(String[] args) {
// 創建 TreeSet 集合
TreeSet<String> treeSet = new TreeSet<>();
// 向集合中添加元素
treeSet.add("zhangsan");
treeSet.add("lisi");
treeSet.add("wangwu");
treeSet.add("zhaoliu");
// 從集合中取出元素 轉為 ArrayList 集合通過下標取出
ArrayList arr = new ArrayList(treeSet);
System.out.println(arr.get(0));
System.out.println("===============分割線============");
// 集合遍歷 獲取迭代器遍歷
Iterator<String> it = treeSet.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("===============分割線============");
// 遍歷集合 通過增強for
for (String s : treeSet) {
System.out.println(s);
}
System.out.println("===============分割線============");
// 測驗TreeSet集合中的元素是可排序的
TreeSet<Integer> treeSet1 = new TreeSet<>(); // 結果 1 2 3 4
treeSet1.add(2);
treeSet1.add(4);
treeSet1.add(1);
treeSet1.add(3);
for (Integer integer : treeSet1) {
System.out.println(integer);
}
System.out.println("===============分割線============");
//測驗TreeSet集合中存盤的型別是自定義的
/* TreeSet<A> treeSet2 = new TreeSet<>();
treeSet2.add(new A(1));
treeSet2.add(new A(3));
treeSet2.add(new A(2));
treeSet2.add(new A(4));
//遍歷集合
for (A a : treeSet2) {
System.out.println(a);
}*/
/* 會報
Exception in thread "main"
java.lang.ClassCastException: Gather.GatherZoJie.A cannot be cast to java.lang.Comparable
這個例外,原因是類未實作Comparable介面
*/
System.out.println("===============分割線============");
// 測驗實作Comparable介面的方式
TreeSet<B> treeSet3 = new TreeSet<>();
treeSet3.add(new B(1));
treeSet3.add(new B(3));
treeSet3.add(new B(2));
treeSet3.add(new B(4));
// B 類以實作 Comparable 介面,同時重寫了 toString 方法
// 遍歷
for (B b : treeSet3) {
System.out.println(b);
}
// 遍歷結果是按 重寫的compareTo方法的排序方式進行排序
System.out.println("===============分割線============");
//測驗實作Comparator介面的方式(最好測驗以下匿名內部類的方式)
// 比較器方法
TreeSet<C> treeSet4 = new TreeSet<>(new Comparator<C>() {
@Override
public int compare(C o1, C o2) {
return o1.age - o2.age;
}
});
treeSet4.add(new C(1));
treeSet4.add(new C(3));
treeSet4.add(new C(2));
treeSet4.add(new C(4));
// 遍歷
for (C c : treeSet4) {
System.out.println(c);
}
// 輸出結果 1 2 3 4 以排序
}
}
// 自定義型別A
class A{
int age;
public A(int age){
this.age = age;
}
}
// 自定義型別B,實作 Comparable 介面,重寫 compareTo 排序方法
class B implements Comparable<B>{
int age;
public B(int age){
this.age = age;
}
@Override
public int compareTo(B o) {
return this.age - o.age;
}
@Override
public String toString() {
return "B{" +
"age=" + age +
'}';
}
}
// 自定義型別A
class C{
int age;
public C(int age){
this.age = age;
}
@Override
public String toString() {
return "B{" +
"age=" + age +
'}';
}
}
總結:
TreeSet 集合底層實際上是 TreeMap
new TreeSet 集合的時候,底層實際上new了一個TreeMap 集合,
往 TreeSet 集合中放資料的時候,實際上是將資料放到 TreeMap 集合中,
TreeMap 集合底層采用二叉樹資料結構,能按照大小順序排序,
注意:
1、HashSet 底層 HashMap
2、TreeSet 底層 TreeMap
🍟7.4、Map
List 集合和 Set 集合都可以存盤一組資料元素,每個元素是一個物件,并且可以實作對這組資料元素的操作,
特點:
1、Map 集合和 Collection 集合沒有關系,
2、Map 集合以 key 和 value 的這種鍵值對的方式存盤元素,
3、key 和 value 都是存盤 java 物件的記憶體地址,
4、所有 Map 集合的 key 特點:無序不可重復的,
Map 集合的 key 和 Set 集合存盤元素特點相同,
Map介面方法:

Map 介面提供了大量實作類,典型實作類如 HashMap 類、HashTable 類、TreeMap 類等,
🍕7.4.1、HashMap 集合類
HashMap 集合的創建、添加、取出、遍歷
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*
1.1、每個集合物件的創建(new)
1.2、向集合中添加元素
1.3、從集合中取出某個元素
1.4、遍歷集合
*/
public class HashMapTest {
public static void main(String[] args) {
// 創建 HashMap 集合
HashMap<Integer,String> hashMap = new HashMap<>();
// 向集合中添加元素
hashMap.put(1,"zhangsan");
hashMap.put(2,"lisi");
hashMap.put(3,"wangwu");
hashMap.put(4,"zhaoliu");
// 從集合中取出某個元素
String s = hashMap.get(1);
System.out.println(s);
// 遍歷集合 獲取所有的key,然后通過key獲取value
Set<Integer> it = hashMap.keySet();
for (Integer integer : it) {
System.out.println(integer + " =" + hashMap.get(integer));
}
System.out.println("===============分割線============");
// 遍歷集合 通過 呼叫 entrySet方法 得到一個 Set
Set<Map.Entry<Integer, String>> entries = hashMap.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey() + " =" + entry.getValue());
}
}
}
總結:
HashMap 集合底層是哈希表資料結構
HashMap 集合是非執行緒安全的
在 JDK8 之后,如果哈希表單向鏈表中元素超過 8 個,單向鏈表這種資料結構會變成紅黑樹資料結構,當紅黑樹上的節點數量小于 6 時,會重新把紅黑樹變成單向鏈表資料結構,
這種方式也是為了提高檢索效率,二叉樹的檢索會再次縮小掃描范圍,提高效率,
初始化容量 .16,默認加載因子.75
擴容:擴容之后的容量是原容量的 2 倍,
HashMap 集合的 key 和 value 允許 null
🍟7.5、集合總結
1、集合彌補了陣列的缺陷,它比陣列更靈活更實用,可大大提高軟體的開發效率,而且不同的集合可用于不同場合,
2、Java 的集合類主要由兩個介面派生而出:Collection 介面和 Map 介面及相關的工具類,這兩個介面又包括了一些子介面或實作類,其中,Collection 介面又包含了兩個子介面—— List 介面和 Set 介面,另外一個重要介面是 Map 介面,
* Collection 介面存盤一組不唯一,無序的物件,
* Set 介面繼承 Collection 介面,存盤一組唯一,無序的物件,
* List 介面繼承 Collection 介面,存盤一組不唯一,有序的物件
* Map 介面存盤一組成對的 鍵-值物件,提供從 key 到 value 的映射,key 不要求有序,不允許重復;value 同樣不要求有序,但允許重復,
3、Iterator 為集合而生,專門實作集合的遍歷,它隱藏了各種集合實作類的內部細節,提供了遍歷集合的統一編程介面,
4、ArrayList 類和陣列采用相同的存盤方式,它的優點在于遍歷元素和隨機訪問元素的效率比較高,
5、LinkedList 類采用鏈表存盤方式,優點在于插入,洗掉元素時效率比較高,
6、HashMap 類是最常見的 Map 實作類,它的存盤方式是哈希表,優點是查詢指定元素效率高,

🍖8、包裝類
Java 包含八種基本資料型別,同時包含與其對應的包裝類,
這些包裝類存在于 java.lang 包中,關系圖如下:

其中:
所有數字型別包裝類都繼承 Number 類,Number 類是一個抽象類,
Number 類包裝了 Byte、Short、Interger、Long、Float、Double 等數字型別,并且實作其所定義的方法,這些方法以不同的數字格式回傳物件的值,
🍟8.1、包裝類和基本資料型別的轉換
🍕8.1.1、基本資料型別轉換為包裝類
在 Java 中,基于基本資料型別資料創建包裝類物件通常可用采用如下兩種方式:
public Type(type value)
public Type(String value)
其中,Type 表示包裝類,引數 type 為基本資料型別,
如下:
/**
* 基本資料型別向包裝類轉換
*/
public class Test{
public static void main(String[] args) {
boolean bl = true;
// 1.通過構造方法現基本資料型別向包裝類轉換
Boolean blObj = new Boolean(bl);
Integer itOBj = new Integer(35);
Character chOBJ = new Character('男');
System.out.println(blObj+","+itOBj+","+chOBJ);
// 2.將字串轉換為 Boolean 物件
Boolean bOBj = new Boolean("true");
Boolean bOBj2 = new Boolean("TRue"); // 不區分大小寫
Boolean bOBj3 = new Boolean("true"); // 非 true 即 false
System.out.println(bOBj+","+bOBj2+","+bOBj3);
// 運行時將出現 java.lang.NumberFormatException 例外
// Long lObj = new Long("hello");
// Char 型資料不能使用字串構造包裝類物件
// Character chobj2 = new Character('男');;
}
}
運行結果:
true,35,男
true,true,true
🍕8.1.2、包裝類轉換為基本資料型別
包裝類轉換為基本資料型別通常采用如下方法:
public type typeValue();
其中:type 指的是基本資料型別,如 byteValue( )、charValue( )、相應的回傳值為 byte、char,將包裝類物件轉換為基本資料型別,
如下:
/**
* 包裝類轉換為基本資料型別
*/
public class Test {
public static void main(String[] args) {
boolean bl = true;
// 通過構造器實作基本資料型別向包裝類轉換
Boolean blObj = new Boolean(bl);
Integer itObj = new Integer(35);
Character chObj = new Character('男');
// 包裝類轉換為基本資料型別
boolean b1 = blObj.booleanValue();
int i = itObj.intValue();
char ch = chObj.charValue();
System.out.println(b1+","+i+","+ch);
}
}
運行結果:
true,35,男
關系圖:

🍟8.2、裝箱拆箱
Java 基本資料型別變數和包裝類物件之間的轉換較繁瑣,從 JDK1.5 之后提供了自動裝箱(Autoboxing)和自動拆箱(AutoUnboxing)機制
- 自動裝箱:把基本資料型別變數直接轉換為對應的包裝類物件,或者轉換為 Object 物件,
- 自動拆箱:與裝箱相反,將包裝類物件轉換成對應的基本資料型別變數
如下:
public class BoxTest {
public static void main(String[] args) {
// 基本資料型別變數轉換為包裝類(裝箱)
Integer inObj = 5;
Object boolObj = true;
System.out.println(inObj+","+boolObj);
// 包裝類轉換為基本資料型別(拆箱)
int it = inObj;
System.out.println(it);
if(boolObj instanceof Boolean){
// 先把 Object 物件強制轉換為 Boolean 型別,再賦值給 boolean 變數
boolean b = (Boolean)boolObj;
System.out.println(b);
}
}
}
運行結果:
5,true
5
true
🍖9、I / O 流
🍟9.1、File 類
File 類是 java.io 包下代表操作與平臺無關的檔案和目錄的類,可以通過 File 類實作對檔案或目錄的新建、洗掉、重命名等操作,
File 類的構造方法定義如下:
public File(String pathName)
File 類的方法如下:

🍟9.2、I / O 流概述
I / O 流就是可以操作檔案的一些 Java類,它們在 java.io 包下
I 即 input,指讀取操作;O 即 output,指寫出操作
🍕9.2.1、按流向劃分:輸入流和輸入流
輸入流:只能從中讀取資料,而不能寫入資料的流,實作程式從資料源中讀操作,(從硬碟輸入到記憶體)
輸出流:只能向其寫入資料,而不能從中讀資料的流,實作程式向目標資料源中寫資料,(從記憶體到硬碟)
Java 的輸入流主要以 inputStream 和 Reader 作為基類,而輸出流則主要以 OutputStream 和 Writer 作為基類,它們都是一些抽象類,無法直接實體化物件,
🍕9.2.2、按處理單元劃分:位元組流和字符流
位元組流:以 8 位位元組位為操作資料單元的流,可用于操作二進制資料,
字符流:以 16 位的字符為操作資料單元的流,可用于操作文本資料,
理解:
可以把 I / O 流看作一個水管,這個水管中依次排列著許多水滴,每滴水滴就是一個處理單元,即一個位元組或字符,在位元組流中每滴水滴是一個位元組,在字符流中每滴水滴是一個字符,
🍕9.2.3、按流的角色劃分:節點流和處理流
節點流:可以直接向一個特定的存盤介質(如磁盤、檔案)讀寫資料的流,
處理流:用于對一個已存在的流進行連接和封裝,是通過封裝后的流實作資料讀寫操作的流,
畫圖如下:

ps:以上圖片源于網路
🍟9.3、位元組流
java.io 包中的流按存盤單元分類主要又位元組流和字符流兩大類,兩類都具有輸入/輸出操作,它們的基類如下圖:

以上基類均為抽象類
🍕9.3.1、位元組輸出流基類:OutputSteam
OutputStream 類為抽象類,必須使用該類的子類進行實體化物件,
OutputStream 類的方法如下:
🍕9.3.2、位元組輸出流 FileOutputStream 類
FileOutputStream 類是 OutputSteam 類的子類
FileOutputStream 構造方法:
實作實體如下:
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* 字符輸出流(寫)測驗
*/
public class OutputStreamTest {
public static void main(String[] args) {
OutputStream fos = null;
try {
// 2.創建檔案輸出流物件
fos = new FileOutputStream("test2.txt"); // 里面是檔案路徑
// 3.執行寫操作
String str = "I love Java"; //準備一個字串
byte[] words = str.getBytes(); //將字串轉換為 byte 陣列
fos.write(words,0,words.length);
System.out.println("檔案寫入成功!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
// 4.關閉輸出流
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
🍕9.3.3、位元組輸入流基類:InputStream
InputStream 類為抽象類,必須使用該類的子類進行實體化物件,
InputStream 類的方法如下:
🍕9.3.4、位元組輸入流 FileInputStream 類
通常使用 InputStream 類的 FileInputStream 子類實作文本檔案內容的讀取
FileInputStream 類構造方法如下:
實作實體如下:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* 位元組輸入流(讀)
*/
public class InputStreamTest {
public static void main(String[] args) {
InputStream fis = null;
try {
// 2.創建檔案輸入流物件
fis = new FileInputStream("test2.txt"); // 里面是路徑
System.out.println("可讀取的位元組數:"+fis.available());
// 3.執行讀操作
int data = 0;
// 回圈讀取資料
System.out.println("檔案內容:");
while((data = fis.read()) != -1){
System.out.print((char)data+" ");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if (fis != null) {
try {
// 4.關閉輸出流
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
可以使用陣列一次性讀取多個位元組,如下:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
// 從檔案中讀資料
public class InputStreamTest {
public static void main(String[] args) {
InputStream fis = null;
try {
// 2.創建檔案輸入流物件
fis = new FileInputStream("test2.txt");
// 3.執行讀操作
byte[] words = new byte[1024]; // 一次讀取 1024 位元組
int len = 0; //實際讀取長度
System.out.println("檔案的內容:");
while ((len = fis.read(words))!= -1) {
System.out.println(new String(words,0,len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
🍟9.4、字符流
在 Java 中,一個字符占用記憶體的兩個位元組,如果用位元組流處理文本檔案,則程式內部需要將位元組轉換成字符,減低了執行效率,所以當輸入和輸出文本檔案時,盡量使用字符流,Java 提供了 Writer 類和 Reader 類來操作字符,
🍕9.4.1、字符輸出流基類:Writer
Writer 類是字符輸出流的抽象類,
Writer 類的方法如下:
🍕9.4.2、字符輸出流 FileWriter 類
FileWriter 類是 Writer 類常用的子類,使用該類可以以字符為資料處理單元向文本檔案中寫資料,
實作實體如下:
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
/**
* 字符輸出流 FileWriter
*/
public class FileWriterTest {
public static void main(String[] args) {
Writer fw = null;
try{
//創建 FileWriter 物件
fw = new FileWriter("text.txt");
// 向檔案寫入資料
fw.write("預測未來的最好方\n式就是創造未來!");
fw.flush(); // 重繪緩沖區
System.out.println("檔案寫入成功!");
} catch (IOException e) {
e.printStackTrace();
System.out.println("檔案不存在!");
}finally{ // 關閉流
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
🍕9.4.3、字符輸入流基類:Reader
Reader 類是字符輸入流的基類,它是抽象類,
Reader 類的方法如下:
🍕9.4.4、字符輸入流 FileReader
Reader 類同樣是抽象類,可以使用其子類 FileReader 創建物件,
實作實體如下:
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
/**
* 字符輸入流
*/
public class FileReaderTest {
public static void main(String[] args) {
Reader fr = null;
try{
// 2.實體化 FileReader 物件
fr = new FileReader("text.txt");
char[] ch = new char[1024]; //字符使用 char 陣列
int len = 0; // 保存實際存盤的字符數
// 3.回圈讀取資料
while((len = fr.read(ch)) != -1){
System.out.println(new String(ch,0,len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
// 4.關閉流
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
🍟9.5、緩沖流
java.io 包提供了緩沖流,緩沖流屬于處理流,
🍕9.5.1、字符緩沖輸出流 BufferedWriter 類
BufferedWriter 類是 Writer 類的子類,BufferedWriter 類可以把一批資料寫到緩沖區,默認情況下,只有在緩沖區滿的時候,才會把緩沖區的資料真正寫到目的地,這樣能減少物理寫資料的次數,從而提高 I/O 操作的執行效率,
BufferedWriter 類的構造方法如下:
實作實體如下:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
/**
* 字符緩沖輸出流 BufferedWriter 類
*/
public class BufferedWriterTest {
public static void main(String[] args) {
Writer fw = null;
BufferedWriter bw = null;
try{
// 創建 FileWriter 物件
fw = new FileWriter("BufferedWriter.txt");
bw = new BufferedWriter(fw);
// 向檔案中寫資料
bw.write("親愛的小伙伴們:");
bw.newLine(); //換行
bw.write("讓我們來使用緩沖流讓程式加速吧!");
bw.flush(); // 重繪
System.out.println("檔案寫入成功!");
} catch (IOException e) {
e.printStackTrace();
System.out.println("檔案不存在!");
}finally {
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
從以上可以看出,創建 BufferedWriter 物件需要借助一個 Writer 物件,在該物件的基礎上進一步封裝,成為一個帶緩沖區的字符輸出流,
🍕9.5.2、字符緩沖輸入流 BufferedReader 類
BufferedReader 類是 Reader 類的子類,它與 FileReader 類的區別在于 BufferedReader 類有緩沖區,它可以把一批資料讀到緩沖區中,接下來的讀操作都是從緩沖區內獲取資料,避免每次都從資料源讀取資料進行字符編碼轉換,從而提高讀取操作的效率,
BufferedReader 類的構造方法如下;
實作實體如下:
import java.io.*;
/**
* 帶緩沖區的字符輸入流 BufferedReader 類
*/
public class BufferedReaderTest {
public static void main(String[] args) {
Reader fr = null;
BufferedReader br = null;
try{
fr = new FileReader("BufferedWriter.txt");
br = new BufferedReader(fr);
String line = br.readLine();
while(line != null){
System.out.println(line);
line = br.readLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
從以上可以看出,創建 BufferedReader物件需要借助一個 Reader物件,在該物件的基礎上進一步封裝,成為一個帶緩沖區的字符輸入流,
🍟9.6、資料操作流
可以對二進制檔案的讀寫操作
🍕9.6.1、DataOutputStream 類和 DataInputStream 類
DataOutputStream 類是 OutputStream 類的子類,利用 DataOutputStream 類寫二進制檔案的實作步驟與使用 FileOutputStream 類寫檔案的步驟及其相似,而且用到了 FileOutputStream 類,
同樣:DataInputStream 類是 InputStream 類的子類,在使用上也與 FileInputStream 類很相似,
使用實體如下:
import java.io.*;
/**
* 資料操作流
*/
public class DataInOutTest {
public static void main(String[] args) throws IOException {
DataOutputStream dos = null;
DataInputStream dis = null;
FileInputStream fis = null;
FileOutputStream fos = null;
try{
// 創建 輸入流檔案
fis = new FileInputStream("D:\\User.class");
dis = new DataInputStream(fis);
// 創建輸出流檔案
fos = new FileOutputStream("Demo/newUser.class");
dos = new DataOutputStream(fos);
int temp;
while((temp = dis.read()) != -1){
fos.write(temp);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally { // 關閉
if (dis != null) {
dis.close();
}
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
if (dos != null) {
dos.close();
}
}
}
}
🍟9.7、序列化與反序列化
將物件的資訊保存到磁盤中便于以后檢索,可以將物件中的屬性資訊逐一記錄到文本檔案中(序列化),還需要再使用的時候還原(反序列化),
序列化的物件保存的是二進制狀態,這樣實作了平臺無關性,可以將在 Windows 作業系統中實作序列化的一個物件,傳輸到 Linux 作業系統的機器上,反序列化是將特定的存盤介質中的資料重新構建物件的程序,通過發序列化后得到相同物件,而無序擔心資料因平臺不同出現例外,
序列化和反序列化程序:
實作序列化和反序列化操作,需要使用物件操作流:
物件輸出流:ObjectOutputStream
物件輸入流:ObjectInputStream
🍕9.7.1、物件輸出流 ObjectOutputStream 實作序列化
可以理解為把一個物件變為二進制的資料流的一種方法,通過物件序列化可以方便的實作物件的傳輸和存盤,
一個類需要被序列化,則這個物件所屬類必須實作 java.io.Serializable 介面,介面如下:
public interface Serializable{}
序列化版本號:
物件序列化和反序列化操作時可能會遇到版本兼容性問題,要考慮 JDK 版本,版本不一致就可能發生錯誤,
所以在序列化操作中引入了一個 serialVersionUID 的常量(序列化版本號),可以通過此常量驗證版本的一致性,
當實作 java.io.Serializable 介面的類沒有顯式定義名為 serialVersionUID、型別為 long 的變數時,java 序列化機制在編譯時會自動生成一個此版本的 serialVersionUID 變數,如果不希望通過編譯自動生成,可以直接自己顯式定義這個變數,
private static final long serialVersionUID = 1L; (1L為自己定義的版本號)
ObjectOutputStream 類的常用方法:
| 方法名 | 描述 | 型別 |
|---|---|---|
| ObjectOutputStream(OutputStream out) | 創建物件輸出流物件 | 構造方法 |
| final void writeObject(Object obj) | 將指定物件寫入流 | 實體方法 |
實作實體如下:
import java.io.*;
/**
* 序列化與反序列化
*/
public class SerializableObj {
public static void main(String[] args) {
ObjectOutputStream oos = null;
try{
// 創建 ObjectOutputStream 輸出流
oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
Person person = new Person("杰米",25,"男"); // Person 類實作了 Serializable 介面
// 物件序列化,寫入資料
oos.writeObject(person);
System.out.println("序列化成功!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
也可以使用集合一次性保存多個物件
🍕9.7.2、物件輸入流 ObjectInputStream 實作反序列化
ObjectInputStream 它可以直接把序列化后的物件還原,
ObjectInputStream 常用方法:
| 方法 | 描述 | 型別 |
|---|---|---|
| ObjectInputStream(InputStream in) | 創建物件輸入流物件 | 構造方法 |
| final Object readObject( ) | 從指定位置讀取物件 | 實體方法 |
實作實體如下:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* 反序列化
*/
public class DeserTest {
public static void main(String[] args) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("Demo/person.txt"));
// 反序列化,進行強制型別轉換
Person per = (Person) ois.readObject();
// 輸出轉換反序列化后的物件資訊
per.print();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally{
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:
如果使用序列化方式向檔案中寫入多個物件,那么反序列化恢復物件時,也按照寫入的順序讀取,
🍕9.7.3、transient 關鍵字
如果希望物件中的某個屬性不被序列化,只需在某個類中的屬性前添加 transient 關鍵字,如下:
private transient int age;
age 屬性將不會參與序列化
🍕9.7.4、Java 體系中常用流總結
Java 中常用的流總結如下:
| 分類 | 位元組輸出流 | 位元組輸入流 | 字符輸出流 | 字符輸入流 |
|---|---|---|---|---|
| 基類 | OutputStream | InputStream | Writer | Reader |
| 檔案流 | FileOutputStream | FileInputStream | FileWriter | FileReader |
| 緩沖流 | BufferedOutputStream | BufferedInputStream | BufferedWriter | BufferedReader |
| 物件流 | ObjectOutputStream | ObjectInputStream | —— | —— |
| 資料操作流 | DataOutputStream | DataInputStream | —— | —— |
所有的基類都是抽象類,無法直接創建實體,需要借助其實作類
所有的輸出流用于實作寫資料操作,所有的輸入流用于實作讀取操作,這個輸入和輸出是相對程式而言的,
所有的檔案流直接與存盤介質關聯,也就是需指定物理節點,屬于節點流,其他流的創建需要在節點流的基礎上進行封裝,使其具有特殊的功能,
在操作文本檔案時,應使用字符流,位元組流可以處理二進制資料,它的功能比字符流更強大,
因為計算機所有的資料都是二進制資料,如果用位元組流處理文本檔案,還需要把這些位元組轉換成字符,就增加了編程的復雜度,減低了執行效率,所以當輸入和輸出是文本檔案時,盡量使用字符流,如果是二進制文本,則應考慮使用節點流,
🍖10、多 線 程
🍟10.1、了解多執行緒
世間萬物每個個體都可以同時完成很多作業,這就是多執行緒
🍕10.1.1、行程與執行緒
行程是程式的一次動態執行程序,它是從代碼加載、執行中到執行完畢的一個完整程序,也是行程本身從產生、發展到最終消亡的程序,
執行緒:(百度百科)
執行緒(英語:thread)是作業系統能夠進行運算調度的最小單位,它被包含在行程之中,是行程
中的實際運作單位,一條執行緒指的是行程中一個單一順序的控制流,一個行程中可以并發多個線
程,每條執行緒并行執行不同的任務,在Unix System V及SunOS中也被稱為輕量行程(lightweight > processes),但輕量行程更多指內核執行緒(kernel thread),而把用戶執行緒(user thread)稱為執行緒,
🍕10.1.2、多執行緒的運行機制
以往開發的程式大多是單執行緒的,即一個程式只有從開始到結束這一條執行路徑,而多執行緒是指一個行程同時存在幾條執行路徑且并發執行的作業方式,
并發:
并發運行表示在一個處理器中,作業系統為了提高程式的運行效率,將 CPU 的執行時間分成多個時間片,分配給同一行程的不同執行緒,當執行完一個時間片后,當前運行的執行緒就可能交付出 CUP 權限,讓其他執行緒執行下一個時間片,當然 CPU 也有可能將相鄰的時間片分配給同一執行緒,即多個執行緒分享 CPU 時間,交替運行,
之所以從表面上看是多個執行緒同時運行的,是因為不同執行緒之間切換的時間非常短,也許僅僅是幾毫秒,對普通人來說是難以感知的,
🍕10.1.3、多執行緒的優勢
多執行緒優勢:
1、充分利用 CPU 的資源:運行單執行緒程式時,若程式發生阻塞,則 CPU 可能會處于空閑狀態,這將造成計算機資源浪費,而使用多執行緒可以在某個執行緒處于休眠或阻塞狀態時運行其他執行緒,這樣將大大提高資源利用率,
2、簡化編程模型:可以考慮將一個既長又復雜的行程分為多個執行緒,成為幾個獨立執行的模塊,
3、良好的用戶體驗:由于多個執行緒可以交替運行,減少或避免了程式阻塞或意外情況造成的回應過慢現象,減少了用戶等待時間,提升了用戶體驗,
🍟10.2、多執行緒編程
Java 語言提供了 java.lang.Thread 類支持多執行緒編程,
🍕10.2.1、Thread 類介紹
Thread 類提供了大量的方法來控制和操作執行緒,
Thread 類常用方法:
| 方法 | 描述 | 型別 |
|---|---|---|
| Thread( ) | 創建 Thread 物件 | 構造方法 |
| Thread(Runnable target) | 創建 Thread 物件,target 為 run( ) 方法被呼叫的物件 | 構造方法 |
| Thread(Runnable target,String name) | 創建 Thread 物件,target 為 run( ) 方法被呼叫的物件, name 為新執行緒的名稱 | 構造方法 |
| void run( ) | 執行任務操作的方法 | 實體方法 |
| void start( ) | 使該執行緒開始運行,JVM 將呼叫該執行緒的 run( ) 方法 | 實體方法 |
| void sleep(long millis) | 在指定的毫秒數內讓當前正在運行的執行緒休眠(暫停運行) | 靜態方法 |
| Thread currentThread( ) | 回傳當前執行緒物件的參考 | 靜態方法 |
主執行緒:
public static void main( ) 方法是主執行緒的入口,每個行程至少有一個主執行緒,重要性如下:
- 主執行緒是產生其他子執行緒的執行緒
- 主執行緒通常必須最后完成運行,因為它執行各種關閉動作,
盡管主執行緒是自動創建的,但是可以由一個 Thread 物件控制,
獲取主執行緒資訊:
public class MainThreadTest {
public static void main(String[] args) {
Thread t = Thread.currentThread();
System.out.println("當前執行緒:"+t.getName());
t.setName("MainThread");
System.out.println("當前執行緒:"+t.getName());
}
}
實作執行緒方式:
Java 中有兩種實作執行緒的方式:
1、繼承 Thread 類
2、實作 Runnable 介面
分別重寫 run( ) 方法,執行代碼寫在 run( ) 方法中
🍕10.2.2、繼承 Thread 類創建執行緒類
繼承 Thread 類是實作執行緒的一種方式,
滿足條件:
- 此類必須繼承 Thread 類,
- 重寫父類 run( ) 方法
- 將執行緒執行代碼寫在 run( ) 方法中,
如下實體:
// 繼承 Thread 類的方式創建自定義執行緒類
public class MyThread extends Thread{
// 重寫 run( ) 方法
public void run(){
// 執行緒執行任務的代碼
}
}
// 啟動執行緒的測驗類
class ThreadTest{
public static void main(String[] args){
// 使用 start() 方法啟動執行緒
MyThread myThread = new MyThread();
myThread.start(); // 啟動執行緒
}
}
🍕10.2.3、實作 Runnable 介面創建執行緒類
在使用繼承 Thread 類的方式創建執行緒的程序中,子類無法再繼承其他父類,因為 Java 不支持多繼承,
可以通過實作 Runnable 介面的方式創建執行緒,這種方式更具有靈活性,用戶執行緒還可以通過繼承,再具有其他類的特性,
Runnable 介面位于 java.lang 包中,其中只提供了一個抽象方法 run( ) 宣告,Thread 類也實作了 Runnable 介面,
如下實體:
// 實作 Runnable 介面方式創建執行緒類
public class MyThread implements Runnable{
// 重寫 run( ) 方法
public void run(){
// 執行緒執行任務的代碼
}
}
// 啟動執行緒的測驗類
class ThreadTest{
public static void main(String[] args){
// 使用 start() 方法啟動執行緒
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
myThread.start(); // 啟動執行緒
}
}
🍟10.3、執行緒的生命周期
新建的執行緒通常會在五種狀態中轉換,即:新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead),

畫的好丑
1、新建狀態:
一個 Thread 類或其子類的物件被宣告并創建,但其在還未啟動的這段時間里處于一種特殊的新建狀態中,此時,執行緒類物件已經被分配了記憶體空間和資源,并且已被初始化,但是該執行緒尚未被調度,
2、就緒狀態:
就緒狀態也被稱為可運行狀態,處于新建狀態的執行緒被啟動后,也就是呼叫 start( ) 方法后,新建執行緒將進入執行緒佇列,排隊等待 CPU 時間片,此時它已具備運行的條件,一旦輪到它使用 CPU 資源,它就可以脫離創建它的主執行緒獨立開始它的生命周期,
3、運行狀態:
當就緒狀態的行程被調度并獲得處理器資源后便進入運行狀態,該狀態表示執行緒正在運行,該執行緒已經擁有了 CPU 的占用權,
處于運行狀態的執行緒在一下四種情況下會讓出 CPU 的控制權:
- 執行緒運行完畢
- 有比當前執行緒優先級更高的執行緒搶占了 CPU
- 執行緒休眠
- 執行緒因等待某個資源而處于阻塞狀態,
4、阻塞狀態:
一個正在運行的執行緒在某些特殊情況下需要讓出 CPU 并暫時終止運行,這是,執行緒處于不可運行的狀態被稱為阻塞狀態,
例如:遇到 sleep 執行緒休眠進入執行緒阻塞狀態
5、死亡狀態:
一個執行緒的 run( ) 方法運行完畢,表明該執行緒已死亡,處于死亡狀態的執行緒不具有繼續運行的能力,
導致執行緒死亡的原因有兩個:
- 正常運行的執行緒完成了它的全部作業,即運行完 run( ) 方法的最后一條陳述句并退出;
- 當行程停止運行時,該執行緒中的所有執行緒將被強行終止,
執行緒處于死亡狀態并且沒有改執行緒的參考時,JVM 會從記憶體中洗掉該執行緒類物件,
🍟10.4、執行緒調度
🍕10.4.1、執行緒調度相關方法
常用的執行緒操作方法:
| 方法 | 描述 |
|---|---|
| int getPriority( ) | 回傳執行緒的優先級 |
| void setPriority(int newPriority) | 更改執行緒的優先級 |
| boolean isAlive( ) | 測驗執行緒是否處于活動狀態 |
| void join( ) | 行程中的其他執行緒必須等待該執行緒終止后才能運行 |
| void interrupt( ) | 中斷執行緒 |
| void yield( ) | 暫停當前正在執行的執行緒類物件并運行其他執行緒 |
🍕10.4.2、執行緒的優先級
Thread 類提供了 setPriority(int newPriority) 方法,getPriority( ) 方法用于設定和回傳指定執行緒的優先級,其中 setPriority(int newPriority) 方法引數 newPriority 可以是一個整數,范圍是 1~10;
也可以使用 Thread 類的如下三個靜態常量設定執行緒的優先級:
- MAX_PRIORITY:其值是 10 ,表示優先級最高,
- MIN_PRIORITY:其值是 1,表示優先級最低,
- NORM_PRIORITY:其值是 5,表示普通優先級,
🍟10.5、執行緒同步
執行緒異步會出現對資料訪問的安全問題,所以需要讓執行緒同步來保證資料的訪問安全性,
🍕10.5.1、實作執行緒的同步
當兩個或多個執行緒需要訪問同一資源時,需要以某種順序來確保該資源某一時刻只能被一個執行緒使用,這被稱為執行緒同步,
🍕1、同步代碼塊
代碼塊即使用 “{ }”括起來的一段代碼,使用 synchronized 關鍵字修飾的代碼塊被稱為同步代碼塊,
語法:
synchronized(obj){
//需要同步的代碼
}
如果一個代碼塊帶有 synchronized(obj) 標記,那么當執行緒執行此代碼塊使,必須先獲得 obj 變數所引向的物件的鎖,其可以針對任何代碼塊,并且可以任意指定上鎖的物件,因此靈活性更高,
🍕2、同步方法
如果一個方法的所有代碼都屬于需要同步的代碼,那么這個方法定義處可以直接使用 synchronized 關鍵字修飾,即同步方法,
語法:
訪問修飾符 synchronized 回傳值型別 方法名(引數串列){
}
//或
synchronized 訪問修飾符 回傳值型別 方法名(引數串列){
}
🍟10.6、執行緒同步的特征
所謂執行緒之間保持同步,是指不同的執行緒在執行以同一個物件作為鎖標記的同步代碼塊或同步方法時,因為要獲得這個物件的鎖而相互牽制,執行緒同步具有以下特征:
- 當多個并發執行緒訪問同一物件的同步代碼塊或同步方法時,同一時刻只能有一個執行緒運行,其他執行緒必須等待當前執行緒運行完畢后才能運行,
- 如果多個執行緒訪問的不是同一共享資源,則無需同步,
- 整篇文章屬于學習筆記加本人知識,寫了一個星期嗚嗚嗚🤣,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/404343.html
標籤:java















