Java基礎復習筆記
第01章:Java語言概述
1. Java基礎學習的章節劃分
第1階段:Java基本語法
Java語言概述、Java的變量與進制、運算符、流程控制語句(條件判斷、循環結構)、break\continue、
IDEA開發工具的使用、數組
第2階段:面向對象編程(基礎、進階、高級)
第3階段:Java高級應用
異常處理、多線程、集合框架、File類與IO流、網絡編程、日期相關的API與比較器、反射、Java8-17新特征
語言 = 語法 + 邏輯
2. 計算機的構成
- 硬件:CPU、內存、硬盤、輸入設備、輸出設備、調制解調器
- 軟件
3. 軟件
- 軟件:即一系列按照
特定順序組織
的計算機數據
和指令
的集合。- 有系統軟件和應用軟件之分。
- 系統軟件:windows、mac os、android、ios、linux
- 應用軟件:qq、微信、音樂播放器等
- 有系統軟件和應用軟件之分。
4. 人機交互方式
-
圖形化界面的方式
-
命令行的方式交互
-
DOS命令(掌握)
- cd cd.. cd/ md rd del exit cls等
5. 語言
-
計算機語言的分代
- 第1代:機器語言:0和1
- 第2代:匯編語言:出現了助記符
- 第3代:高級語言:
- 面向過程階段:C
- 面向對象階段:C++,Java,C#,Python,JS等
-
沒有“最好”的語言,只有在特定場景下相對來說,最適合的語言而已。
6. Java概述
-
Java簡史
- 1995誕生
- 1996:jdk1.0版本
- 2004:Java5.0(jdk1.5)--->里程碑式的版本;J2SE->JavaSE、J2EE->JavaEE、J2ME->JavaME
- 2014:Java8.0--->里程碑式的版本;目前,市場占有率仍然很高。(lambda表達式、StreamAPI)
- 后續:Java11、Java17都屬于LTS(長期支持版本)
-
SUN、Oracle、Google等
-
Java之父:詹姆斯·高斯林
-
Java的應用場景:
- JavaSE:開發桌面級應用 (不靠譜)
- JavaEE:開發企業級后臺應用
- JavaME:開發小型設備的應用(不靠譜)
? ----> JavaEE、Android應用、大數據開發
7. JDK的下載、安裝及環境變量的配置(重點)
- jdk下載:官網下載
- 安裝:jdk8.0和jdk17.0 (傻瓜式安裝)
- path環境變量的配置(重點)
8. 第1個Java程序
新建java文件:PersonInfo.java
class PersonalInfo{
public static void main(String[] args){
System.out.println("姓名:家琪琪\n");
//System.out.println();//換行操作
System.out.println("性別:女");
System.out.println("住址:成都青創園");
}
}
針對于第1個程序的小結及常見問題的分析
1. HelloWorld程序如下:編寫在HelloWorld.java文件中
class HelloJava{
public static void main(String[] args){
System.out.println("HelloWorld!!");
System.out.println("HelloWorld!!");
System.out.println("你好,世界!");
}
}
2. Java程序要想執行成功,需要如下的三個步驟:
第1步:編寫:將java源代碼編寫在.java結尾的源文件中。
第2步:編譯:針對于編寫好的源文件進行編譯操作。格式:javac 源文件名.java
編譯以后,會生成一個或多個.class結尾的字節碼文件。字節碼文件的名稱即為源文件中對應的類名
第3步:運行:針對于編譯好的字節碼文件,進行解釋運行操作。格式: java 字節碼文件名 或 java 類名
3. 針對于編寫過程來說:
3.1 class:是一個關鍵字,小寫,后面跟著一個類名。
3.2 編寫的類或方法必須使用一對{}。
3.3
> main()作為程序的入口出現!格式如下:
public static void main(String[] args)
> main()的格式是固定的!大家剛開始學習,可以"死記硬背"一下。
> 但是,可以考慮修改為如下的格式:
方式1:public static void main(String args[])
方式2:public static void main(String[] a) args:是arguments的縮寫
3.4 輸出語句的編寫:
> System.out.println(123); 表示:輸出123之后換行
> System.out.print(123); 表示:輸出123之后不需換行
3.5 編譯過程中的小結:
> 編譯源文件。此時要求在源文件所在的路徑下執行"javac 源文件名.java"的操作
可能編譯時報錯的情況:
情況1:如果源文件名寫錯(不包括大小寫不同的情況)或者不是在源文件所在的路徑下執行javac操作則會報錯。
情況2:編寫的程序中有非法的語法或非法的字符。
> 缺少必要的大括號、大小寫的問題(Java是嚴格區分大小寫的)、出現的標點符號必須是英文格式下的
3.6 解釋運行過程的小結:
> 針對于字節碼文件對應的類,執行java.exe命令。格式:java 類名。
> 此操作需要在字節碼文件所屬的路徑下執行。
可能運行時報錯的情況:
情況1:執行字節碼文件所在的路徑不對或字節碼文件的名寫錯了(注意,java嚴格區分大小寫,如果大小寫出錯了,仍然認為文件名寫錯了)。
情況2:可以出現運行時異常(放到第9章中講解)
3.7 說明
1. Java是嚴格區分大小寫的
2. 每一行執行語句必須以;結尾
3. 程序在編寫過程中,為了可讀性更強,增加必要的縮進,使用tab鍵即可。
4. 一個源文件中可以聲明一個或多個類。
一個源文件中最多只能有一個類聲明為public。
聲明為public的類的類名必須與源文件名相同。
9. 注釋
- 掌握:單行注釋、多行注釋
- 作用1:對程序中的代碼進行解釋說明
- 作用2:有助于調試程序
- 熟悉:文檔注釋 (可以被javadoc解析)
10. API文檔
- API:(Application Programming Interface,應用程序編程接口)是 Java 提供的基本編程接口。
- 像String、System都屬于API
- API文檔:用于解釋說明API如何使用的一個文檔。
第02章:變量與進制
1. 關鍵字(keyword)
- 關鍵字:被Java語言賦予特殊含義的字符串。
- 注意點:關鍵字都是小寫的!
- Java規范了50個關鍵字(包含了goto、const兩個保留字)
- 額外的三個字面量true、false、null雖然不是關鍵字,但是我們也把他們看做是關鍵字。
2. 標識符
- 凡是可以自己命名的地方,都是標識符。
- 標識符都有哪些位置?類名、變量名、包名、方法名、接口名、常量名等
- 標識符的命名規則
(如果不遵守,編譯不通過。要求大家遵守)
由26個英文字母大小寫,0-9 ,_或 $ 組成
數字不可以開頭。
不可以使用關鍵字和保留字,但能包含關鍵字和保留字。
Java中嚴格區分大小寫,長度無限制。
標識符不能包含空格。
- 標識符的命名規范
(如果不遵守規范,不影響程序的編譯和運行。建議大家遵守,否則容易被鄙視)
包名:多單詞組成時所有字母都小寫:xxxyyyzzz。
例如:java.lang、com.atguigu.bean類名、接口名:多單詞組成時,所有單詞的首字母大寫:XxxYyyZzz
例如:HelloWorld,String,System等變量名、方法名:多單詞組成時,第一個單詞首字母小寫,第二個單詞開始每個單詞首字母大寫:xxxYyyZzz
例如:age,name,bookName,main,binarySearch,getName常量名:所有字母都大寫。多單詞時每個單詞用下劃線連接:XXX_YYY_ZZZ
例如:MAX_VALUE,PI,DEFAULT_CAPACITY
- 標識符在聲明時,要見名知意!
3. 變量的基本使用
- 內存中的一個存儲區域,該區域的數據可以在同一類型范圍內不斷變化
- 變量的構成包含三個要素:數據類型 變量名 變量值
- Java中變量聲明的格式:數據類型 變量名 = 變量值;
- Java是一門強類型的語言。即每一個變量都規定了具體的類型。
- 使用變量注意:
- Java中每個變量必須先聲明,后使用。
- 使用變量名來訪問這塊區域的數據。
- 變量的作用域:其定義所在的一對{ }內。
- 變量只有在其作用域內才有效。出了作用域,變量不可以再被調用。
- 同一個作用域內,不能定義重名的變量。
4. 基本數據類型的變量
變量按照數據類型來分:
基本數據類型:整型(byte \ short \ int \ long ) 、浮點型(float \ double ) 、字符型char 、布爾型boolean
引用數據類型:類(class)、接口(interface)、數組(array); 注解(annotation)、枚舉(enum)、記錄(record)
- 整型變量
//1. 整型的使用:
//byte(1個字節=8bit,-128~127) \ short(2字節) \ int(4字節) \ long(8字節)
byte b1 = 12;
b1 = 127;
//①聲明變量以后,給變量賦的值必須在變量類型所允許的范圍內變化。
//b1 = 128;//因為超出了byte的范圍,所以報錯
//② 給long類型變量賦值時,要求以"l"或"L"結尾
short s1 = 123;
int i1 = 1234;
long l1 = 12313123L;
System.out.println(l1);
//③ 實際開發中,如果沒有特殊需求的話,推薦大家使用int類型來定義整型變量。
//④ 默認情況下,整型常量都是int類型
//int i2 = i1 + 2;
- 浮點類型
//2. 浮點型的使用:
// float(4字節) / double (8字節)
//① float雖然占用的空間比long小,但是表數范圍比long大,進而float精度不高。
//② 給float類型變量賦值時,要求以"f"或"F"結尾。否則,編譯不通過
double d1 = 123.456;
//d1 = 123.456456456456456456; //體會double的精度也有限
System.out.println(d1);
float f1 = 123.456f;
System.out.println(f1);
//③ 實際開發中,如果沒有特殊需求的話,推薦大家使用double類型來定義浮點型變量。
//④ 默認情況下,浮點型常量都是double類型
double d2 = d1 + 12.34; //12.34是常量,是double類型
- char類型(字符類型)
//3.字符類型的使用:char (2字節)
//① 一般情況下,我們使用一對''表示一個具體的字符。
//說明:char定義變量的話,''內有且只能有一個字符
char c1 = 'a';
//編譯不通過
//char c2 = '';
//char c3 = 'ab';
//② char類型變量的定義方式
//方式1:最常見的方式
char c4 = '中';
char c5 = '1';
char c6 = 'す';
//方式2:直接使用Unicode值來表示字符型常量
char c7 = '\u0023';
System.out.println(c7);
//方式3:使用轉義字符
char c8 = '\n';
char c9 = '\t';
System.out.println("hello" + c8 + "world");
System.out.println("hello" + c9 + "world");
//方式4:使用字符對應的ascii碼值進行賦值
char c10 = 'a';
System.out.println(c10 + 1);
char c11 = 97;
System.out.println(c10 == c11);//true
- 布爾類型(boolean)
//① 不談boolean占用內存空間的大小
//② boolean類型只能取兩個值之一:true 、 false
boolean b1 = true;
boolean b2 = false;
//③ 開發中,我們常常在if-else結構、循環結構中使用boolean類型
boolean isMarried = false;
if(isMarried){
System.out.println("很遺憾,不是單身了");
}else{
System.out.println("不錯,可以多談幾個女朋友了");
}
5. 基本數據類型變量間的運算規則
5.1 自動類型提升規則
byte、short、char ---> int ---> long ---> float ---> double
說明:
① 容量小的變量和容量大的變量做運算時,運算的結果是容量大的變量的數據類型。
(此時的容量小、容量大指的是存儲數據的范圍的大小,并非占用內存空間的大小。比如:float的容量要大于long的容量)
② byte、short、char 三者之間的變量做運算,結果是int類型。
③ 不管是自動類型提升規則,還是強制類型轉換規則都只針對于基本數據類型中的7種進行操作(除了boolean類型)
5.2 強制類型轉換規則
說明:
①看做是自動類型提升規則的逆運算
② 如果需要將容量大類型的變量轉換為容量小的類型的變量時,就需要使用強制類型轉換
③ 強制類型轉換需要使用一對()表示
④ 使用強轉符轉換時,可能造成精度的損失
6. String與8種基本數據類型變量間的運算
- String的理解
String,即為字符串類型。
聲明String類型的變量,可以使用一對""表示。
一對""內可以聲明0個、1個或多個字符
- String與基本數據類型變量間的運算
String類型是可以與8種基本數據類型的變量做運算的。
String只能與8種基本數據類型的變量做連接運算:+
連接運算的結果只能是String類型。
7. 進制(了解)
- 計算機中存儲和運算的
所有數據
都要轉為二進制
。包括數字、字符、圖片、聲音、視頻等。
7.1 常見的幾種進制
-
熟悉:
-
十進制(decimal)
- 數字組成:0-9
- 進位規則:滿十進一
-
二進制(binary)
- 數字組成:0-1
- 進位規則:滿二進一,以
0b
或0B
開頭
-
八進制(octal):很少使用
- 數字組成:0-7
- 進位規則:滿八進一,以數字
0
開頭表示
-
十六進制
- 數字組成:0-9,a-f
- 進位規則:滿十六進一,以
0x
或0X
開頭表示。此處的 a-f 不區分大小寫
-
7.2 二進制與十進制間的轉換
熟悉:二進制與十進制間的轉換(見ppt)
-
表示二進制整數時,最高位為符號位。0:正數;1:負數。
-
二進制整數在存儲時,涉及到原碼、反碼、補碼。
-
正數:三碼合一。
-
負數:負數的原碼,除符號位外,各個位取反,得到負數的反碼。
? 負數的反碼+1,得到負數的補碼。
-
-
計算機底層都是以二進制
補碼
的形式存儲數據的。
7.3 二進制與其它進制間的轉換
- 了解:二進制與八進制、十六進制間的轉換
第03章:IDEA的安裝與使用
1. 認識IDEA的地位、特點
- Java開發中占比第1。
- Eclipse?IDEA?① 符合人體工程學 ② 功能強大
2. IDEA的下載、安裝、注冊
略
3. IDEA的基本使用
- 在IDEA中能創建一個工程:Project。
- 在工程的src下寫一個HelloWorld,并運行
- 安裝課件中的第5節中的設置,作必要的修改。
4. 熟悉工程、module中jdk和設置語言級別操作
關于工程:
關于Module:
添加SDK:
5. 熟悉Project-Module-Package-Class的關系
- 上一級和下一級之間是一對多的關系。
- 掌握:新建Project、新建Module、刪除Module、導入老師的Module(難點)
6. 關于IDEA的其它操作
- 模板的使用
- 快捷鍵的使用
- Debug程序
- 插件的使用
第04章:運算符與流程控制
1. 運算符之1:算術運算符
+ - + - * / % ++ -- +
- % : 結果與被模數的符號相同。常用來判別是否能整除一個數。
- (前)++ 與 (后)++ ;(前)-- 與 (后)--
2. 運算符之2:賦值運算符
= += -= *= /= %=
- = : 與 == 的區別。= 的右邊是變量或常量。"連續賦值" (int i,j;i = j = 10;)
- += -= *= /= %= :運算后,不會改變變量的類型。(int i = 1; i *= 0.1; )
3. 運算符之3:比較運算符
> < >= <=
== !=
-
> < >= <=
只能適用于7種基本數據類型(不含boolean) -
== !=
適用于8種基本數據類型、引用數據類型。 - 比較運算符的結果是boolean類型。
4. 運算符之4:邏輯運算符
& && | || ^ !
- 邏輯運算符操作的是boolean類型,結果也是boolean類型
- & 與 && 的區別;| 與 || 的區別
5. 運算符之5:位運算符(非重點)
<< >> >>>
& | ^ ~
- 位運算符操作的整數類型
- << >> >>>的應用場景
6. 運算符之6:條件運算符
(條件表達式)? 表達式1 : 表達式2
規則:
判斷條件表達式是true還是false,如果是true,則執行表達式1;如果是false,則執行表達式2
如果將運算的結果賦給一個變量的話,要求:表達式1與表達式2的類型一致。(相同 或 滿足自動類型提升的規則即可)
- 案例:獲取兩個數的較大值;獲取三個數的最大值
- 與分支結構的if-else對比:
凡是可以使用條件運算符的地方,都可以改寫為if-else。反之,不一定。
在既可以使用條件運算符,又可以使用if-else的場景下,推薦使用條件運算符。因為條件運算符效率稍高。
7. 運算符的優先級
-
我們在開發中,如果希望某個運算符優先運算的話,主動的添加一對()。
-
常見的一些運算符優先級誰高誰低呢?基本上是
如你所想
。int x = 10; boolean y = false; if(x++ == 10 && y = true){...}
-
大家在開放時,如果涉及到多個運算符做運算,建議可以
分行寫
。
8. 流程控制語句概述
順序結構:略,即代碼從上往下依次執行
分支結構:if-else 、 switch-case
循環結構:for、while、do-while
foreach放到集合章節中講解
9. 分支結構1:if-else
- 格式
格式1:
if(條件表達式){
語句塊;
}
格式2:"二選一"
if(條件表達式) {
語句塊1;
}else {
語句塊2;
}
格式3:"多選一"
if (條件表達式1) {
語句塊1;
} else if (條件表達式2) {
語句塊2;
}
...
}else if (條件表達式n) {
語句塊n;
} else {
語句塊n+1;
}
- 說明
說明1:
> 如果多個條件表達式之間是"互斥"關系(或沒有交集的關系),則多個條件表達式誰寫在上面,誰寫在下面都可以。
> 如果多個條件表達式之間是包含關系,則通常需要將條件表達式范圍小的聲明在條件表達式范圍大的上面。
說明2:
> 我們可以在程序使用if-else的嵌套結構
> 如果if-else中一對大括號內的語句塊只有一行執行語句,則此一對大括號可以省略。但是,不建議大家省略!
說明3:
> 開發中,在一些具體的題目中,可以在if-else if -else if -... -else 結構中省略else結構。
10. 分支結構2:switch-case
- 格式
switch(表達式){
case 常量值1:
語句塊1;
//break;
case 常量值2:
語句塊2;
//break;
// ...
[default:
語句塊n+1;
break;
]
}
- 說明
1. switch-case的執行過程:
根據switch中表達式的值,依次匹配一對{}內的case結構。一旦表達式與某個case的常量值相等,則執行此case中的語句塊。
執行完此語句塊之后,如果此case中包含break,則結束當前switch-case結構。
如果此case中不包含break,則會繼續執行其后的case中的語句塊(case穿透的場景)。直到遇到break或執行完default,才會結束switch-case結構。
2. 說明:
> 在switch-case結構中可以使用break關鍵字,一旦執行,表示終止(或退出)當前switch-case結構。
> 開發中,在使用switch-case的場景中,不加break的情況要多于加break的情況。
> switch中的表達式只能是特定的如下類型的變量:
byte \ short \ char \ int ; 枚舉類型(jdk5.0新增) \ String(jdk7.0新增)
> case后的常量值,需要與switch中表達式的值進行==的判斷。如果返回true,則執行此case中的語句塊。返回false,則不執行。
> default類似于if-else結構中else。 可選的,且位置是靈活的。
- if-else 與 switch-case的對比
> 針對的變量的類型來講,if-else沒有限制,而switch-case有類型的限制,且建議case匹配的情況有限、不多的場景。
> 二者的轉換:凡是使用switch-case結構的,都可以轉換為if-else。反之,不成立。
> 開發中,在既可以使用switch-case,又可以使用if-else的情況下,推薦使用switch-case。因為其效率稍高。
> if-else的主要優勢:涉及到任何的分支結構,都可以使用if-else實現
switch-case的主要優勢:在可以使用if-else和switch-case的情況下,效率稍高。
case穿透。
11. 循環結構1:for
- 循環的概述
凡是循環結構,都有如下的4個要素:
> ① 初始化條件部分
> ② 循環條件部分 -->是boolean類型
> ③ 循環體部分
> ④ 迭代條件部分
- 格式
for(①;②;④){
③
}
執行過程:① - ② - ③ - ④ - ② - ③ - ④ - ...- ②
- 說明
1. 注意:循環條件部分必須是boolean類型。
2. break關鍵字的使用
> break可以使用在循環結構中 (復習:還可以使用在switch-case中)
> 一旦執行,就跳出當前循環結構。
12. 循環結構2:while
- 格式
①
while(②){
③
④
}
- 執行過程
① - ② - ③ - ④ - ② - ③ - ④ - ...- ②
- 說明
for循環和while循環一定可以相互轉換。
for、while循環的區別:初始化條件的作用域不同。while循環的初始化條件在while循環結束后,仍然有效。
13.循環結構3:do-while
- 格式
①
do{
③
④
}while(②);
- 執行過程
① - ③ - ④ - ② - ③ - ④ - 。。。- ②
- 說明
do-while相較于其他循環的區別:至少執行一次循環體。
在循環條件第1次判斷時,如果是true的情況下,三個循環結構可以相互轉換的。
- 使用場景
for循環:有明確的循環、遍歷次數時。比如:遍歷100以內的自然數、遍歷數組
while循環:沒有明確的循環、遍歷的次數時。比如:使用迭代器遍歷集合。
do-while循環:確保至少執行一次。
14. "無限"循環
- 結構
while(true) 、 for(;;)
- 使用場景
不確定循環的次數時,使用此結構
- 結束循環的方式
在循環內部,滿足某個條件的情況下,執行break。
- 注意:必須確保此循環可以結束。否則就是死循環!我們開發中要避免死循環
15. 嵌套循環
- 格式
外層循環{
內層循環{
}
}
-
說明:上述的外層循環、內存循環可以是for、while、do-while
-
技巧:
- 外層循環執行m次,內層循環執行n次。意味著內層循環的循環體執行 m * n次
- 外層控制行數,內層控制列數
?
16. break、continue關鍵字的使用
相同點:① 都可以使用在循環結構中 ② 其后不能編寫執行語句
不同點:① 結束循環結構;結束當次循環 ②使用范圍:break:switch-case結構中使用
17. Scanner的使用、隨機數的獲取
- Scanner的使用
1. 如何從鍵盤獲取數據? 使用Scanner類
2. 如何使用Scanner類,從鍵盤獲取數據呢? (掌握)
步驟1:導包
import java.util.Scanner
步驟2:創建Scanner的對象(或實例)
Scanner scanner = new Scanner(System.in);
步驟3:通過Scanner的對象,調用Scanner類中聲明的方法,從鍵盤獲取指定類型的變量
scanner.nextXxx()
步驟4:關閉Scanner
scanner.close();
3. Scanner類中提供了如下的獲取不同類型變量的方法:
獲取byte: nextByte();
獲取short: nextShort();
獲取int: nextInt();
獲取long: nextLong();
獲取float: nextFloat();
獲取double: nextDouble();
獲取boolean: nextBoolean();
注意,沒有提供獲取字符的方法。我們可以通過獲取字符串的方法,來獲取字符。
獲取String: next() / nextLine()。
如何獲取一個字符:next().charAt(0)
- 如何獲取隨機數
1. 調用Math類中的random(),可以獲取一個[0.0,1.0)范圍內的隨機浮點數。
2. 如何獲取[0,9]范圍的隨機整數:(int)(Math.random() * 10);
如何獲取[1,10]范圍的隨機整數:(int)(Math.random() * 10) + 1;
如何獲取[0,100]范圍的隨機整數:(int)(Math.random() * 101);
如何獲取[10,100]范圍的隨機整數:(int)(Math.random() * 91) + 10; //[10,100]
公式:如何獲取[a,b]范圍的隨機整數:(int)(Math.random() * (b - a + 1) + a)
18. 階段項目1:谷粒記賬軟件
略
第05章:數組
1. 數組的概述(理解)
1. 數組的理解
概念:
數組(Array),是多個相同類型數據按一定順序排列的集合,并使用一個名字命名,
并通過編號的方式對這些數據進行統一管理。
簡稱:多個相同類型的數據的組合
Java中的容器:數組、集合框架(用于存儲不同特點的多個數據)
2. 幾個相關的概念
> 數組名(即為容器的名稱)
> 元素 (即為數組中具體的一個個的數據)
> 數組的長度(容器中元素的個數)
> 數組的角標、下標、下角標、索引、index (即為數組中元素的具體位置。從0開始)
3. 數組的特點:
- 數組本身是`引用數據類型`,而數組中的元素可以是`任何數據類型`,包括基本數據類型和引用數據類型。
- 創建數組對象會在內存中開辟一整塊`連續的空間`。占據的空間的大小,取決于數組的長度和數組中元素的類型。
- 數組中的元素在內存中是依次緊密排列的,有序的。
- 數組,一旦初始化完成,其長度就是確定的。
- 數組的`長度一旦確定,就不能修改`。
- 我們可以直接通過下標(或索引)的方式調用指定位置的元素,速度很快。
- 數組名中引用的是這塊連續空間的首地址。
4. 復習:變量按照數據類型的分類
4.1 基本數據類型:byte \ short \ int \ long ;float \ double ;char ;boolean
4.2 引用數據類型:類、數組、接口; 枚舉類型、注解類型、記錄類型(Record)
5. 數組的分類
5.1 按照元素的類型:基本數據類型元素的數組、引用數據類型元素的數組
5.2 按照數組的維數來分:一維數組、二維數組、....
2. 一維數組的使用(重點)
(6個基本點)
> 數組的定義:靜態初始化、動態初始化
> 數組元素的表示:使用角標,角標從0開始,到數組的長度-1結束。
> 數組的長度:length
> 遍歷數組:for循環
> 數組元素的默認值:記住。后續類中屬性的默認值也如此。
> 數組的內存解析(難點)---> 具體圖示見chapter06章節的module中即可。
3. 二維數組的使用(熟悉)
- 二維數組的理解
> 角度1:一個一維數組又作為了另一個數組arr的元素。則數組arr就稱為二維數組。
> 角度2:一個數組arr1的元素,仍是是一個數組,則arr1稱為二維數組
> 數組,屬于引用數據類型;數組的元素也可以是引用數據類型。--> 數組的元素,還可以是數組。
> 說明:其實Java中不存在二維、三維、..數組,只是將一個上述的arr或arr1稱為是二維數組。
> 區分:外層元素、內層元素
- 基本內容
二維數組的使用(6個基本點)
> 數組的定義
> 數組元素的調用
> 數組的長度
> 數組的遍歷
> 數組元素的默認初始化值(稍難)
> 數組的內存解析(難點)---> 具體圖示見chapter06章節的module中即可。
- 數組元素的默認值
1. 二維數組元素的默認初始化值
1.1 動態初始化方式1:(比如:int[][] arr = new int[3][4])
外層元素:存儲的是地址值。(具體來說,就是外層元素指向的一維數組的地址值)
內層元素:與一維數組元素的默認值相同。
> 整型:0
> 浮點型:0.0
> 字符型:0 或 '\u0000'
> 布爾型:false
> 引用類型:null
1.2 動態初始化方式2:(比如:int[][] arr = new int[3][])
外層元素:null
內層元素:不存在。一旦調用會報異常(NullPointerException)
4. 數組的常用算法(熟練)
- 算法常用操作1
1. 數值型數組特征值統計
這里的特征值涉及到:平均值、最大值、最小值、總和等
2. 數組元素的賦值(實際開發中,遇到的場景比較多)
3. 數組的復制、賦值
4. 數組的反轉
- 算法常用操作2
1. 數組的擴容與縮容
2. 數組元素的查找(或搜索)
順序查找:
> 優點:簡單,好理解,數組沒有任何的前提限制。(比如:有序)
> 缺點:相較于二分法查找更慢一些。
二分法查找:
> 優點:相較于順序查找,更快。O(logN)
> 缺點:必須此數組有序。
3. 排序算法
3.1 排序算法的衡量標準:
> 時間復雜度:更為關心的標準。
Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n^2)<Ο(n^3)<…<Ο(2^n)<Ο(n!)<O(n^n)。
> 空間復雜度:常出現以空間換時間的做法。
> 穩定性
3.2 排序的分類:內部排序、外部排序
內部排序的具體算法:十種。
我們需要關注的幾個排序算法:
> 冒泡排序:簡單、容易實現;企業筆試中容易考。時間復雜度:O(n^2)。要求大家可以手寫。
> 快速排序:快速、開發中需要排序情況下的首選。時間復雜度:O(nlogn)。要求大家至少可以說明其實現思路。
5. Arrays:數組的工具類(熟悉)
1. Arrays類所在位置
java.util.Arrays
2. 作用:
封裝了針對數組的常用操作。比如:排序、二分查找、比較數組是否相等、遍歷等。
3. 常用方法:
sort(int[] arr) / binarySearch(int[] arr,int target) / toString(int[] arr)
6. 小結:數組中的常見異常
1. 數組的使用中常見的異常小結
> ArrayIndexOutOfBoundsException:數組角標越界異常
> NullPointerException:空指針異常
2. 出現異常會怎樣?如何處理?
> 一旦程序中出現異常,且沒有處理的情況下,程序就終止執行。
> 目前大家編程時,如果出現上述異常。回來根據異常的提示,修改代碼,確保后續運行不再出現。
第06章:面向對象-基礎
面向對象內容的三條主線:
> 類及類的內部成員:屬性、方法、構造器;代碼塊、內部類
> 面向對象的三大特征:封裝性、繼承性、多態性
> 其它關鍵字的使用:package、import、this、super、static、final、abstract、interface等
1. 理解:面向過程vs面向對象
簡單的語言描述二者的區別
> 面向過程:以`函數`為組織單位。是一種“`執行者思維`”,適合解決簡單問題。擴展能力差、后期維護難度較大。
> 面向對象:以`類`為組織單位。是一種“`設計者思維`”,適合解決復雜問題。代碼擴展性強、可維護性高。
2.2 二者關系:在面向對象的編程中,具體的方法中,仍然會體現面向過程的思想。所以二者是合作關系。
2. 面向對象的要素:類、對象
-
區分類與對象
- 類:抽象的、概念上的定義
- 對象:具體的,實實在在存在的,由類派生出來的
-
設計類,就是設計類的成員:屬性、方法
-
面向對象完成具體功能的操作的三步流程(非常重要)
步驟1:創建類,即設計類的內部成員(屬性、方法) 步驟2:創建類的對象。 步驟3:通過"對象.屬性" 或 "對象.方法"的方式,完成相關的功能。
-
對象的內存解析
- JVM內存分配:虛擬機棧、堆、方法區(目前用不到)、程序計數器(略)、本地方法棧(略)
- 虛擬機棧:存放的是方法對應的棧幀,每個棧幀中存放方法中聲明的局部變量。
- 堆:new出來的"東西":數組實體、對象實體(含成員變量)
- 創建類的1個對象、創建類的多個對象(內存解析圖建議大家都自己畫畫)
- JVM內存分配:虛擬機棧、堆、方法區(目前用不到)、程序計數器(略)、本地方法棧(略)
3. 類的成員之一:屬性(重點)
1.變量的分類:
- 角度一:按照數據類型來分:基本數據類型(8種)、引用數據類型(數組、類、接口;注解、枚舉、記錄)
- 角度二:按照變量在類中聲明的位置來分:成員變量、局部變量
2. 成員變量的幾個稱謂:
成員變量 <=> 屬性 <=> field(字段、域)
3. 區分成員變量 vs 局部變量
3.1 相同點:(了解)
> 都有三個要素(數據類型、變量名、變量值)
> 聲明的格式相同:數據類型 變量名 = 變量值
> 變量都是先聲明后使用
> 變量都有作用域,在其作用域內是有效的
3.2 不同點:
① 類中聲明的位置的不同:
> 成員變量:聲明在類內部、方法等結構的外部。
> 局部變量:聲明在方法內部、方法的形參、構造器的內部、構造器的形參、代碼塊的內部等
② 在內存中分配的位置不同:
> 成員變量:隨著對象實體在堆空間進行分配而分配(或存儲)
> 局部變量:存儲在棧空間。
③ 生命周期:
> 成員變量:隨著對象的創建而產生,隨著對象的消亡而消亡
> 局部變量:(以方法為例)隨著方法的調用而產生,隨著方法的調用結束而消亡。
> 拓展:每一個方法的執行,都對應著一個棧幀加載進棧中。局部變量就存儲在每個方法對應的棧幀中。
當方法執行結束時,對應的棧幀就彈出棧,進而棧幀中的局部變量也彈出,進而消亡。
④ 作用域:
> 成員變量:在整個類的內部是有效的。---> 類的方法中是可以調用類中的成員變量的。
> 局部變量:以方法為例,作用域僅限于方法內部。
⑤ 是否可以有權限修飾符進行修飾:(超綱)
> 成員變量:可以被不同的權限修飾符進行修飾。(后面講封裝性時,具體說:private、public、protected、缺省)
> 局部變量:不可以被權限修飾符進行修飾。一旦修飾,編譯不通過。
⑥ 是否有默認值:
> 成員變量:都有默認值
默認值的情況與不同類型的一維數組的元素的默認值相同。
> 整型:0
> 浮點型:0.0
> 字符型:0
> 布爾型:false
> 引用類型:null
> 局部變量:沒有默認值。
意味著在調用之前必須要顯示賦值。如果不賦值,就報錯。
> 特別的:方法的形參在方法調用時賦值即可。
4. 類的成員之二:方法(重點)
4.1 方法的使用
1. 使用方法的好處
將功能封裝為方法的目的是,可以實現代碼重用,減少冗余,簡化代碼。
2. 使用舉例
- Math.random()的random()方法
- Math.sqrt(x)的sqrt(x)方法
- System.out.println(x)的println(x)方法
- new Scanner(System.in).nextInt()的nextInt()方法
- Arrays類中的binarySearch()方法、sort()方法、equals()方法
3. 方法聲明的格式
舉例:public void eat()
public void sleep(int hour)
public String getName()
public String playGame(String game)
格式:
權限修飾符 返回值類型 方法名(形參列表){
方法體
}
4. 具體的方法聲明的細節
4.1 權限修飾符:體現此方法被調用時,是否能被調用的問題。(主要放到封裝性的時候講解)
暫時,大家在聲明方法時,先都使用public修飾即可。
4.2 返回值類型:(難點)
> 分類:有具體的返回值的類型(指明具體的數據類型) 、 沒有返回值類型(使用void)
> 情況1:有具體的返回值的類型的要求:既然有返回值的類型,則要求此方法在執行完時,一定要返回
滿足此類型的一個變量或常量。
> 內部使用"return 變量(或常量)"的方法,返回數據
> 情況2:沒有返回值類型:內部就不需要使用return結構了。
> (難點)其實,我們在此方法中也可以使用return,僅表示結束此方法。
開發中,設計一個方法時,是否需要設計返回值類型?
> 根據題目的要求設計。
> 具體問題具體分析:調用完此方法之后,是否需要一個結果性的數據,供之后使用。如果有必要,就設計有返回值類型的場景即可。
4.3 方法名:屬性標識符,定義時需要滿足標識符的命名規則、規范、"見名知意"。
4.4 形參列表:(難點)
> 在一個方法的一對小括號中可以聲明形參列表,形參的個數可以為0個、1個或多個。
> 如果有形參的話,格式為: (數據類型1 形參名1,數據類型2 形參名2,...)
開發中,設計一個方法時,是否需要提供形參呢?
> 根據題目的要求設計。
> 具體問題具體分析:調用此方法時,是否存在不確定性的數據。如果有,則以形參的方法傳入此不確定的數據。
4.5 方法體:即為調用方法時,執行的代碼。可以使用當前方法聲明的形參,使用類中的成員變量。
5. 注意點
> Java里的方法`不能獨立存在`,所有的方法必須定義在類里。
> 方法內可以使用類中的成員變量
> 方法內不可以定義方法,但是方法內可以調用本類中的其它方法。 ---> 遞歸方法中談方法內自己調用自己。
> 類中不可以定義多個相同的方法。---> 方法的重載
4.2 return關鍵字
1. return的作用
> 作用1:結束當前方法的執行
> 作用2:"return + 變量/常量"結構在方法結束的同時,還可以返回一個數據。
2. 使用注意點:
與break、continue類似,其后不能聲明執行語句。
5. 內存的分配使用
5.1 方法調用的內存解析
- 形參:方法聲明時,一對小括號內聲明的參數,簡稱:形參
- 實參:方法調用時,實際賦值給形參的值,稱為:實參
過程概述:
每當調用一個方法時,方法就以棧幀的方法加載進虛擬機棧中。方法中聲明的局部變量存放在棧幀中。
當方法執行結束時,棧幀就會彈出棧。棧幀中存放的局部變量也隨之消亡。
5.2 目前為止,內存分析(重要)
- 基本原則
1、JVM中內存劃分
- 棧:以棧幀為基本單位(每個方法對應一個棧幀);棧幀里存放局部變量。
- 堆:new 出來的"東西":數組實體(含數組元素)、對象實體(含成員變量)
2、區分清成員變量(類內部、方法外聲明的)、局部變量(方法的形參、方法內定義的變量、構造器內定義的變量、構造器的形參、代碼塊內部等)
3、值傳遞機制:
- 如果參數是基本數據類型,傳遞的是基本數據類型變量存儲的
數據值
- 如果參數是引用數據類型,傳遞的是引用數據類型變量存儲的
地址值
6. 再談方法
6.1 方法的重載(overload )
1. 定義:
在同一個類中,允許存在一個以上的同名方法,只要它們的參數列表不同即可。滿足這樣特點的多個方法彼此之間稱為
方法的重載。
2. 總結為:"兩同一不同":同一個類、相同的方法名;形參列表不同(參數的個數不同,參數的類型不同)
> 重載與否與形參名沒有關系、返回值類型沒有關系、權限修飾符沒有關系
3. 舉例
> Arrays中的重載的binarySearch(xxx) \ equals(xxx,xxx) \ toString(xxx)
> System.out的多個重載的println();
4. 如何判斷兩個方法是相同的呢?(換句話說,編譯器是如何確定調用的某個具體的方法呢?)
> 在同一個類中,只要兩個方法的方法名相同,且參數列表相同(參數的個數相同且參數類型相同),
則認為這兩個方法是相同的。
> 與方法的權限修飾符、返回值類型、形參名都沒有關系。
> 在同一個類,不能編寫兩個相同的方法的。
后續會講:方法的重寫(overwrite / override)
面試題:方法的重載與重寫的區別?
throw \ throws
Collection \ Collections
final \ finally \ finalize
String \ StringBuffer \ StringBuilder
ArrayList \ LinkedList
。。。
== 、equals()
抽象類、接口
6.2 可變個數形參的方法
1. 使用場景
JDK5.0的新特性。
如果方法在調用時,參數的類型是確定的,但是參數的個數不確定,則可以考慮使用可變個數形參的方法。
2. 格式:類型 ... 變量名
3. 說明:
> 可變個數形參的方法在調用時,可以傳入0個,1個或多個參數。
> 可變個數形參的方法與參數是其它類型的同名方法構成重載。
> 可變個數形參的方法與參數是同樣類型的數組參數構成的方法,在方法名相同的情況下,不構成重載。即兩個方法不能
同時存在。
> 可變個數的形參在編譯器看來就是同一個類型的數組參數
> 規定:可變個數的形參需要聲明在方法形參列表的最后
> 一個方法的形參位置,最多只能有一個可變個數的形參
/*
String sql1 = "update customers set name = ?,salary = ? where id = ?";
String sql2 = "delete from customs where id = ?";
public void update(String sql,Object ... objs){
//使用可變形參objs中的各個元素值給形參sql中的?賦值
}
*/
6.3方法的參數傳遞機制(難點、重點)
1. 對于方法內聲明的局部變量來說:
> 如果此局部變量是基本數據類型的,則將基本數據類型變量保存的數據值傳遞出去
> 如果此局部變量是引用數據類型的,則將引用數據類型變量保存的地址值傳遞出去
2. 方法的參數的傳遞機制:值傳遞
2.1 概念(復習)
形參:方法聲明時,一對小括號內聲明的參數,簡稱:形參
實參:方法調用時,實際賦值給形參的值,稱為:實參
2.2 規則
> 如果此形參是基本數據類型的,則將基本數據類型的實參保存的數據值傳遞給形參
> 如果此形參是引用數據類型的,則將引用數據類型的實參保存的地址值傳遞給形參
3. 面試題:Java中的參數傳遞機制是什么? 值傳遞機制。
6.4 遞歸方法(熟悉)
1. 何為遞歸方法?
方法自己調用自己的現象就稱為遞歸。
2. 遞歸方法分類
直接遞歸、間接遞歸。
3. 使用說明:
- 遞歸方法包含了一種`隱式的循環`。
- 遞歸方法會`重復執行`某段代碼,但這種重復執行無須循環控制。
- 遞歸一定要向`已知方向`遞歸,否則這種遞歸就變成了無窮遞歸,停不下來,類似于`死循環`。最終發生`棧內存溢出`。
7. 對象數組(難點)
1. 何為對象數組?如何理解?
數組中的元素,如果存儲的是對象的話,則稱此數組為對象數組。
2. 舉例:
String[] arr = new String[10];
arr[0] = "hello";
arr[1] = new String("abc");
Person[] arr1 = new Person[10];
arr1[0] = new Person();
Phone[] arr2 = new Phone[10];
3. 內存解析:
數組名(比如:stus)存儲在棧空間
創建的20個學生對象,存儲在堆空間中。學生對象的地址值存儲在數組的每個元素中。
8. 關鍵字:package、import
- package:包,指明了Java中的類、接口等結構所在的包。聲明在文件的首行
- import:導入。指明在當前類中使用的其它包中的結構。聲明在package下,類的聲明之前。
一、package關鍵字的使用
1. 說明
- package,稱為包,用于指明該文件中定義的類、接口等結構所在的包。
- 一個源文件只能有一個聲明包的package語句
- package語句作為Java源文件的第一條語句出現。若缺省該語句,則指定為無名包。以后聲明源文件時,不要使用無名包。
- 包名,屬于標識符,滿足標識符命名的規則和規范(全部小寫)、見名知意
- 包名推薦使用所在公司域名的倒置:com.atguigu.xxx。
- 大家取包名時不要使用"`java.xx`"包,否則運行會報錯
- 包對應于文件系統的目錄,package語句中用 “.” 來指明包(目錄)的層次,每.一次就表示一層文件目錄。
- 同一個包下可以聲明多個結構(類、接口),但是不能定義同名的結構(類、接口)。不同的包下可以定義同名的結構(類、接口)
2. 包的作用
- 包可以包含類和子包,劃分`項目層次`,便于管理
- 幫助`管理大型軟件`系統:將功能相近的類劃分到同一個包中。比如:MVC的設計模式
- 解決`類命名沖突`的問題 ---> 不同包下可以命名同名的類。
- 控制`訪問權限` ---> 講了封裝性,大家就清楚了。
二、import關鍵字的使用
- import:導入,后面跟一個具體包下的類或接口等結構。
-為了使用定義在其它包中的Java類,需用import語句來顯式引入指定包下所需要的類。
相當于`import語句告訴編譯器到哪里去尋找這個類`。
- import語句,聲明在包的聲明和類的聲明之間。
- 如果需要導入多個類或接口,那么就并列顯式多個import語句即可
- 如果使用`a.*`導入結構,表示可以導入a包下的所有的結構。
舉例:可以使用java.util.*的方式,一次性導入util包下所有的類或接口。
- 如果導入的類或接口是java.lang包下的,或者是當前包下的,則可以省略此import語句。
- 如果已經導入java.a包下的類,那么如果需要使用a包的子包下的類的話,仍然需要導入。
- 如果在代碼中使用不同包下的同名的類,那么就需要使用類的全類名的方式指明調用的是哪個類。
- (了解)`import static`組合的使用:調用指定類或接口下的靜態的屬性或方法
9. 面向對象的特征一:封裝性
- 什么是封裝性?
在Java實現項目時,將不用功能的代碼封裝進不同的方法。使用Java給我們提供的4種權限修飾對類及類的內部成員進行修飾。
體現被修飾的結構在調用時的可見性的大小。
- 如何體現封裝性?
> 舉例1:類中的屬性私有化,提供公共的get()和set()方法,用于獲取或設置此屬性的值。
> 舉例2:如果類中存在一些方法,這些方法只在類的內部使用,不希望對外暴露,則可以將這些方法聲明為私有的。
> 舉例3:單例設計模式。(后面講static的時候說)
- 為什么需要封裝性?
- `高內聚`:類的內部數據操作細節自己完成,不允許外部干涉;
- `低耦合`:僅暴露少量的方法給外部使用,盡量方便外部調用。
- 通俗的講,把該隱藏的隱藏起來,該暴露的暴露出來。這就是封裝性的設計思想。
10. 類的成員之三:構造器
1. 構造器的理解
體會1: Scanner scan = new Scanner(System.in);
Person per = new Person();
體會2:
construct : v. 建設、建造
construction: n. 建設、建造 CCB 中國建設銀行 ICBC
constructor : n.建設者,建造者
2. 構造器的作用
>作用1:搭配new關鍵一起,用于對象的創建
>作用2:用于初始化對象中的成員變量
3. 構造器的使用說明
> 一個類中,如果沒有顯式提供構造器的話,則JVM會默認提供一個空參的構造器。(其權限修飾符與類的權限修飾符相同)
> 聲明格式:權限修飾符 類名(形參列表){}
> 一個類的多個構造器,彼此構成重載
> 如果一個類中,一旦顯式的聲明了構造器,則JVM不再提供默認的空參的構造器了。
> 結論:凡是類,都有構造器(自始至終都是對的)
11. 其它幾個小知識
11.1 類中實例變量的賦值位置及順序
0.實例變量:屬于屬性(或成員變量),不使用static修飾即可。
1. 在類的屬性中,可以有哪些位置給屬性賦值?
> ① 默認初始化 ---> 只執行一次
> ② 顯式初始化 ---> 只執行一次
> ③ 構造器中初始化 ---> 只執行一次
*********************************
> ④ 創建對象以后,通過"對象.屬性" 或"對象.方法"的方式,給屬性賦值 ---> 可以多次執行
2. 這些位置執行的先后順序是怎樣?
① - ② - ③ - ④
3. 以上操作在對象創建過程中可以執行的次數如何?
①、②、③:只執行一次
④:可以多次執行
11.2 JavaBean
所謂JavaBean,是指符合如下標準的Java類:
- 類是公共的
- 有一個無參的公共的構造器
- 有屬性,且有對應的get、set方法
11.3 UML類圖
理解
11.4 匿名對象
//匿名對象
System.out.println(new Circle(2.5).findArea());
//知識點1:如上寫法的匿名對象,只能被調用一次。
System.out.println(new Circle(2.5).getRadius());
//知識點2:開發中,常常將匿名對象作為參數傳遞給方法的形參。
Test4_5 test = new Test4_5();
test.show(new Circle(3.4));
第07章:面向對象-進階
1. 關鍵字:this
- this可以調用屬性、方法;構造器。
- 記住:this必須使用的場景:屬性與形參同名時;調用重載的構造器
2. 面向對象特征二:繼承性
-
為什么需要繼承性?
- 繼承的出現減少了代碼冗余,提高了代碼的復用性。
- 繼承的出現,更有利于功能的擴展。
- 繼承的出現讓類與類之間產生了
is-a
的關系,為多態的使用提供了前提。
-
什么是繼承性?
-
class B extends A{}
繼承中的基本概念:
A類:父類、SuperClass、超類、基類
B類:子類、SubClass、派生類
-
-
繼承性的基本使用
1. 有了繼承性以后:
> 子類繼承父類以后,就獲取了父類中聲明的所有的屬性和方法。 ----> 刻畫是否存在此屬性、方法
但是,由于封裝性的影響,可能導致子類不能調用。 ----> 刻畫能否調用此屬性、方法
> extends: 繼承。還可以理解為“擴展、延展”。意味著子類在繼承父類的基礎上,還可以擴展自己特有的屬性、方法。
父類、子類的關系不同于集合、子集的關系。
2. 默認的父類:
如果一個類顯式聲明了父類,則其父類為指定聲明的父類。
如果一個類沒有顯式聲明其父類,則默認繼承于java.lang.Object類。
3. 補充說明:
> 一個父類可以被多個子類繼承。
> 一個子類只能聲明一個父類。----> Java中類的單繼承性。
> Java中的類支持多層繼承。
> 子類、父類是相對的概念。
> 概念:直接父類、間接父類
> Java中的任何類(除了java.lang.Object類)都直接或間接的繼承于java.lang.Object類。
3. 方法的重寫
1. 為什么需要方法的重寫?
子類繼承父類以后,父類中的方法在權限允許的情況下,子類可以直接調用。但是我們在一些場景中發現,父類
中的方法不適用于子類。怎么處理呢?需要使用方法的重寫。
舉例(銀行賬戶):
class Account{ //賬戶
double balance; //余額
//取錢
public void withdraw(double amt){
if(balance >= amt){
balance -= amt;
System.out.println("取款成功");
}
}
//...
}
class CheckAccount extends Account{ //信用卡賬戶
double protectedBy; //可透支額度
//取錢
public void withdraw(double amt){
if(balance >= amt){
balance -= amt;
System.out.println("取款成功");
}else if(protectedBy >= amt - balance){
protectedBy -= amt - balance;
balance = 0;
System.out.println("取款成功");
}else{
System.out.println("取款失敗");
}
}
}
2. 何為方法的重寫?
子類繼承父類以后,對父類中繼承過來的方法進行覆蓋、覆寫的操作。此操作就稱為方法的重寫。
3. 方法重寫應遵循的規則
[復習]方法聲明的格式:權限修飾符 返回值類型 方法名(形參列表){ 方法體 }
具體規則:稱謂:父類被重寫的方法;子類重寫父類的方法
> 子類重寫父類的方法 與 父類被重寫的方法的方法名、形參列表相同。
> 子類重寫父類的方法的權限修飾符不小于父類被重寫的方法的權限修飾符
> 返回值類型:
> 父類被重寫的方法的返回值類型為void,則子類重寫父類的方法的返回值類型必須為void
> 父類被重寫的方法的返回值類型為基本數據類型,則子類重寫父類的方法的返回值類型必須為同類型的基本數據類型
> 父類被重寫的方法的返回值類型為引用數據類型,則子類重寫父類的方法的返回值類型與父類的相同,或是父類的類型的子類。
技巧:建議子類重寫父類的方法時,我們將權限修飾符、返回值類型都聲明為與父類的方法相同的。
注意點:
> 子類不能重寫父類中聲明為private權限的方法。
4. 面試題:區分方法的重載(overload)與重寫(override / overwrite)
重載:"兩同一不同"
重寫:子類在繼承父類以后,可以對父類中的同名同參數的方法進行覆蓋、覆寫。此操作即為方法的重寫。
具體的規則為:....。
4. 關鍵字:super
-
super調用父類的屬性、方法;構造器
-
使用場景:子父類中出現同名屬性;子類重寫了父類的方法時。
? super調用構造器,體現加載父類的結構。
-
5. 子類對象實例化的全過程(了解)
1. 從結果的角度來看:---->體現為類的繼承性。
當子類繼承父類以后,子類就獲取了父類(直接父類、所有的間接父類)中聲明的所有的屬性、方法。
當我們創建了子類對象以后,在堆空間中就保存了子類本身及其所有的父類中聲明的屬性。同時,子類對象在權限允許
的情況下,可以調用子類及其所有的父類中聲明的方法。
2. 從過程的角度來看:
當我們通過子類的構造器創建對象時,一定會直接或間接的調用到其直接父類的構造器,其直接父類的構造器同樣會
直接或間接的調用到其父類的構造器,...,以此類推,最終一定會調用到java.lang.Object類的構造器為止。
因為我們調用過所有的父類的構造器,進而所有的父類就需要加載到內存中,進而堆空間中就有所有父類中聲明的屬性。
以及可以在權限允許的情況下,調用父類中聲明的方法。
問題:在創建子類對象的過程中,一定會調用父類中的構造器嗎? yes!
3. 問題:創建子類的對象時,內存中到底有幾個對象?
只有1個!
6. 面向對象特征三:多態性
6.1 向上轉型:多態
- Java中的多態性體現為:子類對象的多態性(狹義上理解)。即父類的引用指向子類的對象。
- 應用場景:當通過父類的引用調用方法時,實際執行的是子類重寫父類的方法。
- 好處:多態性常使用在方法的形參位置。多態的出現,極大的減少了方法的重載,同時有利于程序的擴展。
- 舉例:① equals(Object obj) ② Account - Customer : setAccount(Account acct) ③ 凡是代碼中出現了抽象類、接口,都可以體現為多態性。
- 共識:Java中的多態性(廣義上理解):1、子類對象的多態性。 2、方法的重寫。
6.2 向下轉型:多態的逆過程
- Student s = (Student)new Person(); //編譯通過,運行不通過。
- 如何向下轉型:使用強轉符:()
- 可能出現的問題:可能會出現ClassCastException異常
- 如何解決?建議在強轉前進行instanceof的判斷。
7. Object類的使用
1. Object類的說明
> java.lang.Object類是所有Java類(除了自己以外)的根父類。
> java.lang.Object類中沒有聲明屬性,聲明有一個空參的構造器:Object(){}
下面重點關注java.lang.Object類中聲明的方法。在權限允許的情況下,任何一個類的對象都可以調用。
2. 常用方法
重點方法:equals(Object obj) \ toString()
熟悉方法:clone() \ finalize()
目前不需要關注:getClass() \ hashCode() \ wait() \ wait(xxx) \ notify() \ notifyAll()
7.1 equals()方法
區分 == 和 equals()
1. == : 運算符,適用于基本數據類型、引用數據類型
equals():方法,適用于引用數據類型
2. 針對于引用數據類型, == :用來比較兩個引用變量的地址值是否相等。(或判斷兩個引用是否指向同一個對象)
equals(): 需要區分此方法是否被重寫過。具體見3
3.
3.1 像String、Date、包裝類、File等類,它們都重寫了Object類中的equals()方法,用于比較對象的實體內容
是否相等。如果相等,就返回true。
3.2 對于自定義的類,如果沒有重寫Object類中的equals()方法,則仍然比較兩個對象的地址值是否相等。
如果我們重寫Object類中的equals()方法的話,通常也是用來比較兩個對象的實體內容是否相等。
int i = 65;
int j = 65;
sout(i == j);//true
char c = 'A';
sout(i == c);//true
float f = 65.0F;
sout(i == f);//true
7.2 toString()方法
1. Object類中toString()的定義:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
2. 開發中的使用場景
> 像String、Date、包裝類、File等類,它們都重寫了Object類中的toString(),用于返回當前對象的實體內容。
> 對于自定義的類,如果沒有重寫Object類中的toString()方法,則仍然返回當前對象的類型及地址
如果重寫了Object類中的toString()方法,通常也都是返回當前對象的實體內容。
3. 開發中使用說明:
對于自定義的類,當我們調用對象的toString()方法時,習慣上希望輸出對象的實體內容。所以,需要重寫Object
類中的toString(),否則就是返回當前對象的類型及地址了。
8. 項目二:拼電商客戶管理系統
- 項目中主要的類:
- (重點)封裝客戶數據的類:Customer
- (重點)管理多個客戶對象的類:CustomerList
- 與用戶交互的管理界面操作的類:CustomerView
- 封裝Scanner的工具類:CMUtility
第08章:面向對象-高級
1. 關鍵字:static
2. static 用來修飾的結構:屬性、方法;代碼塊、內部類
3. static修飾屬性
3.1 復習:變量的分類
方式1:按照數據類型: 基本數據類型、引用數據類型
方式2:按照類中聲明的位置:
成員變量(或屬性):以是否使用static修飾
> 使用static修飾 : 類變量(或靜態變量)
> 不使用static修飾 : 實例變量(或非靜態變量)
局部變量:方法內聲明的變量、方法形參、構造器內聲明的變量、構造器形參、代碼塊內聲明的變量等。
3.2 靜態變量:類中的屬性使用static進行修飾。
對比靜態變量與實例變量:
① 個數
>靜態變量:內存中只存在一份。與具體對象的個數,以及是否存在對象都無關。
>實例變量:歸屬于具體的對象所有。進而創建過幾個對象,就存在一個實例變量。
② 內存位置
>靜態變量:jdk6:存放在方法區。 從jdk7開始,存放在堆空間中。(注意:不在具體的對象內部)
>實例變量:堆空間存儲了對象實體。在具體的對象實體中,保存著實例變量。
③ 加載時機
>靜態變量:隨著類的加載而加載。(即類加載完成時,此靜態變量就分配好了內存空間)
>實例變量:隨著對象的創建,在堆空間此對象內部,分配內存存儲具體的實例變量。
④ 調用者
>靜態變量:可以被類調用,也可以被類的對象調用。
>實例變量:只能被類的對象調用
⑤ 判斷是否可以調用 ---> 從生命周期的角度解釋
類變量 實例變量
類 yes no
對象 yes yes
⑥ 消亡時機
>靜態變量:隨著類的卸載而消亡。
>實例變量:隨著對象的消亡而消亡。
4. static修飾方法:(類方法、靜態方法)
> 隨著類的加載而加載
> 靜態方法,可以使用"類.靜態方法"的方式進行調用
同時,還可以使用"對象.靜態方法"的方式進行調用。--->從生命周期的角度解釋
> 判斷是否可以調用
類方法 實例方法
類 yes no
對象 yes yes
> 靜態方法中只能調用當前類中的靜態的變量、靜態的方法。(即不能調用非靜態的變量、非靜態的方法)
非靜態的方法中既可以調用當前類中非靜態的變量、非靜態的方法,也可以調用靜態的變量、靜態的方法。
> 靜態方法中不能使用this、super關鍵字。
5. 開發中,什么時候需要將屬性聲明為靜態的?
> 是否適合被類的多個對象所共享,同時多個對象對應的此變量值是相同的。
> 開發中,常常將一些常量聲明為靜態的。比如:Math的PI。
什么時候需要將方法聲明為靜態的?
> 操作靜態變量的方法,通常設置為靜態方法
> 開發中,工具類中的方法常常設置為static的。
2. 單例模式
1. 設計模式概述:
設計模式是在大量的`實踐中總結`和`理論化`之后優選的代碼結構、編程風格、以及解決問題的思考方式。
設計模式免去我們自己再思考和摸索。就像是經典的棋譜,不同的棋局,我們用不同的棋譜。
2. 何為單例模式(Singleton):在整個軟件系統中,針對于某個類來講,只存在該類的唯一的一個實例。則此類的設計
即為單例模式。
3. 如何實現單例模式(掌握):
餓漢式、懶漢式
4. 對比兩種模式(特點、優缺點)
特點:
餓漢式:隨著類的加載,當前類的實例就創建成功。
懶漢式:只有在首次調用get()方法時,才會創建單例對應類的實例。
餓漢式:(缺點)類一加載對象就創建成功,占用內存時間較長。(優點)線程安全的。
懶漢式:(優點)延遲了對象的創建,節省內存空間。(缺點)線程不安全的。 --->后續多線程章節中,將此方式改為線程安全的。
【面試題】 寫一個(線程安全的)單例模式。
3. main()的理解(了解)
1. 理解1:程序的入口。
理解2:看做是一個普通的有形參的靜態方法。
2. 與控制臺交互(了解即可)
方式1:使用Scanner類及其內部的nextXxx()
方式2:使用main(),將從控制臺獲取的數據存儲在其形參String[] args中。
4. 類的內部成員之四:代碼塊
4.1 代碼塊的基本使用
1. 代碼塊(或初始化塊)的作用:用來對類或對象進行初始化操作的。
2. 代碼塊的修飾:只能使用static修飾。
3. 代碼塊的分類:靜態代碼塊、非靜態代碼塊
4. 具體使用:
4.1 靜態代碼塊:
> 隨著類的加載而執行。主要用來初始化類。
> 因為類只加載一次,進而靜態代碼塊也只會執行一次
> 內部可以有輸出語句、聲明變量等操作
> 內部可以調用當前類中靜態的結構(屬性、方法),不能調用非靜態的結構
> 如果一個類中聲明了多個靜態代碼塊,按照聲明的順序先后執行
> 靜態代碼塊的執行要先于非靜態代碼塊的執行
4.2 非靜態代碼塊:
> 隨著對象的創建而執行。主要用來初始化對象。
> 每創建一個對象,非靜態代碼塊就執行一次。
> 內部可以有輸出語句、聲明變量等操作
> 內部可以調用當前類中靜態的結構(屬性、方法),能調用非靜態的結構
> 如果一個類中聲明了多個非靜態代碼塊,按照聲明的順序先后執行
4.2 屬性賦值位置、過程
1. 可以給類的非靜態的屬性(即實例變量)賦值的位置有:
① 默認初始化
② 顯式初始化 / ③ 代碼塊中初始化
④ 構造器中初始化
***************************
⑤ 有了對象以后,通過"對象.屬性"或"對象.方法"的方式給屬性賦值
2. 執行的先后順序:
① - ②/③ - ④ - ⑤
3. (超綱)關于字節碼文件中的<init>\<clinit>的簡單說明:
<clinit> : 系統自動生成的,內部包含了針對于靜態屬性的顯式賦值、代碼塊中賦值操作。
如果類中的靜態屬性沒有顯式賦值、沒有靜態代碼塊,則不會自動生成<clinit>方法。
> 內部顯式賦值、代碼塊中賦值操作的執行先后順序取決于聲明的先后順序。
<init> : 系統自動生成的,內部包含了針對于非靜態屬性的顯式賦值、代碼塊中賦值、構造器中賦值操作。
> 一個字節碼文件中至少包含一個<init>。換句話說,一個字節碼文件中,包含幾個<init>方法
取決于類中聲明了幾個構造器。
> 內部顯式賦值、代碼塊中賦值操作的執行先后順序取決于聲明的先后順序;構造器中賦值操作是最后執行的。
5. 關鍵字:final
1. final的理解:最終的
2. final可以用來修飾的結構:類、方法、變量
3. 具體說明:
3.1 final修飾類:此類不能被繼承。
> 比如:String、StringBuffer、StringBuilder類都使用了final修飾。
3.2 final修飾方法:此方法不能被重寫。
> 比如:Object類中的getClass()
3.3 final修飾變量(重點關注):表示此變量一旦賦值就不可更改,即此變量理解為是一個常量。
> final修飾成員變量:此變量即為一個常量。
可以有哪些位置給常量賦值呢?① 顯式賦值 ② 代碼塊中賦值 ③ 構造器中賦值。
> final修飾局部變量:此變量即為一個常量。
此局部變量只能被賦值一次。針對于形參來講,使用final修飾以后,在調用此方法時給此常量形參賦值。
4. final與static搭配:用來修飾一個屬性,此屬性稱為:全局常量。
比如: Math類中的PI。
6. 關鍵字:abstract
1. abstract的概念:抽象的
2. abstract可以用來修飾:類、方法
3. 具體的使用:
abstract修飾類:抽象類
> 不能實例化!
> 抽象類中一定聲明有構造器,只是不能創建對象而已。---> 此時的構造器,用來給子類對象實例化時調用的。
> 抽象類中的方法可以是抽象方法,也可以是普通的非抽象方法。
abstract修飾方法:抽象方法
> 不包含方法體的方法,并且使用abstract修飾。
> 抽象類中可以沒有抽象方法,但是抽象方法所屬的類一定是抽象類。
> 子類繼承抽象父類以后,如果重寫了父類中的所有的抽象方法,此子類方可實例化。
如果子類沒有重寫父類中所有的抽象方法的話,則此子類必須也聲明為抽象類。
4. abstract不能使用的場景
4.1 abstract 不能修飾哪些結構?屬性、構造器、代碼塊等
4.2 abstract 不能與哪些關鍵字共用?
不能用abstract修飾私有方法、靜態方法、final的方法、final的類。
5. 注意:
抽象類在使用時,如果出現在方法的形參位置。則在調用方法時,一定要使用多態了。
7. 與類并列的結構:接口(interface)
1. 定義接口的關鍵字: interface
2. 接口的理解:
接口就是規范,定義的是一組規則,體現了現實世界中“如果你是/要...則必須能...”的思想。
繼承是一個"是不是"的is-a關系,而接口實現則是 "能不能"的`has-a`關系。
3. 接口內部結構的說明:
> 可以聲明:
jdk8之前:只能聲明全局常量(public static final)、抽象方法(public abstract)
******************************************************
jdk8中:增加了靜態方法、默認方法(default)
jdk9中:增加了私有方法。
> 不可以聲明:構造器、代碼塊等結構。
4. 接口與類的關系 :實現關系(implements)
5. 滿足此關系之后,說明:
> 實現類實現相應的接口以后,就獲取了接口中聲明的全局常量和抽象方法。
> 如果實現類重寫了接口中聲明的所有的抽象方法,則此實現類可以實例化
如果實現類沒有重寫完接口中聲明的所有的抽象方法,則此實現類仍為一個抽象類。
> 一個類可以實現多個接口。--->一定程度上緩解了Java中類的單繼承性的局限性。
6. 格式:
class SubA extends SuperA implements A,B,C{}
7. 接口與接口的關系:繼承關系,而且是多繼承的。
interface A{
void method1();
}
interface B{
void method2();
}
interface C extends A,B{} //多繼承
8. 接口的多態性(重要)
9. 面試題:區分抽象類和接口
角度1:
共性:都不能實例化
不同點:抽象類:有構造器
接口:沒有構造器
角度2:抽象類中可以聲明抽象方法;接口中(jdk8之前)方法只能是抽象的。
角度3:類與類之間是繼承關系,是單繼承的;接口與接口之間是繼承關系,是多繼承的;類與接口之間是實現關系,是多實現的。
角度4:jdk8及之后的新特性:接口中可以聲明靜態方法、默認方法,包含方法體。
jdk9:新增私有方法。
8. 類的內部成員之五:內部類
> 內部類的分類(參照變量的分類)
> 如何創建成員內部類的對象
> 從兩個角度來認識成員內部類(作為類、作為外部類的成員)
> 內部類如何調用外部類的成員(屬性、方法)
> 在出現同名的屬性、方法時,使用"外部類.this.結構"的方式顯式調用父類的結構。
> 談談局部內部類開發中的使用場景
9. 枚舉類
> 枚舉類的特點:一個類中的對象個數是有限的、可數個的。
> (了解)jdk5.0之前,枚舉類的定義方式。
> jdk5.0中新增了enum的方式定義枚舉類。 ----需要掌握
> 自定義的枚舉類的父類:Enum類。此類中聲明的常用方法。
> values() \ valueOf(String objName) \ toString() ; name() \ ordinal()
> 枚舉類實現接口。
10. 注解
> 注解的作用:與注釋的區別。注解的作用
> Java基礎中三個常見的注解
> 如何自定義注解
> 元注解:對現有的注解進行修飾作用的注解。
> 體會:框架的理解:框架 = 注解 + 反射 + 設計模式
> 掌握如何使用:單元測試方法。
11. 包裝類
> 理解:為什么需要包裝類?
> add(Object obj) / equals(Object obj)
> 基本數據類型以及對應的包裝類
> 重點:基本數據類型、包裝類、String三者之間的轉換
> 基本數據類型 < --- > 包裝類:自動裝箱、自動拆箱
> 基本數據類型、包裝類 ---> String: 調用String的valueOf(); +
> String ---> 基本數據類型、包裝類:調用包裝類的parseXxx(String str)
12.IDEA的使用
- IDEA常用的快捷鍵
- IDEA的debug功能
第09章:異常處理
1. 異常的概述、理解
1. 什么是異常?
指的是程序在執行過程中,出現的非正常情況,如果不處理最終會導致JVM的非正常停止。
2. 異常的拋出機制 ---> 萬事萬物皆對象
Java中把不同的異常用不同的類表示,一旦發生某種異常,就`創建該異常類型的對象`,并且拋出(throw)。
然后程序員可以捕獲(catch)到這個異常對象,并處理;如果沒有捕獲(catch)這個異常對象,那么這個異常
對象將會導致程序終止。
3. 如何對待異常
> 態度1:一是遇到錯誤,不進行任何的處理,終止程序的運行。
> 態度2:如果之前的測試中出現了異常的情況,則修改代碼,保證之后盡量不要出現同樣的異常。
> 態度3:在編寫程序時,就充分考慮到各種可能發生的異常和錯誤,極力預防和避免。實在無法避免的,
要編寫相應的代碼進行異常的檢測、以及`異常的處理`
2. 常見的異常(重點)
java.lang.Throwable
|---java.lang.Error:錯誤
> Java虛擬機無法解決的嚴重問題。如:JVM系統內部錯誤、資源耗盡等嚴重情況。
> 一般不編寫針對性的代碼進行處理。
> 常見的Error:StackOverFlowError,OutOfMemoryError
|---java.lang.Exception:異常
> 其它因編程錯誤或偶然的外在因素導致的一般性問題,需要使用針對性的代碼進行處理,
使程序繼續運行。
> 分類:編譯時異常 、 運行時異常
> 舉例:
運行時異常:
NullPointerException
ArrayIndexOutOfBoundsException
ClassCastException
NumberFormatException
ArithmeticException
InputMismatchException
編譯時異常:
ClassNotFoundException
FileNotFoundException
IOException
3. 異常的處理(重點)
3.1 try-catch-finally
- try-catch的使用
1. 方式一(抓拋模型):
過程1:“拋”:Java程序的執行過程中如果出現異常,會生成一個對應異常類的對象,并將此對象拋出。
過程2:“抓”:針對于上一個過程中拋出的異常類的對象,進行的捕獲(catch)行為。
2. 基本結構:
try{
//可能出現異常的代碼
}catch(異常類型1 e){
//異常的處理方式
}catch(異常類型2 e){
//異常的處理方式
}
...
finally{
//一定會被執行的代碼
}
3. 使用細節:
> finally是可選的。暫時先不考慮
> try中包裹的是可能出現異常的代碼。如果在執行過程中,沒有出現異常,則程序正常結束,不會考慮執行多個catch結構
如果try中出現了異常,則會自動創建對應異常類的對象,并將此對象拋出。
如果拋出的異常對象匹配某個具體的catch結構,則進入相應的catch中進行處理。一旦執行結果,就跳出當前結構,繼續執行其后的代碼
如果沒有匹配到相應的catch結構,則相當于沒有捕獲異常,會導致程序的終止。
> 如果多個catch中的異常類型有子父類關系,則必須將子類異常的捕獲聲明在父類異常捕獲的上面。
> try中聲明的變量,在出了try的一對{}之后,就失效了。
> catch中異常處理的方式:
方式1:自己自定義輸出語句
方式2:調用異常類的現有方法:方法1:調用printStackTrace(),用于打印異常出現的堆棧信息。(推薦)
方法2:調用getMessage(),返回一個異常的字符串信息
4. 開發體會:
> 對于運行時異常:實際開發中,我們通常都不再處理運行時異常。
> 對于編譯時異常:實際開發中,我們是必須要提前給出異常方案。否則,編譯不通過。
- finally的使用
1. finally的理解
> 將一定會被執行的代碼聲明在finally中
> finally結構是可選的。
> 不管try、catch中是否存在未被處理的異常,不管try、catch是否執行了return語句;finally是一定要被執行的結構。
2. 什么樣的代碼我們一定要聲明在finally中呢?
> 開發中會涉及到相關的資源(流、數據庫連接)的關閉的問題,如果相關的資源沒有及時關閉,會出現內存泄漏。
為了避免出現內存泄漏,我們必須將其關閉操作聲明在finally中,確保在出現異常的情況下,
此關閉操作也一定會被執行。
3. 面試題
final 、 finally 、finalize 的區別
3.2 throws
1. 格式:
在方法的聲明處,使用"throws 異常類型1,異常類型2,..."
2. 舉例:
public void method1() throws FileNotFoundException, IOException{}
3. 是否真正處理了異常?
> 從是否能通過編譯的角度來說:使用throws的方法聲明了可能出現的異常的類型,使得編譯能通過。
> 從是否真正意義上解決了可能拋出的異常對象:并沒有。只是將可能出現的異常對象繼續向上拋出。
只有使用try-catch-finally的方式才是真正意義上處理了異常。
4. 方法的重寫的要求:
針對于編譯時異常:
子類重寫父類中的方法,要求子類重寫的方法拋出的異常類型不大于父類被重寫方法拋出的異常類型。
比如:父類被重寫的方法throws 異常類型1,子類可以throws異常類型1或異常類型1的子類。
針對于運行時異常:沒有這樣的要求。 ----> 開發中,針對于運行時異常,也不會進行try-catch或throws的處理。
- 如何選擇兩種處理方式?
開發中,如何選擇異常處理的兩種方式?(重要、經驗之談)
> 情況1:如果程序中使用了相關的資源,為了確保這些資源在出現異常的情況仍然能被執行關閉操作。
建議使用:try-catch-finally。將資源的關閉操作聲明在finally中。
> 情況2:如果父類中被重寫的方法沒有使用throws的結構,則子類重寫父類的方法中如果出現編譯時異常,只能
使用try-catch-finally的方式進行處理。
> 情況3:如果在方法1中依次調用了方法2,3,4,而且此時的方法2,3,4是遞進調用的關系。則通常方法2,3,4中
出現異常的情況下,選擇使用throws的方式進行異常的處理,在方法1中使用try-catch-finally進行處理。
4. 手動拋出異常的對象(熟悉)
1. 為什么需要手動拋出異常?
在實際開發中,為了滿足實際問題的需要,必要時需要手動的throw一個異常類的對象。
比如:要求分子、分母都不能為負數。如果出現負數了就報錯。如何體現報錯呢?手動拋出異常類的對象。
比如:給學生的id賦值,要求此id不能為負數。如果賦值為負數,就報錯。如何體現報錯呢?手動拋出異常類的對象。
2. 如何理解"自動 vs 手動"拋出異常對象?
過程1:“拋”:Java程序的執行過程中如果出現異常,會生成一個對應異常類的對象,并將此對象拋出。
情況1:自動拋出(throw)
情況2:手動拋出(throw):在方法體內使用
過程2:“抓”:針對于上一個過程中拋出的異常類的對象,進行的捕獲(catch)行為。
廣義上"抓"理解為異常處理的方式:
> 方式1:try-catch-finally
> 方式2:throws:使用在方法的聲明處
類比:上游排污、下游治污。
3. 如何實現手動拋出異常?
在方法體的內部,滿足某個條件的情況下,使用“throw + 異常類的對象”。
4. 注意點:throw后的代碼不能被執行,編譯不通過。
[面試題] 區分throw和throws
5. 如何自定義異常類(熟悉)
1. 如何自定義異常類? (參照著Exception、RuntimeException進行設計即可)
① 繼承于現有的異常體系結構中的某一個類。比如繼承于RuntimeException、Exception
② 提供幾個重載的構造器
③ 提供一個全局常量serialVersionUID,用于唯一的標識當前類
2. 如何使用自定義異常類?
在滿足相應情況的條件下,方法體內使用"throw + 自定義異常類的對象"的方式使用。
3. 為什么需要自定義異常類?
我們在開發中,針對于出現的異常,比較關心的是異常的名稱。通過異常名,就可以直接定位出現的異常的問題。
所以,我們在開發中,具體到項目的具體要求時,我們都可以拋出自己定義的異常類型的對象。
五個關鍵字:
try-catch-finally
throws
throw
第10章:多線程
1. 相關概念
- 掌握:程序、進程、線程
- 熟悉:線程的調度機制:分時調度、搶占式調度
- 了解:單核CPU、多核CPU
- 了解:并行與并發
2. 創建多線程的兩種經典方式(重點)
1. 線程的創建方式一:
1.1 步驟:
① 創建一個繼承于Thread類的子類
② 重寫Thread類的run()方法:將此線程要執行的操作編寫在此方法體中。
③ 創建Thread類的子類的對象
④ 調用start()方法: 1、啟動線程 2、調用線程的run()
1.2 例題:創建一個分線程1,用于遍歷100以內的偶數
【拓展】 再創建一個分線程2,用于遍歷100以內的偶數
2. 線程的創建方式二:
2.1 步驟:
① 創建實現Runnable接口的實現類
② 實現接口中的抽象方法run():將此線程要執行的操作編寫在此方法體中。
③ 創建此實現類的對象
④ 將此對象作為參數傳遞到Thread類的構造器中,創建Thread類的對象
⑤ 通過Thread類的對象調用start():1、啟動線程 2、調用線程的run()
2.2 例題:創建分線程遍歷100以內的偶數
3. 對比兩種方式?
共同點:① 創建的線程都是Thread類或其子類的對象
② 啟動線程,調用的都是Thread類中的start()
不同點:一種是繼承的方式,一種是實現的方式(推薦);
推薦實現的方式的原因: ① 類的單繼承的局限性 ②實現的方式更適合、方便的用來處理共享數據的場景。
聯系:
public class Thread implements Runnable
3. 線程的常用方法、生命周期
一、線程的常用結構
1. 線程中的構造器
- public Thread() :分配一個新的線程對象。
- public Thread(String name) :分配一個指定名字的新的線程對象。
- public Thread(Runnable target) :指定創建線程的目標對象,它實現了Runnable接口中的run方法
- public Thread(Runnable target,String name) :分配一個帶有指定目標新的線程對象并指定名字。
2.線程中的常用方法:
> run():在繼承的方式中,需要被重寫的方法。
> start():要想啟動一個線程,必須要調用此方法:①啟動線程 ② 調用線程的run()
> static currentThread():獲取當前執行的代碼所屬的線程。
> getName():獲取線程名
> setName(String name):設置線程名
> yield():一旦線程執行此方法,當前線程就釋放cpu的執行權
> join(): 在線程a中調用線程b的join()方法,此時線程a就進入阻塞狀態,直到線程b執行結束以后,線程a才可以從被阻塞的位置繼續執行
> static sleep(long millis):指定線程"睡眠"多少毫秒
> isAlive() : 判斷當前線程是否存活
過時方法:
> stop():強行結束一個線程的執行,直接進入死亡狀態。
> suspend() / resume() : 這兩個操作就好比播放器的暫停和恢復。二者必須成對出現,否則非常容易發生死鎖。
3. 線程的優先級:
3.1 線程的優先級的范圍:[1,10]
int MAX_PRIORITY = 10; //最大優先級
int MIN_PRIORITY = 1; //最小優先級
int NORM_PRIORITY = 5; //默認優先級
3.2 如何設置/獲取優先級:
setPriority(int priority):設置線程的優先級
getPriority() : 獲取線程的優先級
-
生命周期
- jdk5.0之前:
-
jdk5.0
4. 線程的安全問題與同步機制(重點)
線程的安全問題與線程的同步機制
1. 多線程賣票,出現的問題:出現了重票、錯票
2. 什么原因導致的?一個線程在沒有操作完ticket的情況下,其他線程參與進來,導致出現了重票、錯票
3. 如何解決?
應該包裝一個線程在操作完共享數據ticket的情況下,其它線程才能參與進來繼續操作ticket。
4. Java是如何解決線程的安全問題的? 同步機制
方式1:同步代碼塊
synchronized(同步監視器){
//需要被同步的代碼
}
說明:
> 需要被同步的代碼,即為操作共享數據的代碼。
> 什么是共享數據:即為多個線程共同操作的數據。比如:ticket
> 使用synchronized將操作共享數據的代碼包起來,確保這部分代碼作為一個整體出現。只有當一個線程操作完此部分代碼
之后,其他線程才有機會操作同樣的這部分代碼。
> 同步監視器,俗稱鎖。哪個線程獲取了同步監視器,這個線程就能執行操作共享數據的代碼。沒有獲取同步監視器的線程就只能等待。
注意:
> 操作共享數據的代碼,不能包多了,也不能包少了。
> 同步監視器:任何一個類的對象,都可以充當同步監視器。但是,多個線程必須共用同一個同步監視器。
> 實現Runnable的方式中,使用的同步監視器可以考慮this。
繼承Thread類的方式中,使用的同步監視器慎重this,可以考慮使用當前類。
方式2:同步方法
如果操作共享數據的代碼完整的聲明在一個方法中。我們也可以考慮將此方法聲明為同步方法。
說明:
> 非靜態的同步方法,其默認的同步監視器是:this
> 靜態的同步方法,其默認的同步監視器是:當前類本身
5. synchronized好處:解決了線程的安全問題
弊端:串行的執行,是得多線程的性能受限
5. 同步機制的相關問題
5.1 解決懶漢式的線程安全問題(重點)
package com.atguigu04.threadsafemore.singleton;
/**
* ClassName: BankTest
* Description:
*
* @Author 尚硅谷-宋紅康
* @Create 2023/2/24 11:50
*/
public class BankTest {
static Bank b1 = null;
static Bank b2 = null;
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
b1 = Bank.getInstance();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
b2 = Bank.getInstance();
}
});
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(b1);
System.out.println(b2);
System.out.println(b1 == b2);
}
}
//懶漢式
class Bank{
private Bank(){}
private static Bank bank = null;
//方式1:使用同步方法
// public static synchronized Bank getInstance(){
//
// if(bank == null){
//
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//
// bank = new Bank();
// }
// return bank;
//
// }
//方式2:使用同步代碼塊
public static Bank getInstance(){
synchronized (Bank.class) {
if(bank == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bank = new Bank();
}
return bank;
}
}
//思考:使用同步代碼塊,存在指令重排
// public static Bank getInstance(){
//
// if(bank == null){
//
// synchronized (Bank.class) {
// if(bank == null){
//
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//
// bank = new Bank();
// }
//
// }
// }
//
// return bank;
// }
}
5.2 死鎖問題
線程的同步機制帶來的問題:死鎖
1. 如何看待死鎖?
> 不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖。
> 一旦出現死鎖,整個程序既不會發生異常,也不會給出任何提示,只是所有線程處于阻塞狀態,無法繼續。
> 我們編程中,要避免出現死鎖
2. 誘發死鎖的原因?
- 互斥條件
- 占用且等待
- 不可搶奪(或不可搶占)
- 循環等待
以上4個條件,同時出現就會觸發死鎖。
3. 如何避免死鎖?
死鎖一旦出現,基本很難人為干預,只能盡量規避。可以考慮打破上面的誘發條件。
針對條件1:互斥條件基本上無法被破壞。因為線程需要通過互斥解決安全問題。
針對條件2:可以考慮一次性申請所有所需的資源,這樣就不存在等待的問題。
針對條件3:占用部分資源的線程在進一步申請其他資源時,如果申請不到,就主動釋放掉已經占用的資源。
針對條件4:可以將資源改為線性順序。申請資源時,先申請序號較小的,這樣避免循環等待問題。
5.3 JDK5.0新增解決安全問題的方式:Lock
除了使用synchronized同步機制處理線程安全問題之外,還可以使用jdk5.0提供的Lock鎖的方式
1. 步驟:
步驟1. 創建ReentrantLock的實例,必須保證多個線程共用一個。
步驟2. 調用lock(),鎖住共享數據的代碼
步驟3. 調用unlock(),解鎖共享數據的代碼
2. 面試題:
synchronized同步的方式 與Lock的對比 ?
> synchronized同步機制,利用同步監視器,確保同步監視器的唯一性。
> 同步代碼塊、同步方法對應的一對{}中的代碼是需要被同步的,只能有一個線程執行。
> Lock,確保Lock的實例的唯一性
> 在lock()和unlock()方法之間的操作,確保只有一個線程在執行。
官方文檔:
Lock implementations provide more extensive locking operations
than can be obtained using synchronized methods and statements.
6. 線程的通信
1. 線程間的通信
為什么需要線程間的通信?
當我們`需要多個線程`來共同完成一件任務,并且我們希望他們`有規律的執行`,那么多線程之間需要一些通信機制,
可以協調它們的工作,以此實現多線程共同操作一份數據。
2. 涉及到三個方法的使用:
wait(): 一旦執行此方法,對應的線程就進入阻塞狀態,并釋放同步監視器的調用
notify():喚醒被wait的線程中優先級最高的那一個。如果被wait的多個線程優先級相同,則會隨機喚醒其中被wait的線程。
notifyAll():喚醒所有被wait的線程。
3. 注意點:
> 此三個方法的調用者必須是同步監視器
> 此三個方法聲明在java.lang.Object類中
> 此三個方法的使用,必須在同步方法或同步代碼塊中。
---> 在Lock方式解決線程安全問題的前提下,不能使用此三個方法。在Lock的情況下,使用Condition實現通信。
4. 案例:
案例1:使用兩個線程打印 1-100。線程1, 線程2 交替打印
案例2:生產者&消費者
生產者(Productor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品,店員一次只能持有
固定數量的產品(比如:20),如果生產者試圖生產更多的產品,店員會叫生產者停一下,如果店中有空位放產品
了再通知生產者繼續生產;如果店中沒有產品了,店員會告訴消費者等一下,如果店中有產品了再通知消費者來
取走產品。
5. 【面試題】wait() 和 sleep()的區別?
> 所屬的類:wait()存在于Object類的非靜態方法;sleep()存在于Thread類的靜態方法
> 使用環境:wait() 必須使用在同步代碼塊或同步方法中;sleep():在調用時沒有任何限制
> 都使用在同步代碼塊或同步方法情況下,區別:wait()一旦調用會釋放同步監視器;sleep()不會釋放同步監視器
> 相同點:二者一旦執行都可以使得當前線程進入阻塞狀態
> 但是結束阻塞的方式不同:wait()的線程需要被notify()/notifyAll();sleep()的線程在指定時間結束后就結束阻塞。
7. JDK5.0新增兩種創建多線程的方式
- 使用Callable接口
- 使用線程池
1. 創建多線程的方式三:實現Callable (jdk5.0新增的)
與之前的方式的對比:對比Runnable
> call()方法有返回值類型,比run()靈活
> call()聲明有throws結構,內部有異常的話,不必非要使用try-catch
> Callable接口使用了泛型,call()的返回值類型更加靈活。(超綱)
2. 創建多線程的方式四:使用線程池
此方式的好處:
> 提高了程序執行的效率
> 提高了資源的重用率
> 設置相關的參數,實現對線程的管理
聯想:與后續講的數據庫連接池的好處是相同的。
第11章:常用類與基礎API
1、String
- String的聲明:final 、實現Comparable接口等
- String內部的屬性:final char[] value (jdk8中);final byte[] value(jdk9中)
- String的聲明方式1:使用字面量的方式。需要使用字符串常量池。
- String的不可變性
- String的聲明方式2:new
- String的特殊運算: +
- 常量 + 常量;變量+ 常量 ; 變量+變量;concat();intern()
- String的構造器、常用方法
2、與String相關的類:StringBuffer、StringBuilder
- String、StringBuffer、StringBuilder三個的異同。
- StringBuffer、StringBuilder的常用方法:增、刪、改、查、插、長度、反轉
- 三者添加數據方面的執行效率:StringBuilder > StringBuffer > String
3、比較器
開發中只要涉及到對象比較大小,都跟比較器打交道。
3.1 自然排序:Comparable
實現步驟:
1. 待排序的對象所說的類要實現Comaprable接口
2. 實現接口中的抽象方法:compareTo(Object obj),指明比較大小的規則
3. 創建待排序的多個對象,在相關的邏輯下進行排序操作即可。比如:Arrays.sort(Object[] objs)
3.2 定制排序:Comparator
實現步驟:
1. 創建一個實現了Comparator接口的類SubComparator
2. 此SubComparator類重寫compare(Object o1,Object o2)
此方法中指明要比較類A的實例對象o1,o2的大小。
3. 創建多個類A的對象,在相關的邏輯下進行排序操作即可。比如:Arrays.sort(A[] objs,SubComparator的對象)
對比兩種方式:
Comparable:在聲明好類的同時,就指明了默認的排序的方式。
一旦聲明,一勞永逸
Comparator: 比較靈活,可以在需要的具體場景下,靈活的指定排序的方式。
每次在需要的時候,都得現創建一個Comparator實現類的對象。
4、日期時間相關的API
4.1 jdk8之前相關API
- System的currentTimeMillis()
- java.util.Date 和 java.sql.Date
- getTime() \ toString()
- SimpleDateFormat:用來格式化、解析日期
- 格式化:String format(Date date)
- 解析:Date parse(String str)
- Calendar:日歷類,抽象類
- 實例化:getInstance()
- 方法:get(int field) 、set(int field,...)、add(...),Date getTime() 、 setTime(Date date)
4.2 jdk8新增的API
- LocalDate、LocalTime、LocalDateTime --> 類似于Calendar
- Instant : 瞬時 ---> 類似于Date
- DateTimeFormatter :類似于SimpleDateFormat。針對LocalDate、LocalTime、LocalDateTime的格式化或解析操作
5、其他api的使用
1. System類
2. Runtime類:單例設計模式,對應著一個java進程中運行時內存環境。
3. Math類:跟數學操作相關的api
4. BigInteger類和BigDecimal類
BigInteger類:如果在程序中,需要使用的整型數據超出了long的范圍(最大值為2^63 -1 ),則可以使用BigInteger替換,
可以表示任意精度范圍的整數。
BigDecimal類:如果在程序中,想表示任意精度的浮點型值,則使用BigDecimal類替換double的使用。
5. Random類
第12章:集合框架
1、集合與數組的對比
- 數組的特點、弊端
數組存儲多個數據方面的特點:
> 數組中存儲的多個元素是有序的、可以重復的的數據,緊密排列的
> 數組在內存中使用一整塊連續的內存空間進行存儲
> 數組一旦初始化,其長度就確定了。
> 數組一旦聲明,其元素的類型就確定了。不能添加非此類型的元素。
Person[] arr = new Person[4];
Object[] arr1 = new Object[5];
數組存儲多個數據方面的弊端:
> 數組一旦初始化,其長度就不可變。
> 數組可用的方法基本沒有。涉及到的增刪改查操作都需要自己編寫代碼
> 對于無序的、不可以重復的多個數據,就不適合使用數組存儲
- 集合框架結構
Java集合框架體系(java.util包下)
java.util.Collection接口:存儲一個一個的數據
|----java.util.List子接口:有序的、可以重復的數據 ("動態"數組)
|--- ArrayList(主要實現類) \ LinkedList \ Vector
|----java.util.Set子接口:無序的、不可以重復的數據 (高中講的集合)
|--- HashSet(主要實現類) \ LinkedHashSet \ TreeSet
java.util.Map接口:存儲一對一對(key-value)的數據 (高中講的映射、函數 (x1,y1)、(x2,y2) )
|---- HashMap(主要實現類) \ LinkedHashMap \ TreeMap \ Hashtable \ Properties
2、Collection中的常用方法
1. 常用方法:(Collection中定義了15個抽象方法。這些方法需要大家熟悉!)
2. 集合與數組的相互轉換:
Collection集合 ----> 數組: toArray()
數組 ----> Collection集合的子接口:List : 調用Arrays的靜態方法asList(Object ... objs)
3. 向Collection中添加元素的要求:
添加的元素所在的類要重寫equals(Object obj)
原因:因為Collection中的一些方法在調用時,要使用到元素所在類的equals()。
比如:constais(Object obj) / remove(Object obj) ..
學習的程度把握:
> 第1層次:針對要存儲的多個數據的特點,選擇相關的接口的主要實現類,完成對象的創建、相關方法的調用
> 第2層次:需要熟悉接口的不同的實現類之間的區別,進而熟悉不同的場景下應該選擇哪個實現類。
> 第3層次:熟悉不同的實現類的底層源碼實現。--->間接考查數據結構。放到14章中講。
比如:HashMap、ArrayList/LinkedList/Vector、LinkedHashMap、HashSet
3、迭代器Iterator
1. 迭代器(Iterator)的作用?
用于遍歷Collection集合元素。
2. 如何獲取迭代器(Iterator)對象?
使用集合的iterator(),返回一個迭代器的對象
3. 如何實現遍歷(代碼實現)
while(iterator.hasNext()){
//next():①指針下移 ② 將下移以后集合位置上的元素返回
Object obj = iterator.next();
System.out.println(obj);
}
4. 增強for循環(foreach循環)的使用(jdk5.0新特性)
4.1 作用
用于遍歷集合元素、遍歷數組元素
4.2 格式:
增強for循環格式:for(集合元素的類型 臨時變量 : 要遍歷的集合)
4.3 說明:
我們不要使用增強for循環來修改集合或數組中的元素。因為此操作常常失敗。
4、Collection的子接口1:List接口
List及其實現類特點
java.util.Collection接口:存儲一個一個的數據
|----java.util.List子接口:有序的、可以重復的數據 ("動態"數組)
|--- ArrayList:主要實現類;線程不安全的,效率高;底層使用Object[]存儲
對于頻繁的查找、尾部添加,性能較高,時間復雜度O(1)
|--- LinkedList:使用雙向鏈表存儲數據;
對于頻繁的刪除、插入操作,性能較高,時間復雜度為O(1)
|--- Vector:古老的實現類;線程安全的,效率低;底層使用Object[]存儲
小結:
增:add(Object obj) / addAll(Collection coll)
刪:remove(Object obj) / remove(int index)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele) / addAll(int index, Collection eles)
長度:size()
遍歷:① 迭代器 ② 增強for ③ 一般的for
5、Collection的子接口2:Set接口
- Set的實現類的特點
1. Set及其實現類特點
java.util.Collection接口:存儲一個一個的數據
|----java.util.Set子接口:無序的、不可以重復的數據 (高中講的集合)
|--- HashSet:主要實現類;底層使用數組+鏈表+紅黑樹結構進行存儲(jdk8.0)
|--- LinkedHashSet:是HashSet的子類;在底層使用Haset結構進行存儲之外,又增加了一對
雙向鏈表,用于記錄添加元素的先后順序。對應頻繁的遍歷操作,性能較高。
|--- TreeSet:底層使用紅黑樹進行存儲。可以按照添加的元素的指定屬性的大小順序進行遍歷。
2. 開發中的使用頻率及場景:
> 使用頻率較低;
> 使用場景:用于過濾重復數據
- Set中的常用方法
Set中常用方法
就是Collection中定義的15個方法。
- Set的實現類:HashSet
1. Set中無序性、不可重復性的理解(以HashSet及其子類為例說明)
> 無序性:!= 隨機性, != 添加的順序和遍歷的順序不一致。
> 不可重復性:哈希算法。
以兩個元素的比較來說,
如果兩個元素根據hashCode()方法計算得到的哈希值相同,且equals()判斷時也返回true,則認為兩個元素是相同的。
如果兩個元素根據hashCode()方法計算得到的哈希值不同,或者哈希值相同,但equals()判斷時返回false,則認為兩個元素是不同的。
2. 添加到HashSet/LinkedHashSet中元素的要求:
元素所在的類要重寫兩個方法:equals() 、 hashCode()。 使用IDEA自動生成即可。
重寫時,要盡量保證equals() 、 hashCode()的一致性。
- Set的實現類:TreeSet
1. 底層的數據結構:紅黑樹
2. 添加數據后的特點:可以按照添加的元素的指定的屬性的大小順序進行遍歷
3. 向TreeSet中添加的元素的要求:
> 添加的多個元素,必須是同一個類的對象,即不能是不同類的對象。
4. 判斷數據是否相同的標準
> 不再是equals() 和 hashCode()了。
> 應該是:
自然排序,實現了Comparable接口,是否相同的標準在于compareTo()是否返回0
定制排序,實現了Comparator接口,是否相同的標準在于compare()是否返回0
6、Map接口的使用
- Map的實現類的對比
java.util.Map接口:存儲一對一對(key-value)的數據 (高中講的映射、函數 (x1,y1)、(x2,y2) )
|---- HashMap:主要實現類;線程不安全的,效率高;jdk7:數組+單向鏈表,jdk8:數組+單向鏈表+紅黑樹
可以添加null的key或value
|---- LinkedHashMap:繼承于HashMap;在底層使用HashMap數據結構的基礎上,又增加了一對雙向鏈表,用于
記錄添加元素的先后順序。當遍歷此集合時,就可以按照添加的順序實現遍歷。
對于頻繁的遍歷操作,建議使用此類。
|---- TreeMap:底層使用紅黑樹結構存儲;可以按照添加的key-value對的key的指定的屬性的大小進行排序。
進而遍歷時,也是按照key的指定的屬性的大小順序進行遍歷的。
|---- Hashtable:古老的實現類;線程安全的,效率低;數組+單向鏈表
不可以添加null的key或value
|---- Properties:繼承于Hashtable。key、value都是String類型,常用來處理屬性文件。
[面試題]
HashMap的底層源碼實現
HashMap和Hashtable的區別
HashMap、LinkedHashMap的區別
- HashMap的存儲數據的特點
HashMap中元素的特點
> HashMap中的所有的key彼此之間不相同,且無序。多個key構成一個Set。--->key所在的類要重寫equals()、hashCode()
> HashMap中的所有的value彼此之間可以相同,且無序。多個value構成一個Collection。--> value所在的類要重寫equals()
> HashMap中的一個key-value構成一個Entry。
> HashMap中的所有的entry彼此之間不相同,且無序。多個entry構成一個Set。
- Map中的常用方法
Map中的常用方法
- 添加、修改操作:
- Object put(Object key,Object value):將指定key-value添加到(或修改)當前map對象中
- void putAll(Map m):將m中的所有key-value對存放到當前map中
- 刪除操作:
- Object remove(Object key):移除指定key的key-value對,并返回value
- void clear():清空當前map中的所有數據
- 元素查詢的操作:
- Object get(Object key):獲取指定key對應的value
- boolean containsKey(Object key):是否包含指定的key
- boolean containsValue(Object value):是否包含指定的value
- int size():返回map中key-value對的個數
- boolean isEmpty():判斷當前map是否為空
- boolean equals(Object obj):判斷當前map和參數對象obj是否相等
- 元視圖操作的方法:
- Set keySet():返回所有key構成的Set集合
- Collection values():返回所有value構成的Collection集合
- Set entrySet():返回所有key-value對構成的Set集合
小結:
增:put(Object key,Object value)
刪:remove(Object key)
改:put(Object key,Object value)
查:get(Object key)
長度:size()
遍歷:keySet() \ values() \ entrySet()
- Map的實現類:TreeMap
TreeMap的使用
> 可以按照添加的key-value對的key的指定的屬性的大小進行排序。
進而遍歷時,也是按照key的指定的屬性的大小順序進行遍歷的。
> 針對于key-value對中的key進行自然排序或定制排序即可。
- Map的實現類:Hashtable與Properties
Hashtable與Properties的使用
> Properties:繼承于Hashtable。key、value都是String類型,常用來處理屬性文件。
7、操作集合的工具類:Collections
1. Collections概述
Collections操作集合框架(Collection、Map)的工具類。
2. 常用方法
3. 面試題:區分Collection 和 Collections
Collection:集合框架中提供的一個用于存儲一個一個數據的頂級接口。下面提供了List和Set等子接口。
第13章:jdk5.0新特性:泛型
1、在集合中使用泛型前后的對比
1. 什么是泛型?
所謂泛型,就是允許在定義類、接口時通過一個`標識`表示類中某個`屬性的類型`或者是某個方法的`返回值或參數的類型`。
這個類型參數將在使用時(例如,繼承或實現這個接口、創建對象或調用方法時)確定(即傳入實際的類型參數,也稱為
類型實參)。
2. 在集合中使用泛型之前可能存在的問題
問題1:添加的數據類型不安全
問題2:繁瑣:必須要使用向下轉型。 還可能會報ClassCastException
@Test
public void test2(){
// List<Integer> list = new ArrayList<Integer>();
ArrayList<Integer> list = new ArrayList<>(); //jdk7.0新特性:類型推斷
//添加學生的成績
list.add(78);
list.add(87);
list.add(66);
list.add(99);
list.add(66);
//1.如下的操作,編譯不通過
// list.add("AA");
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
//2.不需要使用向下轉型
Integer score = iterator.next();
System.out.println(score);
}
}
@Test
public void test3(){
HashMap<String,Integer> map = new HashMap<>();
map.put("Tom",78);
map.put("Jerry",88);
map.put("Jack",55);
map.put("Rose",89);
// map.put(56,"Tony");//編譯不通過
Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String,Integer>> iterator = entrySet.iterator();
while(iterator.hasNext()){
Map.Entry<String,Integer> entry = iterator.next();
System.out.println(entry.getKey() + "--->" + entry.getValue());
}
}
2、在其它結構中使用泛型
- 比較器:Comparable
public class Employee implements Comparable<Employee>{
private String name;
private int age;
private MyDate Birthday;
//省略get、set、構造器、toString()
@Override
public int compareTo(Employee o) {
// if (this == o){
// return 0;
// }
return this.name.compareTo((o.name));
}
}
- 比較器:Comparator
//定制排序
@Test
public void test2() {
Employee e1 = new Employee("Tom", 23, new MyDate(1999, 12, 3));
Employee e2 = new Employee("Jerry", 33, new MyDate(1990, 2, 3));
Employee e3 = new Employee("Peter", 22, new MyDate(2000, 3, 5));
Employee e4 = new Employee("NiPing", 23, new MyDate(2000, 12, 5));
Employee e5 = new Employee("Fengyi", 20, new MyDate(2002, 9, 9));
Comparator<Employee> comparator = new Comparator<>() {
@Override
public int compare(Employee e1, Employee e2) {
if (e1 == e2) {
return 0;
}
int yearDistance = e1.getBirthday().getYear() - e2.getBirthday().getYear();
if (yearDistance != 0) {
return yearDistance;
}
int monthDistance = e1.getBirthday().getMonth() - e2.getBirthday().getMonth();
if (monthDistance != 0) {
return monthDistance;
}
return e1.getBirthday().getDay() - e2.getBirthday().getDay();
}
};
TreeSet<Employee> treeSet = new TreeSet<>(comparator);
treeSet.add(e1);
treeSet.add(e2);
treeSet.add(e3);
treeSet.add(e4);
treeSet.add(e5);
Iterator<Employee> iterator = treeSet.iterator();
while (iterator.hasNext()) {
Employee e = iterator.next();
System.out.println(e);
}
}
3、如何自定義泛型類、泛型接口、泛型方法
1. 自定義泛型類\接口
1.1 格式
class A<T>{}
interface B<T>{}
1.2 使用說明
> 聲明的泛型類,在實例化時可以不使用類的泛型。
> 聲明泛型類以后,可以在類的內部結構中,使用類的泛型參數。比如:屬性、方法、構造器
> 何時指明具體的類的泛型參數類型呢?① 類的實例化 ② 提供子類時
> 泛型參數類型只能是引用數據類型,不能使用基本數據類型。
> 一旦指定類的泛型參數的具體類型以后,則凡是使用類的泛型參數的位置,都確定為具體的泛型參數的類型。
如果實例化時未指定泛型參數的具體類型,則默認看做是Object類型。
> 泛型類中,使用泛型參數的屬性、方法是不能聲明為static的。
2. 自定義泛型方法
2.1 問題:在泛型類的方法中,使用了類的泛型參數。那么此方法是泛型方法嗎?
2.2 格式
權限修飾符 <T> 返回值類型 方法名(形參列表){}
2.3 舉例
public <E> ArrayList<E> copyFromArrayToList(E[] arr){
2.4 說明
> 泛型方法所在的類是否是一個泛型類,都可以。
> 泛型方法中的泛型參數通常是在調用此方法時,指明其具體的類型。
一旦指明了其泛型的類型,則方法內部凡是使用方法泛型參數的位置都指定為具體的泛型類型。
> 泛型方法可以根據需要聲明為static
4、泛型在繼承上的體現
1. 類A是類B的父類,則G<A> 與 G<B>的關系:沒有繼承上的關系。
比如:ArrayList<String>的實例就不能賦值給ArrayList<Object>
2. 類A是類B的父類或接口,A<G> 與 B<G>的關系:仍然滿足繼承或實現關系。意味著可以使用多態。
比如:將ArrayList<String>的類型的變量賦值給List<String>的變量,是可以的。
5、通配符、有條件限制的通配符的使用
1. 通配符: ?
2. 使用說明:
2.1 舉例:
List<?> list1 = null;
List<Object> list2 = new ArrayList<Object>();
List<String> list3 = null;
list1 = list2;
list1 = list3;
2.2 說明:可以將List<?>看做是List<Object> 、 List<String>結構共同的父類
3. 讀寫數據的特點
> 讀取:可以讀數據,但是讀取的數據的類型是Object類型。
> 寫入:不能向集合中添加數據。特例:null
4. 有限制條件的通配符
List<? extends A> : 可以將List<A> 或 List<SubA>賦值給List<? extends A>。其中SubA是A類的子類
List <? super A> : 可以將List<A> 或 List<SuperA>賦值給List<? super A>。其中SuperA是A類的父類
5. 有限制條件的統配符的讀寫操作(難、了解)
見代碼
第14章:數據結構與集合源碼
1、數據結構
- 數據結構的研究對象
1. 數據結構概念:
數據結構,就是一種程序設計優化的方法論,研究數據的`邏輯結構`和`物理結構`以及它們之間相互關系,
并對這種結構定義相應的`運算`,目的是加快程序的執行速度、減少內存占用的空間。
2. 數據結構的研究對象
研究對象1:數據間邏輯關系
> 集合結構
> 線性結構:一對一關系
> 樹形結構:一對多關系
> 圖形結構:多對多關系
研究對象2:數據的存儲結構
> 結構1:順序結構
> 結構2:鏈式結構
> 結構3:索引結構
> 結構4:散列結構
開發中更關注存儲結構:
> 線性表:數組、單向鏈表、雙向鏈表、棧、隊列等
> 樹:二叉樹、B+樹
> 圖:無序圖、有序圖
> 散列表:HashMap、HashSet
研究對象3:
- 分配資源,建立結構,釋放資源
- 插入和刪除
- 獲取和遍歷
- 修改和排序
- 常見的數據存儲結構
3. 常見存儲結構之:數組
4. 常見存儲結構之:鏈表
4.1 單向鏈表
class Node{
Object data;
Node next;
public Node(){}
public Node(Object data){
this.data = data;
}
public Node(Object data,Node next){
this.data = data;
this.next = next;
}
}
舉例:
Node node1 = new Node("AA");
Node node2 = new Node("BB");
node1.next = node2; //尾插法
node2.next = node1;//頭插法
或
Node node2 = new Node("BB",node1);
4.2 雙向鏈表
class Node{
Object data;
Node prev;
Node next;
public Node(Object data){
this.data = data;
}
public Node(Node prev,Object data,Node next){
this.prev = prev;
this.data = data;
this.next = next;
}
}
舉例:
Node node1 = new Node("AA");
Node node2 = new Node(node1,"BB",null);
Node node3 = new Node(node2,"CC",null);
node1.next = node2;
node2.next = node3;
5. 常見存儲結構之:二叉樹
class Node{
Object data;
Node left;
Node right;
public Node(Object data){
this.data = data;
}
public Node(Node left,Object data,Node right){
this.left = left;
this.data = data;
this.right = right;
}
}
舉例:
Node node1 = new Node("AA");
Node node2 = new Node("BB");
Node node3 = new Node("CC");
node1.left = node2;
node1.right = node3;
或者:
class Node{
Object data;
Node left;
Node right;
Node parent;
public Node(Object data){
this.data = data;
}
public Node(Node left,Object data,Node right){
this.left = left;
this.data = data;
this.right = right;
}
public Node(Node parent,Node left,Object data,Node right){
this.parent = parent;
this.left = left;
this.data = data;
this.right = right;
}
}
舉例:
Node node1 = new Node("AA");
Node node2 = new Node(node1,null,"BB",null);
Node node3 = new Node(node1,null,"CC",null);
node1.left = node2;
node1.right = node3;
6. 常見存儲結構之:棧 (先進后出、后進先出、FILO、LIFO)
(ADT:abstract data type,棧可以使用數組、鏈表生成)
class Stack{
Object[] values;
int size;//記錄添加的元素個數
public Stack(int capacity){
values = new Object[capacity];
}
//入棧
public void push(Object ele){
if(size >= values.length){
throw new RuntimeException("棧已滿,添加失敗");
}
values[size] = ele;
size++;
}
//出棧
public Object pop(){
if(size <= 0){
throw new RuntimeException("棧已空,彈出棧操作失敗");
}
Object returnValue = values[size - 1];
values[size - 1] = null;
size--;
return returnValue;
}
}
7. 常見存儲結構之:隊列(先進先出、FIFO)
(ADT:abstract data type,棧可以使用數組、鏈表生成)
class Queue{
Object[] values;
int size;//記錄添加的元素個數
public Queue(int capacity){
values = new Object[capacity];
}
//添加元素
public void add(Object ele){
if(size >= values.length){
throw new RuntimeException("隊列已滿,添加失敗");
}
values[size] = ele;
size++;
}
//獲取元素
public Object get(){
if(size <= 0){
throw new RuntimeException("隊列已空,獲取數據失敗");
}
Object returnValue = values[0];
for(int i = 0;i < size - 1;i++){
values[i] = values[i + 1];
}
values[size - 1] = null;
size--;
return returnValue;
}
}
2、集合源碼
- List的實現
一、ArrayList
1. ArrayList的特點:
主要實現類;線程不安全的,效率高;底層使用Object[]存儲
對于頻繁的查找、尾部添加,性能較高,時間復雜度O(1)
2. ArrayList源碼解析:
2.1 jdk7版本:(以jdk1.7.0_07為例)
ArrayList list = new ArrayList(); //底層創建了一個長度為10的Object[]:Object[] elementData = new Object[10];
list.add("AA");//elementData[0] = "AA";
...
當添加第11個元素時:由于容量不夠,需要擴容,默認擴容為原來的1.5倍。
2.2 jdk8版本:(以jdk1.8.0_271為例)
ArrayList list = new ArrayList(); //底層并沒有創建長度為10的Object[]數組,而是Object[] elementData = {};
list.add("AA");//當首次添加元素時,底層創建長度為10的數組,賦給elementData,同時elementData[0] = "AA";
...
當添加第11個元素時:由于容量不夠,需要擴容,默認擴容為原來的1.5倍。
類比:1.7類似于餓漢式;1.8類似于懶漢式。
二、Vector
1. Vector的特點:
古老的實現類;線程安全的,效率低;底層使用Object[]存儲
2. Vector源碼解析:(以jdk1.8.0_271為例)
Vector在jdk1.8中初始化時就創建了長度為10的Object[]數組。當添加元素到滿的時候,默認擴容為原來的2倍。
三、LinkedList
1. LinkedList的特點:
使用雙向鏈表存儲數據;
對于頻繁的刪除、插入操作,性能較高,時間復雜度為O(1)
2. LinkedList在jdk8中的源碼解析:
LinkedList list = new LinkedList(); //底層沒有做什么操作
list.add("AA");//內部創建一個Node對象a,LinkedList內部的屬性first、last都指向對象a
list.add("BB");//內部創建一個Node對象b,對象a的next指向對象b,對象b的prev指向對象a,last指向對象b。
內部聲明的Node如下:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
3. LinkedList是否存在擴容問題?沒有!
四、啟示與開發建議
> Vector基本不用,效率低,使用ArrayList替換
> 對于頻繁的刪除、插入操作,使用LinkedList替換ArrayList
> 除此之外,我們首推ArrayList。
> new ArrayList() / new ArrayList(int capacity) (推薦,避免出現不必要的多次擴容)
- HashMap的底層實現
一、HashMap
1. HashMap中元素的特點
> HashMap中的所有的key彼此之間不相同,且無序。多個key構成一個Set。--->key所在的類要重寫equals()、hashCode()
> HashMap中的所有的value彼此之間可以相同,且無序。多個value構成一個Collection。--> value所在的類要重寫equals()
> HashMap中的一個key-value構成一個Entry。
> HashMap中的所有的entry彼此之間不相同,且無序。多個entry構成一個Set。
2. HashMap源碼解析
2.1 jdk7中創建對象和添加數據過程(以JDK1.7.0_07為例說明):數組+單向鏈表
HashMap map = new HashMap();//底層創建了長度為16的數組:Entry[] table = new Entry[16];
map.put("AA",67);//添加過程如下。
將(key1,value1)添加到map中:
1)通過key1所在的hashCode(),計算得到key1的哈希值1,此哈希值1經過某種算法(hash())以后得到哈希值2。
此哈希值2經過某種算法(indexFor())以后,得到key1-value1在數組table中的存儲位置i。
2)判斷table[i] 是否為空。
如果為空,key1-value1添加成功。 --->添加成功1
如果不為空,假設已有元素(key0,value0),則需要繼續比較。 ----> 哈希沖突
3) 比較key1的哈希值2與key0的哈希值2是否相等。
如果兩個哈希值2不相等。則認為key1-value1與key0-value0不相同。key1-value1添加成功。 --->添加成功2
如果兩個哈希值2相同,則需要繼續比較。
4) 調用key1所在類的equals(),將key0放入equals()的形參中。看返回值。
如果返回值為false,則key1和key0不同,則key1-value1添加成功。 --->添加成功3
如果返回值為true,則認為key1和key0相同,則value1替換value0。理解為修改成功。
說明:
添加成功1:將key1-value1封裝在entry的對象中,將此對象放入數組的位置
添加成功2、3:key1-value1封裝在entry的對象1中,與key0-value0封裝的entry對象0構成單向鏈表的結構。
jdk7中是entry對象1指向entry對象0,entry對象1放在數組里。
...
不斷的添加,添加到什么情況時會擴容呢?一旦達到臨界值(且索引i的位置上恰好還有元素),就考慮擴容。默認擴容為原來的2倍。
(源碼為:if ((size >= threshold) && (null != table[bucketIndex])) 條件下擴容)。
2.2 jdk8與jdk7的不同之處(以jdk1.8.0_271為例):
1)HashMap map = new HashMap(); 底層并沒有創建長度為16的數組。
2)調用put(k,v)添加元素。如果是首次添加,則底層默認創建長度為16的table[]
3)jdk8中HashMap內部使用Node[]替換Entry[]。
4)如果要添加的key1-value1在經過一系列判斷后,確定能添加到索引i的位置。此時,采用尾插法。
即原有的此索引i位置上的鏈表的最后一個元素指向新要添加的key1-value1。 "七上八下"
5)如果索引i位置上的元素達到8了,且數組的長度達到64的情況下,索引i位置上的多個元素要改為使用紅黑樹存儲。
目的:為了提升查找的效率。(鏈表情況下查找的復雜度:O(n),紅黑樹的查找的復雜度:O(logN))
當索引i位置上的元素個數少于6個時,會將此索引i位置上的紅黑樹改為單向鏈表。
2.3 屬性字段:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默認的初始容量 16
static final float DEFAULT_LOAD_FACTOR = 0.75f; //默認加載因子
static final int TREEIFY_THRESHOLD = 8; //默認樹化閾值8,當鏈表的長度達到這個值后,要考慮樹化
static final int UNTREEIFY_THRESHOLD = 6;//默認反樹化閾值6,當樹中結點的個數達到此閾值后,要考慮變為鏈表
//當單個的鏈表的結點個數達到8,并且table的長度達到64,才會樹化。
//當單個的鏈表的結點個數達到8,但是table的長度未達到64,會先擴容
static final int MIN_TREEIFY_CAPACITY = 64; //最小樹化容量64
transient Node<K,V>[] table; //數組
transient int size; //記錄有效映射關系的對數,也是Entry對象的個數
int threshold; //閾值,當size達到閾值時,考慮擴容
final float loadFactor; //加載因子,影響擴容的頻率
二、LinkedHashMap
1. LinkedHashMap 與 HashMap 的關系:繼承關系。在HashMap的Node的基礎上,增加了一對雙向鏈表,記錄
添加的先后順序。
2. 底層結構:
重寫了如下的方法:
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
其中:
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
拓展:
HashSet的底層實現原理?
第15章:File類與IO流
1. File類的使用
1. File類的理解
> File聲明在java.io包下的。
> File類的對象可以表示一個文件或一個文件目錄。
> File類中包含了關于文件、文件目錄的新建、刪除、重命名、查詢所在路徑、獲取文件大小等方法。
但是不包含讀寫文件內部內容的方法。要想讀寫文件內容,我們需要使用IO流。
> File類的對象常作為IO流讀寫數據的端點出現:常作為IO流的構造器的形參出現。
2. 內部api使用說明
2.1 構造器
* `public File(String pathname) ` :以pathname為路徑創建File對象,可以是絕對路徑或者相對路徑,如果pathname是相對路徑,則默認的當前路徑在系統屬性user.dir中存儲。
* `public File(String parent, String child) ` :以parent為父路徑,child為子路徑創建File對象。
* `public File(File parent, String child)` :根據一個父File對象和子文件路徑創建File對象
2.2 方法
1、獲取文件和目錄基本信息
* public String getName() :獲取名稱
* public String getPath() :獲取路徑
* `public String getAbsolutePath()`:獲取絕對路徑
* public File getAbsoluteFile():獲取絕對路徑表示的文件
* `public String getParent()`:獲取上層文件目錄路徑。若無,返回null
* public long length() :獲取文件長度(即:字節數)。不能獲取目錄的長度。
* public long lastModified() :獲取最后一次的修改時間,毫秒值
2、列出目錄的下一級
* public String[] list() :返回一個String數組,表示該File目錄中的所有子文件或目錄。
* public File[] listFiles() :返回一個File數組,表示該File目錄中的所有的子文件或目錄。
3、File類的重命名功能
- public boolean renameTo(File dest):把文件重命名為指定的文件路徑。
4、判斷功能的方法
- `public boolean exists()` :此File表示的文件或目錄是否實際存在。
- `public boolean isDirectory()` :此File表示的是否為目錄。
- `public boolean isFile()` :此File表示的是否為文件。
- public boolean canRead() :判斷是否可讀
- public boolean canWrite() :判斷是否可寫
- public boolean isHidden() :判斷是否隱藏
5、創建、刪除功能
- `public boolean createNewFile()` :創建文件。若文件存在,則不創建,返回false。
- `public boolean mkdir()` :創建文件目錄。如果此文件目錄存在,就不創建了。如果此文件目錄的上層目錄不存在,也不創建。
- `public boolean mkdirs()` :創建文件目錄。如果上層文件目錄不存在,一并創建。
- `public boolean delete()` :刪除文件或者文件夾
刪除注意事項:① Java中的刪除不走回收站。② 要刪除一個文件目錄,請注意該文件目錄內不能包含文件或者文件目錄。
3. 概念:
絕對路徑:在windows操作系統中,以盤符開始的路徑。
相對路徑:相較于某個指定路徑下的具體路徑。
單元測試方法:相較于當前的module
main():相較于當前的工程
2. IO流的概述
- 流的分類
- 流的流向:輸入流、輸出流
- 操作的數據單位:字節流、字符流
- 角色的不同:節點流、處理流
- 4個抽象基類
- 整個流這一章涉及到的具體的流的使用,操作的步驟都是標準規范的。
- 步驟1:創建File的對象
- 步驟2:創建流的對象,構造器中需要傳入File的對象
- 步驟3:讀取、寫出操作的細節
- 步驟4:關閉資源
3. 文件流的使用
-
FileInputStream與FileOutputStream、FileReader與FileWriter
-
注意點1:
注意點:
> 因為涉及到資源的關閉,所有異常的處理需要使用try-catch-finally結構替換throws
> 對于輸入流來講,File對象對應的物理磁盤上的文件必須存在。否則,報FileNotFoundException
對于輸出流來講,File對象對應的物理磁盤上的文件可以不存在。
> 如果不存在,則在輸出的過程中,會自動創建指定名的文件
> 如果存在,如果使用的是FileWriter(File file)或FileWriter(File file,false)構造器,則在輸出的過程中,會覆蓋已有的文件
如果存在,如果使用的是FileWriter(File file,true)構造器,則在輸出的過程中,會在現有文件末尾追加內容。
> 務必記得關閉資源,否則出現內存泄漏
- 注意點2:
> FileReader \ FileWriter :主要用來處理文本文件
對于非文本文件的處理是失敗的。
> FileInputStream \ FileOutputStream:主要用來處理非文本文件。
> 文本文件:.txt、.java、.c、.cpp、.py
非文本文件:.doc、.jpg、.png、.avi、.mp3、.mp4、.ppt
4. 處理流之一:緩沖流
-
BufferedInputStream與BufferedOutputStream、BufferedReader與BufferedWriter
-
作用:加快文件的讀寫效率。
-
原理:內部提供了緩沖區(數組實現的),減少和磁盤交互的次數。
-
4個緩沖流 使用的方法 處理非文本文件的字節流: BufferedInputStream read(byte[] buffer) BufferedOutputStream write(byte[] buffer,0,len) \ flush() 處理文本文件的字符流: BuffferedReader read(char[] buffer)\readLine() BufferedWriter write(char[] buffer,0,len) \ flush() 3. 實現的步驟 第1步:創建File的對象、流的對象(包括文件流、緩沖流) 第2步:使用緩沖流實現 讀取數據 或 寫出數據的過程(重點) 讀取:int read(char[] cbuf/byte[] buffer) : 每次將數據讀入到cbuf/buffer數組中,并返回讀入到數組中的字符的個數 寫出:void write(String str)/write(char[] cbuf):將str或cbuf寫出到文件中 void write(byte[] buffer) 將byte[]寫出到文件中 第3步:關閉資源
5. 處理流之二:轉換流
-
InputStreamReader和OutputStreamWriter
-
基本使用
-
1. 復習 字符編碼:字符、字符串 ----> 字節、字節數組。對應著編碼集 字符解碼:字節、字節數組 ---> 字符、字符串。對應著解碼集 2. 如果希望程序在讀取文本文件時,不出現亂碼,需要注意什么? 使用的解碼集必須與當初保存文本文件使用的編碼集一致。
6. 處理流之三:對象流
-
了解:數據流:DataInputStream 、DataOutputStream
- 讀寫8種基本數據類型的變量、String、字節數組
-
掌握:ObjectInputStream、ObjectOutputStream
- 讀寫8種基本數據類型的變量、對象(readObject();writeObject(Object obj))
-
掌握:對象的序列化機制
-
對象序列化機制允許把內存中的Java對象轉換成平臺無關的二進制流,從而允許把這種二進制流持久地保存在磁盤上,或通過網絡將這種二進制流傳輸到另一個網絡節點。//當其它程序獲取了這種二進制流,就可以恢復成原來的Java對象。
-
序列化過程:將內存中的Java對象轉換為二進制流,保存在文件中或通過網絡傳輸出去。 使用ObjectOutputStream 反序列化過程:將文件中或者通過網絡接收到的二進制流轉換為內存中的Java對象 使用ObjectInputStream
-
-
熟悉:自定義類要想實現序列化機制,需要滿足:
> 必須實現接口Serializable。 (此接口中沒有抽象方法,稱為標識接口) > 類中必須顯式聲明一個全局常量serialVersionUID,用于唯一標識當前類本身。 如果不顯式聲明,系統會自動分配一個serialVersionUID,但是此屬性在類修改的情況下,可能被改變。不建議使用默認情況。 > 自定義類的所有屬性也必須是可以序列化的。滿足上述的兩個條件。 特別的:基本數據類型、String類型本身已經是可以序列化的了。 6.注意點: > 類中聲明為static或transient的變量,不能實現序列化。
7. 其它流的使用
- 標準的輸入、輸出流
System.in: 默認的輸入設備:鍵盤
System.out: 默認的輸出設備:顯示屏
- 打印流
PrintStream和PrintWriter
-
使用第三方框架
- apache-common包
第16章:網絡編程
1. 網絡編程概述
1. 要想實現網絡通信,需要解決的三個問題:
- 問題1:如何準確地定位網絡上一臺或多臺主機
- 問題2:如何定位主機上的特定的應用
- 問題3:找到主機后,如何可靠、高效地進行數據傳輸
2. 實現網絡傳輸的三個要素:(對應解決三個問題)
> 通信要素1:IP地址。對應著解決定位網絡上主機的問題
> 通信要素2:端口號。區分同一臺主機上的不同進程。
> 通信要素3:通信協議。規范通信的規則,進而實現可靠、高效地進行數據傳輸
2. 要素1:InetAddress類的使用
1. 作用:準確地定位網絡上一臺或多臺主機
2. IP地址分類
> IP地址分類方式1 :IPv4 和 IPv6
> IP地址分類方式2:公網地址( 萬維網使用)和 私有地址( 局域網使用)
> 192.168.開頭的就是私有地址
3. 本地回路地址:127.0.0.1 ---> localhost
4. 域名: www.atguigu.com www.baidu.com www.jd.com
www.mi.com www.vip.com
5. InetAddress的使用
5.1 作用:InetAddress類的一個實例表示一個具體的ip地址。
5.2 實例化方式與常用方法
> 實例化:getByName(String host) / getLocalHost()
> 方法:getHostName() / getHostAddress()
3. 要素2:端口號
> 唯一標識設備中的進程(應用程序)
> 不同的進程,需要使用不同的端口號
> 用兩個字節表示的整數,它的取值范圍是0~65535
4. 要素3:網絡通信協議
1. 網絡通信協議的目的:實現雙方可靠、高效的數據傳輸。
2. 網絡參考模型
> OSI參考模型(7層,過于理想化)
> TCP/IP參考模型
> 應用層:HTTP、FTP
> 傳輸層:TCP、UDP
> 網絡層:IP
> 物理+數據鏈路層
5. TCP網絡編程、UDP網絡編程
- TCP、UDP的對比
- 熟悉:TCP的三次握手、四次揮手。
- 例題
-
例題1:客戶端發送內容給服務端,服務端將內容打印到控制臺上。
-
例題2:客戶端發送文件給服務端,服務端將文件保存在本地。
-
例題3:從客戶端發送文件給服務端,服務端保存到本地。并返回“發送成功”給客戶端。并關閉相應的連接。
-
6. URL網絡編程
URL(Uniform Resource Locator):
1. 作用:
統一資源定位符,它表示 Internet 上某一資源的地址。
2. URL的格式:
http://127.0.0.1:8080/examples/ym.png
應用層協議 ip地址 端口號 資源地址
3. URL類的實例化及常用方法
4. 下載指定的URL的資源到本地(了解)
第17章:反射機制
1. Class 的使用
- 掌握:Class的理解
- 掌握:獲取Class的實例(前三種)
/*
* 獲取Class實例的幾種方式(掌握前三種)
* */
@Test
public void test1() throws ClassNotFoundException {
//1.調用類的靜態屬性(.class)
Class clazz1 = User.class;
System.out.println(clazz1);
//2. 通過對象調用getClass()
User u1 = new User();
Class clazz2 = u1.getClass();
System.out.println(clazz2);
System.out.println(clazz1 == clazz2); //true
//3. 調用Class的靜態方法forName(String className)
Class clazz3 = Class.forName("com.atguigu02._class.User");
System.out.println(clazz1 == clazz3);//true
//4. (了解)使用類的加載器,調用loadClass(String className)
Class clazz4 = ClassLoader.getSystemClassLoader().loadClass("com.atguigu02._class.User");
System.out.println(clazz1 == clazz4);
}
- 熟悉:Class可以指向哪些具體的結構?
- 類、接口、注解、枚舉、基本數據類型、數組、void等
2. 類的加載與類的加載器
- 類的加載過程:
過程1:裝載(Loading)
將類的class文件讀入內存,并為之創建一個java.lang.Class對象。此過程由類加載器完成。
過程2:鏈接(Linking)
①驗證Verify:確保加載的類信息符合JVM規范,例如:以cafebabe開頭,沒有安全方面的問題。
②準備Prepare:正式為類變量(static)分配內存并`設置類變量默認初始值`的階段,這些內存都將在方法區中進行分配。
③解析Resolve:虛擬機常量池內的符號引用(常量名)替換為直接引用(地址)的過程。
過程3:初始化(Initialization)
- 執行`類構造器<clinit>()方法`的過程。`類構造器<clinit>()方法`是由編譯期自動收集類中所有類變量的賦值動作和靜態代碼塊中的語句合并產生的。(類構造器是構造類信息的,不是構造該類對象的構造器)。
- 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
- 虛擬機會保證一個`類的<clinit>()方法`在多線程環境中被正確加鎖和同步。
- 了解:類的加載器
1. 常見的類的加載器
> 引導類加載器(Bootstrap ClassLoader):負責系統核心api的加載。
> 擴展類加載器(ExtClassLoader):jdk目錄下的jre/lib/ext目錄下的api
> 應用程序類加載器(或系統類加載器 AppClassLoader):用戶自定義的類的默認的類的加載器
2. 相互之間的關系:不是繼承關系。
但是我們經常說:應用程序類加載器的父類加載器是擴展類加載器;擴展類加載器的父類加載器是引導類加載器。
為什么習慣這樣稱謂呢?涉及到類的加載機制:雙親委派機制。
class SystemClassLoader{
ExtClassLoader loader;
public SystemClassLoader(ExtClassLoader loader){
this.loader = loader;
}
}
3.(掌握)使用類的加載器獲取流,并讀取配置文件信息
/*
* (掌握)使用類的加載器獲取流,并讀取配置文件信息
* */
@Test
public void test3() throws Exception {
Properties pros = new Properties();
//方式1:默認的文件所在當前module下
// FileInputStream is = new FileInputStream(new File("jdbc.properties"));
FileInputStream is = new FileInputStream(new File("src/jdbc1.properties"));
//方式2:默認的文件所在當前module的src下
// ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// InputStream is = systemClassLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
String name = pros.getProperty("name");
String password = pros.getProperty("password");
System.out.println(name + ":" + password);
}
3. 反射的應用1:創建運行時類的對象(掌握)
- jdk8及之前:Class的newInstance()
1.1 如何實現?
調用Class的方法:newInstance(),返回一個運行時類的實例。
1.2 要想創建對象成功,需要滿足:
> 對應的運行時類中必須提供一個空參的構造器
> 對應的運行時類的空參的構造器的訪問權限必須夠
1.3 回憶:JavaBean中要求給當前類提供一個公共的空參的構造器。有什么用?
> 子類繼承父類以后,子類的構造器中在沒有顯式聲明this(形參列表)或super(形參列表)的情況下,默認調用父類空參的構造器
> 提供空參的構造器,便于通過反射的方式動態的創建指定類的對象。
1.4 在jdk9中標識為過時,替換成什么結構:
clazz.getDeclaredConstructor().newInstance()進行替換。
- jdk8之后:調用指定的構造器,設置構造器的訪問權限,創建對象
4. 反射的應用2:獲取運行時類的完整結構
- 了解:獲取所有的屬性、所有的方法、所有的構造器、聲明的注解等。
- 熟悉:獲取運行時類的父類、實現的接口們、所在的包、帶泛型的父類、父類的泛型等
5. 反射的應用3:調用指定的屬性、方法、構造器
(掌握)反射的應用3:調用指定的結構:指定的屬性、方法、構造器
1.1 調用指定的屬性:
步驟1:獲取指定名稱的屬性:調用Class類中的getDeclaredField(String fieldName)
步驟2:確保此屬性是可訪問的:調用Field類中的setAccessible(true);
步驟3:獲取此屬性的值:調用Field類的get(Object obj)
設置此屬性的值:調用Field類的set(Object obj,Object fieldValue)
1.2 調用指定的方法
步驟1:獲取運行時類中指明的方法:調用Class類的getDeclaredMethod(String methodName,Class ... paramTypes)
步驟2:確保此方法是可訪問的:調用Method類中的setAccessible(true);
步驟3:調用此方法:調用Method類的invoke()方法,此方法的返回值即為invoke方法調用者對應的方法的返回值。
1.3 調用指定的構造器
步驟1:獲取指定的構造器:調用Class類的getDeclaredConstructor(Class ... paramTypes)
步驟2:確保此構造器是可訪問的:調用Constructor類中的setAccessible(true);
步驟3:調用此構造器,創建運行時類的對象:調用Constructor類中的newInstance(Object ... values);
6. 反射的應用4:注解的使用(了解)
略。為了后期學習框架做準備
7. 體會反射的動態性
見課后練習題。
第18章:JDK8-17新特性
1. 新特性概述
> 角度1:新的語法規則 (多關注)
比如:lambda表達式、enum、annotation、自動拆箱裝箱、接口中的默認方法和靜態方法、switch表達式、record等
> 角度2:增加、過時、刪除API
比如:新的日期時間的API、Optional、String、HashMap、Stream API
> 角度3:底層的優化、JVM參數的調整、GC的變化、內存結構(永久代--->元空間)
2. JDK8:lambda表達式、方法引用等
重點!
3. JDK8:Stream API的使用
重點!
4. 其它新特性
- 熟悉:Optional的使用
- try-catch、switch表達式、var、instanceof模式匹配、密封類等。