尚硅谷Java入门视频教程(在线答疑+Java面试真题)
Java核心技術
一、Java基本語法
1、關鍵字與保留字
關鍵字的定義和特點
- 被Java語言賦予了特殊含義,用做專門用途的字符串(單詞)
- 關鍵字中所有字母都為小寫
保留字的定義
- 現有Java版本尚未使用,但以后版本可能會作為關鍵字使用。命名標識符時要避免使用這些保留字。
總結:48個關鍵字、2個保留字、3個字面值
用于定義數據類型的關鍵字 class interface enum byte short int long float double char boolean void 用于定義流程控制的關鍵字 if else switch case default while do for break continue return 用于定義訪問權限修飾符的關鍵字 private protected public 用于定義類,函數,變量修飾符的關鍵字 abstract final static synchronized 用于定義類與類之間關系的關鍵字 extends implements 用于定義建立實例及引用實例,判斷實例的關鍵字 new this super instanceof 用于異常處理的關鍵字 try catch finally throw throws 用于包的關鍵字 package import 其他修飾符關鍵字 native strictfp transient volatile assert保留字 goto const用于定義數據類型值的字面值 true false null2、標識符
標識符的定義
- Java對各種變量、方法和類等要素命名時使用的字符序列稱為標識符
技巧:凡是自己可以起名字的地方都叫標識符
定義合法標識符規則
- 由26個英文字母大小寫,0-9,_或$組成
- 數字不可以開頭
- 不可以使用關鍵字和保留字,但能包含關鍵字和保留字
- Java中嚴格區分大小寫,長度無限制
- 標識符不能包含空格
Java中的名稱命名規范
- 包名:多單詞組成時所有字母都小寫:xxxyyyzzz
- 類名、接口名:多單詞組成時,所有單詞的首字母大寫:XxxYyyZzz
- 變量名、方法名:多單詞組成時,第一個單詞首字母小寫,第二個單詞開始每個單詞首字母大寫:xxxYyyZzz
- 常量名:所有字母都大寫,多單詞時每個單詞用下劃線連接:XXX_YYY_ZZZ
注意點:
1、在起名字時,為了提高閱讀性,要盡量有意義,“見名知意”。
2、Java采用unicode字符集,因此標識符也可以使用漢字聲明,但是不建議使用。
3、變量
變量的概念
- 內存中的一個存儲區域
- 該區域的數據可以在同一類型范圍內不斷變化
- 變量是程序中最基本的存儲單元,包含變量類型、變量名、存儲的值
變量的作用
- 用于在內存中保存數據
使用變量注意
- Java中每個變量必須先聲明,后使用
- 使用變量名來訪問這塊區域的數據
- 變量的作用域:其定義所在的一對{}內
- 變量只有在其作用域內才有效
- 同一個作用域內,不能定義重名的變量
變量的分類
按數據類型分類
- 基本數據類型
- 數值型
- 整數類型 (byte short int long)
- 浮點類型 (float double)
- 字符型 (char)
- 布爾型 (boolean)
- 數值型
- 引用數據類型
- 類 (class)
- 接口 (interface)
- 數組 ([])
按聲明位置分類
- 成員變量
- 實例變量 (不以static修飾)
- 類變量 (以static修飾)
- 局部變量
- 形參 (方法、構造器中定義的變量)
- 方法局部變量 (在方法內部定義)
- 代碼塊局部變量 (在代碼塊內定義)
整數變量:byte、short、int、long
- Java各整數類型有固定的表數范圍和字段長度,不受具體OS的影響,以保證Java程序的可移植性。
- Java的整型常量默認為int型,聲明long型常量須加l或者L
- Java程序中變量通常聲明為int型,除非不足以表示較大的數,才使用long
| byte | 1字節=8bit位 | -128~127 |
| short | 2字節 | - 2 15 2^{15} 215~ 2 15 2^{15} 215-1 |
| int | 4字節 | - 2 31 2^{31} 231~ 2 31 2^{31} 231-1(約21億) |
| long | 8字節 | - 2 63 2^{63} 263~ 2 63 2^{63} 263-1 |
bit:計算機中的最小存儲單位。byte:計算機中基本存儲單元。
浮點類型:float、double
- 與整數類型類似,Java浮點類型也有固定的表數范圍和字段長度,不受具體操作系統的影響
- 浮點型常量有兩種表示形式:
- 十進制數形式
- 科學計數法形式
- float:單精度,尾數可以精確到7位有效數字。很多情況下,精度很難滿足需求。
- double:雙精度,精度是float的兩倍。通常采用此類型。
- Java的浮點型常量默認是double型,聲明float型常量,須后加f或F。
| float | 4字節 | -3.403E38~3.403E38 |
| double | 8字節 | -1.798E308~1.798E308 |
注意點:float表示數值的范圍比long還大
字符類型:char
- char型數據用來表示通常意義上“字符”(2字節)
- Java中所有字符都使用Unicode編碼,故一個字符可以存儲一個字母,一個漢字,或其他書面語的一個字符。
- 字符型變量的三種表現形式:
- 字符常量是用單引號’'括起來的單個字符。
- Java中還允許使用轉義字符’\'來將其后的字符轉變為特殊字符型常量。
- 直接使用Unicode值來表示字符型常量。
- char類型是可以進行運算的。因為它都對應有Unicode編碼。
常用ASCII碼值: ‘a’ == 97; ‘A’ == 65;
布爾型:boolean
- 只能取兩個值之一:true、false
- 常常在條件判斷、循環結構中使用
基本數據類型轉換
- 自動類型轉換:容量小的類型自動轉換為容量大的數據類型。數據類型按容量大小排序為:char、byte、short --> int --> long --> float --> double
- 有多種類型的數據混合運算時,系統首先自動將所有數據類型轉換為容量最大的那種數據類型,然后再進行計算
- byte,short,char之間不會互相轉換,他們三者在計算時首先轉換為int類型
Java在做運算的時候,如果操作數均在int范圍內,那么一律在int的空間內運算。
- boolean類型不能與其它數據類型運算
- 當把任何基本數據類型的值和字符串(String)進行連接運算時(+),基本數據類型的值將自動轉化為字符串(String)類型
說明:此時的容量大小指的是表示數的范圍的大小。比如:float容量要大于long的容量
強制類型轉換
- 自動類型轉換的逆過程,將容量大的數據類型轉換為容量小的數據類型。使用時要加上強制轉換符:(),但可能造成精度降低或溢出,格外要注意
- 通常,字符串不能直接轉換為基本類型,但通過基本類型對應的包裝類則可以實現把字符串轉換為基本類型
- boolean類型不可以轉換為其它的數據類型
說明:整型常量,默認類型是int型;浮點型常量,默認類型是double型。
byte b = 12;// byte b1 = b + 1; // 編譯失敗// float f1 = b + 12.3; // 編譯失敗字符串類型:String
- String不是基本數據類型,屬于引用數據類型
- 使用方式與基本數據類型一致。
- 一個字符串可以串接另一個字符串,也可以直接串接其他類型的數據。
4、進制
- 所有數字在計算機底層都是以二進制形式存在。
- 對于整數,有四種表示方式:
- 二進制(binary):0,1,滿2進1,以0b或0B開頭
- 十進制(decimal):0-9,滿10進1
- 八進制(octal):0-7,滿8進1,以數字0開頭表示
- 十六進制(hex):0-9及A-F,滿16進1,以0x或0X開頭表示。此處的A-F不區分大小寫。
- Java整數常量默認是int類型,當用二進制定義整數時,其第32位(最高位)是符號位:當是long類型時,二進制默認占64位,第64位是符號位
- 二進制的整數有如下三種形式:
- 原碼:直接將一個數值換成二進制數。最高位是符號位
- 負數的反碼:是對原碼按位取反,只有最高位(符號位)確認為1
- 負數的補碼:其反碼加1
- 計算機以二進制補碼的形式保持所有的整數
- 正數的原碼、反碼、補碼都相同
- 負數的補碼是其反碼+1
5、運算符
運算符是一種特殊的符號,用以表示數據的運算、賦值和比較等。
- 算術運算符
- 賦值運算符
- 比較運算符(關系運算符)
- 邏輯運算符
- 位運算符
- 三元運算符
算術運算符
| + | 正號 | +3 | 3 |
| - | 負號 | b=4; -b | -4 |
| + | 加 | 5+5 | 10 |
| - | 減 | 6-4 | 2 |
| * | 乘 | 3*4 | 12 |
| / | 除 | 5/5 | 1 |
| % | 取模(取余) | 7%5 | 2 |
| ++ | 自增(前),先運算后取值 | a=2;b=++a; | a=3;b=3 |
| ++ | 自增(后),先取值后運算 | a=2;b=a++; | a=3;b=2 |
| – | 自減(前),先運算后取值 | a=2;b=–a; | a=1;b=1 |
| – | 自減(后),先取值后運算 | a=2;b=a–; | a=1;b=2 |
| + | 字符串連接 | “He”+“llo” | “Hello” |
疑難點1:%取余運算,結果的符號與被模數的符號相同
int m1 = 12; int n1 = 5; System.out.println("m1 % n1 = " + m1 % n1); // m1 % n1 = 2 int m2 = -12; int n2 = 5; System.out.println("m1 % n1 = " + m2 % n2); // m1 % n1 = -2 int m3 = 12; int n3 = -5; System.out.println("m1 % n1 = " + m3 % n3); // m1 % n1 = 2 int m4 = -12; int n4 = -5; System.out.println("m1 % n1 = " + m4 % n4); // m1 % n1 = -2疑難點2:++ 和 – 不會改變變量本身的數據類型
short s = 10; //s = s + 1; //編譯失敗 s = (short) (s + 1); //正確 s++; //正確byte b = 127; b++; System.out.println(b); //-128賦值運算符
符號:= 拓展賦值運算符:+=,-=,*=,/=,%=
- 當=兩側數據類型不一致時,可以使用自動類型轉換或使用強制類型轉換原則進行處理
- 支持連續賦值
疑難點:拓展賦值運算符不會改變變量本身的數據類型
short s = 10; //s = s + 2; //編譯失敗 s += 2; System.out.println(s); //12 int i = 1; i *= 0.1; System.out.println(i); // 0比較運算符
| == | 相等于 | 4==3 | false |
| != | 不等于 | 4!=3 | true |
| < | 小于 | 4<3 | false |
| > | 大于 | 4>3 | true |
| <= | 小于等于 | 4<=3 | false |
| >= | 大于等于 | 4>=3 | true |
| instanceof | 檢查是否是類的對象 | “Hello” instanceof String | true |
- 比較運算符的結果都是boolean型,也就是要么是true,要么是false。
- 比較運算符==不能誤寫成=
- > < >= <=:只能使用在數值類型的數據之間
- ==:不僅可以使用在數值類型數據之間,還可以使用在其他引用類型變量之間。
邏輯運算符
&-邏輯與
|-邏輯或
!-邏輯非
&&-短路與
||-短路或
^-邏輯異或
| true | true | true | true | true | true | false | false |
| true | false | false | false | true | true | false | true |
| false | true | false | false | true | true | true | true |
| false | false | false | false | false | false | true | false |
區分 & 與 &&
- 相同點1:&與&&的運算結果相同
- 相同點2:當符號左邊為true時,二者都會執行符號右邊的運算
- 不同點:當符號左邊為false時,&繼續執行符號右邊的運算,&&不再執行符號右邊的運算
區分 | 與 ||
- 相同點1:|與||的運算結果相同
- 相同點2:當符號左邊是false時,二者都會執行符號右邊的運算
- 不同點1:當符號左邊是true時,|繼續執行符號右邊的運算,而||不再執行符號右邊的運算
位運算符
| << | 左移 | 3 << 2 = 12 --> 3*2*2=12 |
| >> | 右移 | 3 >> 1 = 1 --> 3/2=1 |
| >>> | 無符號右移 | 3 >>> 1 = 1 --> 3/2=1 |
| & | 與運算 | 6 & 3 = 2 |
| | | 或運算 | 6 | 3 = 7 |
| ^ | 異或運算 | 6 ^ 3 = 5 |
| ~ | 取反運算 | ~6 = -7 |
位運算時直接對整數的二進制進行的運算
| << | 空位補0,被移除的高位丟棄,空缺位補0 |
| >> | 被移除的二進制最高位是0,右移后,空缺位補0,若最高位是1,空缺位補1 |
| >>> | 被移位二進制最高位無論是0或者是1,空缺位都用0補 |
| & | 二進制位進行&運算,只有1&1時結果是1,否則是0 |
| | | 二進制位進行|運算,只有0|0時結果是0,否則是1 |
| ^ | 相同二進制進行^運算結果是0:1^1=0,0^0=0;不相同二進制位^運算結果是1:1^0=1,0^1=1 |
| ~ | 各二進制碼按補碼各位取反 |
m = k ^ n = (m ^ n) ^ n
三元運算符
- 格式:(條件表達式) ? 表達式1 : 表達式2;
- 表達式1和表達式2為同種類型
三元運算符與if-else的聯系與區別
- 三元運算符可簡化if-else語句
- 三元運算符要求必須返回一個結果
- if后的代碼塊可有多個語句
附加:運算符的優先級
| R–>L | ++ -- ~ !(data type) |
| L–>R | * / % |
| L–>R | + - |
| L–>R | << >> >>> |
| L–>R | < > <= >= instanceof |
| L–>R | == != |
| L–>R | & |
| L–>R | ^ |
| L–>R | | |
| L–>R | && |
| L–>R | || |
| R–>L | ? : |
| R–>L | = *= /= %= |
| += -= <<= >>= | |
| >>>= &= ^= |= |
6、程序流程控制
- 程序控制語句是用來控制程序中各語句執行順序的語句,可以把語句組合成能完成一定功能的小邏輯模塊。
- 其流程控制方式采用結構化程序設計中規定的三種基本流程結果,即:順序結構、分支結構、循環結構
分支結構
if-else結構
- else 結構是可選的
- 針對與條件表達式:
- 如果多個條件表達式之間是“互斥”關系(或沒有交集的關系),哪個判斷和執行語句聲明在上面還是下面,無所謂。
- 如果多個條件表達式之間有交集的關系,需要根據實際情況,考慮清楚應該將哪個結構聲明在上面。
- 如果多個條件表達式之間有包含關系,通常情況下,需要將范圍小的聲明在范圍大的上面。否則,范圍小的就沒有機會執行
- if-else 結構是可以互相嵌套的。
- 如果if-else結構中的執行語句只有一行時,對應的一對{}可以省略的。但是,不建議大家省略。
- 多個if-else時,if-else的配對方式采用就近原則進行配對
switch-case結構
switch(表達式){case 常量1:語句1;// break;case 常量2:語句2;// break;... ...case 常量N:語句N;// break;default:語句;// break; }- 根據switch表達式中的值,依次匹配各個case中的常量。一旦匹配成功,則進入相應的case結構中,調用其執行語句。當調用完執行語句以后,則仍然繼續向下執行其他case結構中的執行語句,直到遇到break關鍵字或此switch-case結構末尾結束為止。
- break,可以使用在switch-case結構中,表示一旦執行到此關鍵字,就跳出switch-case結構
- switch結構中的表達式,只能是如下的6種數據類型之一:byte、short、char、int、枚舉類型(JDK5.0新增)、String類型(JDK7.0新增)
- case 之后只能聲明常量,不能聲明范圍。
- break 關鍵字是可選的
- default:相當于if-else結構中的else。default結構是可選的,而且位置是靈活的。
- 凡是可以使用switch-case的結構,都可以轉換為if-else。反之,不成立。
- 當發現既可以使用switch-case且switch中表達式的取值情況不太多,又可以使用if-else時,我們優先選擇使用switch-case。原因:switch-case執行效率稍高。
循環結構
循環語句的四個組成部分
- 初始化部分
- 循環條件部分
- 循環體部分
- 迭代部分
for循環結構
for(初始化部分;循環條件部分;迭代部分) {循環體部分 }執行過程:初始化部分-循環條件部分-循環體部分-迭代部分-循環條件部分-循環體部分-迭代部分- ... - 循環條件部分while循環結構
初始化部分 while(循環條件部分) {循環體部分迭代部分 }執行過程:初始化部分-循環條件部分-循環體部分-迭代部分-循環條件部分-循環體部分-迭代部分- ... - 循環條件部分- 寫while循環千萬小心不要丟了迭代條件。一旦丟了,就可能導致死循環!
- 寫程序需要避免出現死循環。
- for循環和while循環是可以相互轉換的。區別在于for循環和while循環的初始化條件部分的作用范圍不同。
do-while循環結構
初始化部分 do{循環體部分迭代部分 }while(循環條件部分);執行過程:初始化部分-循環體部分-迭代部分-循環條件部分-循環體部分-迭代部分- ... - 循環條件部分- do-while循環至少會執行一次循環體!
- 開發中,使用for和while更多一些。較少使用do-while
特殊關鍵字的使用
| break | switch-case和循環結構中 | 結束當前循環 | 關鍵字后面不能聲明執行語句 |
| continue | 循環結構中 | 結束當次循環 | 關鍵字后面不能聲明執行語句 |
- return:并非專門用于結束循環的,它的功能是結束一個方法。當一個方法執行到一個return語句時,這個方法將被結束。
- 與break和continue不同的是,return直接結束整個方法,不管這個return處于多少層循環之內
7、數組
- 數組(Array),是多個相同類型數據按一定順序排列的集合,并使用一個名字命名,并通過編號的方式對這些數據進行統一管理。
- 數組本身是引用數據類型,而數組中的元素可以是任何數據類型,包括基本數據類型和引用數據類型。
- 創建數組對象會在內存中開辟一整塊連續的空間,而數組名中引用的是這塊連續空間的首地址。
- 數組的長度一旦確定,就不能修改。
數組元素的默認初始化值
- 數組元素是整型:0
- 數組元素是浮點型:0.0
- 數組元素是char型:0或者’\u0000’,而非’0’
- 數組元素是boolean型:false
- 數組元素是引用數據類型:null
對于二維數組的理解:我們可以看成是一維數組array1又作為另一個一維數組array2的元素而存在。其實,從數組底層的運行機制來看,其實沒有多維數組。
//一些錯誤的寫法 //int[] ids; //ids = {1,2,2}; //編譯錯誤 //一些正確但是不標準的寫法 int[] arr1 = {1,2,2,3}; //類型推斷 int[] arr2[] = new int[][]{{1,2,2},{4,5},{6,7,8}}; // 數組[]位置可以隨意 int[] arr3[] = {{1,2,2},{4,5},{6,7,8}}; String[] strArray1[] = {{"222", "2222"},{"111", "3333", "5555"}};附加:Arrays工具類的使用
- java.util.Arrays類即為操作數組的工具類,包含了用來操作數組的各種方法
| boolean equals(int[] a,int[] b) | 判斷兩個數組是否相等 |
| String toString(int[] a) | 輸出數組信息 |
| void fill(int[] a,int val) | 將指定值填充到數組之中 |
| void sort(int[] a) | 對數組進行排序 |
| int binarySearch(int[] a,int key) | 對排列后的數組進行二分法檢索指定的值 |
二、面向對象編程
1、面向過程與面向對象
面向過程(POP)與面向對象(OOP)
- 二者都是一種思想,面向對象是相對于面向過程而言的。面向過程,強調的是功能行為,以函數為最小單位,考慮怎么做。面向對象,將功能封裝進對象,強調具備了功能的對象,以類/對象為最小單位,考慮誰來做。
- 面向對象更加強調運用人類在日常的思維邏輯中采用的思想方法與原則,如抽象、分類、繼承、聚合、多態等。
面向對象的三大特征
- 封裝性
- 繼承性
- 多態性
面向對象的思想概述
- 程序員從面向過程的執行者轉化成了面向對象的指揮者
- 面向對象分析方法分析問題的思路和步驟:
- 根據問題需要,選擇問題所針對的現實世界的實體。
- 從實體中尋找解決問題相關的屬性和功能,這些屬性和功能就形成了概念世界中的類。
- 把抽象的實體用計算機語言進行描述,形成計算機世界中的類的定義。即借助某種程序語言,把類構造成計算機能夠識別和處理的數據結構。
- 把類實例化成計算機世界中的對象。對象是計算機世界中解決問題的最終工具。
2、Java語言基本元素:類和對象
類和對象
- 類(Class)和對象(Object)是面向對象的核心概念。
- 類是對一類事物的描述,是抽象的、概念上的定義
- 對象是實際存在的的該類事物的每個個體,因而也稱為實例。
類和對象的使用(面向對象思想落地的實現)
- 創建類,設計類的成員
- 創建類的對象
- 通過"對象.屬性"或"對象.方法"調用對象的結構
如果創建了一個類的多個對象,則每個對象都獨立的擁有一套類的(非static的)屬性。意味著:如果我們修改一個對象的屬性a,則不影響另外一個對象屬性a的值。
對象的創建和使用:內存解析
- 堆(Heap),此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內存。這一點在Java虛擬機規范中的描述是:所有的對象實例以及數組都要在堆上分配。
- 通常所說的棧(Stack),是指虛擬機棧。虛擬機棧用于存儲局部變量等。局部變量表存放了編譯期可知長度的各種基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不等同于對象本身,是對象在堆內存的首地址)。方法執行完,自動釋放。
- 方法區(Method Area),用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據
3、類的成員之一:屬性
屬性(成員變量) VS 局部變量
- 相同點
- 定義變量的格式:數據類型 變量名 = 變量值
- 先聲明,后使用
- 變量都有其對應的作用域
- 不同點
- 在類中聲明的位置不同。屬性直接定義在類的一對{}內;局部變量聲明在方法內、方法形參、代碼塊內、構造器形參、構造器內部的變量
- 關于權限修飾符的不同。屬性可以在聲明屬性時,指明其權限,使用權限修飾符。常用的權限修飾符:private、public、缺省、protected。局部變量不可以使用權限修飾符。
- 默認初始化值的情況
- 屬性:類的屬性,根據其類型,都有默認初始化值。整型(byte、short、int、long):0;浮點型(float、double):0.0;字符型(char):0(或’\u0000’);布爾型(boolean):false;引用數據類型(類、數組、接口):null
- 局部變量:沒有默認初始值。意味著,我們在調用局部變量之前,一定要顯式賦值。特別地,形參在調用時,我們賦值即可。
- 在內存中加載的位置:屬性加載在堆空間中(非static);局部變量加載在??臻g
4、類的成員之二:方法
方法的聲明
權限修飾符 返回值類型 方法名(形參列表){方法體 } // 注意:static、final、abstract來修飾的方法,后面再講返回值類型:有返回值 VS 沒有返回值
- 如果方法有返回值,則必須在方法聲明時,指定返回值的類型。同時,方法中,需要使用return關鍵字來返回指定類型的變量或常量:“return 數據”。
- 如果方法沒有返回值,則方法聲明時,使用void來表示。通常,沒有返回值的方法中,就不需要使用return。但是,如果使用的話,只能“return;”表示結束此方法的意思。
return 關鍵字的使用
- 使用范圍:使用在方法體中
- 作用:1、結束方法;2、針對于有返回值類型的方法,使用"return 數據"方法返回所要的數據。
- 注意點:return關鍵字后面不可以聲明執行語句。
方法的使用
- 方法的使用中,可以調用當前類的屬性或方法。特殊的,方法A中又調用了方法A:遞歸方法
- 方法中不可以定義方法。
理解“萬事萬物皆對象”
- 在Java語言范疇中,我們都將功能、結構等封裝到類中,通過類的實例化,來調用具體的功能接口
- Scanner, String等
- 文件:File
- 網絡資源:URL
- 涉及到Java與前端Html、后端的數據庫交互時,前后端的結構在Java層面交互時,都體現為類和對象。
方法的重載(overload)
- 重載的概念:在同一個類中,允許存在一個以上的同名方法,只要它們的參數個數或者參數類型不同即可。
- 重載的特點:與返回值類型無關,只看參數列表,且參數列表必須不同(參數個數或參數類型)。調用時,根據方法參數列表的不同來區分。
可變個數的形參
JavaSE 5.0中提供了Varargs(variable number of arguments)機制,允許直接定義能和多個實參相匹配的形參。從而,可以用一種更簡單的方式,來傳遞個數可以變的實參。
// JDK 5.0以前:采用數組形參來定義方法,傳入多個同一類型的變量 public static void test(int a, String[] books); // JDK 5.0:采用可變個數形參來定義方法,傳入多個同一類型變量 public static void test(int a, String...books);- 聲明格式:方法名(參數的類型名…參數名)
- 可變參數:方法參數部分指定類型的參數個數是可變多個:0個,1個或多個
- 可變個數形參的方法和同名的方法之間,彼此構成重載
- 可變參數方法的使用與方法參數部分使用數組是一致的
- 方法的參數部分有可變形參,需要放在形參聲明的最后
- 在一個方法的形參位置,最多只能聲明一個可變個數形參
方法參數的值傳遞機制
Java的實參值如何傳入方法呢?
- Java里方法的參數傳遞方式只有一種:值傳遞。即將實際參數值的副本(復制品)傳入方法內,而參數本身不受影響。
- 形參是基本數據類型:將實參基本數據類型變量的“數據值”傳遞給形參
- 形參是引用數據類型:將實參引用數據類型變量的“地址值”傳遞給形參
5、面向對象特征之一:封裝性
四種訪問權限修飾符
- Java權限修飾符public、protected、private置于類的成員定義前,用來限定對象對該類成員的訪問權限
| private | Yes | |||
| (缺省) | Yes | Yes | ||
| protected | Yes | Yes | Yes | |
| public | Yes | Yes | Yes | Yes |
對于class的權限修飾只可以用public和default(缺省)。
- public類可以在任意地方被訪問。
- default類只可以被同一個包內部的類訪問。
什么是封裝性?
- 隱藏對象內部的復雜性,只對外公開簡單的接口。便于外界調用,從而提高系統的可拓展性、可維護性。通俗的說,把該隱藏的隱藏起來,該暴露的暴露出去。這就是封裝性的設計思想。
封裝性的體現
- Java規定的四種權限(從小到大排列):private、缺省、protected、public
- 4種權限可以修飾類及類的內部結構:屬性、方法、構造器、內部類
- 具體的,4中權限都可以用來修飾類的內部結構:屬性、方法、構造器、內部類;修飾類的話,只能使用:缺省、public
總結:Java提供了4種權限修飾符來修飾類及類的內部結構,體現類及類的內部結構在被調用時的可見性的大小
6、類的成員之三:構造器
構造器的作用
- 創建對象
- 初始化對象的信息
說明
- 如果沒有顯示的定義類的構造器的話,則系統默認提供一個空參的構造器,默認構造器的權限修飾符與類修飾符一致
- 定義構造器的格式:權限修飾符 類名(形參列表){}
- 一個類中定義的多個構造器,彼此構成重載
- 一旦我們顯式的定義了類的構造器之后,系統就不再提供默認的空參構造器
- 一個類中,至少會有一個構造器
屬性賦值的過程
- 默認初始化
- 顯式初始化
- 構造器中的初始化
- 通過“對象.屬性”或“對象.方法”的方式賦值
7、拓展知識
JavaBean
- JavaBean是一種Java語言寫成的可重用組件。
- 所謂JavaBean,是指符合如下標準的Java類:
- 類是公共的
- 有一個無參的公共的構造器
- 有屬性,且有對應的get、set方法
UML類圖
- +表示public類型,-表示private類型,#表示protected類型
- 方法的寫法:方法的類型(+、-) 方法名(參數名: 參數類型): 返回值類型
- 屬性::前是屬性名,:后是屬性的類型
8、關鍵字:this
- this可以用來修飾、調用:屬性、方法、構造器
- this修飾屬性和方法時可以理解為:當前對象或當前正在創建的對象
- 在類的方法中,我們可以使用“this.屬性”或者“this.方法”的方式,調用當前對象屬性或方法。但是通常情況下,我們都選擇省略"this."。特殊情況下,如果方法的形參和類的屬性同名時,我們必須顯示的使用“this.變量”的方式,表明此變量是屬性,而非形參。
- 在類的構造器中,我們可以使用“this.屬性”或“this.方法”的方式,調用當前正在創建的對象屬性或方法,但是通常情況下,我們都選擇省略"this."。特殊情況下,如果構造器的形參和類的屬性同名時,我們必須顯式的使用“this.變量”的方式,表明此變量是屬性,而非形參。
- this調用構造器
- 我們在類的構造器中,可以顯式的使用“this(形參列表)”方式,調用本類中指定的其他構造器
- 構造器中不能通過“this(形參列表)”方式調用自己
- 如果一個類中有n個構造器,則最多有n-1個構造器中使用了“this(形參列表)”
- 規定,“this(形參列表)”必須聲明在當前構造器的首行,因此構造器內部,最多只能聲明一個“this(形參列表)”,用來調用其他的構造器
9、關鍵字:package
- 為了更好的實現項目中類的管理,提供了包的概念
- 使用package聲明類或接口所屬的包,聲明在源文件的首行
- 包,屬于標識符,遵循標識符的命名規則、規范(xxxyyyzzz)、“見名知意”
- 每“.”一次,就表示一層文件目錄
補充:同一個包下,不能命名同名的接口、類;不同包下,可以命名同名的接口、類。
10、關鍵字:import
- 在源文件中使用import顯示的導入指定包下的類或接口
- 聲明在包的聲明和類的聲明之間
- 如果需要導入多個類或者接口,那么就并列顯式多個import語句即可
- 舉例:可以使用java.util.*的方式,一次性導入util包下的所有的類或接口
- 如果導入的類或接口是java.lang包下的,或者是當前包下的,則可以省略此import語句
- 如果在代碼中使用不同包下的同名的類。那么就需要使用類的全類名的方式指明調用的是哪個類
- 如果已導入java.a包下的類。那么如果需要使用a包的子包下的類的話,仍然需要導入。
- import static組合的使用:調用指定類或接口下的靜態的屬性或方法
11、面向對象特征之二:繼承性
繼承性的好處
- 減少了代碼的冗余,提高了代碼的復用性
- 便于功能的拓展
- 為之后多態性的使用,提供了前提
繼承性的說明
- 繼承性的格式: class A extends B{}
- 體現:一旦子類A繼承父類B以后,子類A中就獲取了父類B中的聲明的所有的屬性和方法。特別的,父類中聲明為private的屬性或方法,子類繼承父類以后,仍然認為獲取了父類中的私有的結構。只是因為封裝性的影響,使得子類不能直接調用父類的結構而已。
- 子類繼承父類以后,還可以聲明自己特有的屬性或方法:實現功能的拓展
Java中關于繼承性的規定
- 一個類可以被多個子類繼承
- Java中類的單繼承性:一個類只能有一個父類
- 子父類是相對的概念
- 子類直接繼承的父類稱為直接父類;間接繼承的父類稱為間接父類
- 子類繼承父類以后,就獲取了直接父類以及所有間接父類中聲明的屬性和方法
補充說明
- 如果我們沒有顯示的聲明一個類的父類的話,則此類繼承于java.lang.Object類
- 所有的java類(除java.lang.Object類之外)都直接或間接的繼承于java.lang.Object類
- 意味著,所有的java類都具有java.lang.Object類聲明的功能。
方法的重寫(override/overwrite)
- 重寫:子類繼承父類以后,可以對父類中同名同參數的方法,進行覆蓋操作
- 應用:重寫以后,當創建子類對象以后,通過子類對象調用子父類中的同名同參數的方法時,實際執行的是子類重寫父類的方法
- 重寫的規定
- 子類重寫的方法的方法名和形參列表與父類被重寫的方法的方法名和形參列表相同
- 子類重寫的方法的權限修飾符不小于父類被重寫的方法的權限修飾符
- 特殊情況:子類不能重寫父類中聲明為private權限的方法
- 返回值類型:
- 父類被重寫的方法的返回值類型是void,則子類重寫的方法的返回值類型只能是void
- 父類被重寫的方法的返回值類型是A類型,則子類重寫的方法的返回值類型可以是A類或A類的子類
- 父類被重寫的方法的返回值類型是基本數據類型(比如:double),則子類重寫的方法的返回值類型必須是相同的。
- 子類重寫的方法拋出的異常類型不大于父類被重寫的方法拋出的異常類型
- 子類和父類中的同名同參數的方法要么都聲明為非static的(考慮重寫),要么都聲明為static的(不是重寫)。
12、關鍵字:super
- super可以用來調用:屬性、方法、構造器
- super的使用
- 我們可以在子類的方法或構造器中。通過使用"super.屬性"或"super.方法"的方式,顯式的調用父類中聲明的屬性或方法。但是,通常情況下,我們習慣省略"super."
- 特殊情況:當子類和父類中定義了同名的屬性時,我們要想要在子類中調用父類中聲明的屬性,則必須顯式的使用"super.屬性"的方式,表明調用的是父類中聲明的屬性,否則默認是調用子類的屬性。
- 特殊情況:當子類重寫了父類中的方法以后,我們想在子類中的方法中調用父類中被重寫的方法時,則必須顯式使用"super.方法"的方式,表明調用的是父類中被重寫的方法,否則默認是調用子類的方法。
- super調用構造器
- 我們可以在子類的構造器中顯式的使用"super(形參列表)"的方式,調用父類中聲明的指定的構造器
- "super(形參列表)"的使用,必須聲明在子類構造器的首行
- 我們在類的構造器中,針對于"this(形參列表)"或"super(形參列表)"只能二選一,不能同時出現
- 在構造器的首行,沒有顯示的聲明"this(形參列表)“或"super(形參列表)”,則默認調用的是父類中的空參的構造器
- 在類的多個構造器中,至少有一個類的構造器中使用了"super(形參列表)",調用父類中的構造器
子類對象實例化的全過程
- 從結果上來看:(繼承性)
- 子類繼承父類以后,就獲取了父類中聲明的屬性或方法。
- 創建子類的對象,在堆空間中,就會加載所有父類中聲明的屬性
- 從過程上來看:
- 當我們通過父類的構造器創建子類對象時,我們一定會直接或間接的調用其父類的構造器,進而調用父類的父類的構造器,直到調用了java.lang.Object類中空參的構造器為止。正因為加載過所有的父類的結構,所以才可以看到內存中有父類中的結構,子類對象才可以考慮進行調用。
- 明確:雖然創建子類對象時,調用了父類的構造器,但是自始至終就創建過一個對象,即為new的子類對象。
13、面向對象特征之三:多態性
- 理解多態性:可以理解為一個事物的多種形態。
- 何為多態性:
- 對象的多態性:父類的引用指向子類的對象(或子類的對象賦給父類的引用)
- 多態的使用,虛擬方法調用
- 有了對象的多態性以后,我們在編譯期,只能調用父類中聲明的方法,但在運行期,我們實際執行的是子類重寫父類的方法。
- 總結:編譯,看左邊;運行,看右邊。
- 多態性的使用前提:
- 類的繼承關系
- 方法的重寫
- 對象的多態性,只適用于方法,不適用于屬性(編譯和運行都看左邊)
區分方法的重載與重寫
- 二者定義的不同:
- 重載:在同一個類中,允許存在一個以上的同名方法,只要它們的參數個數或者參數類型不同即可。
- 重寫:子類繼承父類以后,可以對父類中同名同參數的方法,進行覆蓋操作
- 從編譯和運行的角度看:
- 重載,是指允許存在多個同名方法,而這些方法的參數不同。編譯器根據方法不同的參數表,對同名方法的名稱做修飾。對于編譯器而言,這些同名方法就成了不同的方法。它們的調用地址在編譯期就綁定了。Java的重載是可以包括父類和子類的,即子類可以重載父類的同名不同參數的方法。
- 所以:對于重載而言,在方法調用之前,編譯期就已經確定了所要調用的方法,這稱為“早綁定”或“靜態綁定”
- 而對于多態,只有等到方法調用的那一刻,解釋器運行器才會確定所要調用的具體方法,這稱為“晚綁定”或“動態綁定”。
instanceof 操作符
- instanceof 關鍵字的使用:a instanceof A:判斷對象a是否是類A的實例。如果是,返回true;如果不是,返回false
- 使用情境: 為了避免在向下轉型時出現ClassCastException的異常,我們在向下轉型之前,先進行instanceof的判斷,一旦返回true,就進行向下轉型。如果返回false,就不進行向下轉型。
- 如果類B是類A的父類,且a instanceof A返回true,則a instanceof B也返回true
==和equals的區別
- ==既可以比較基本類型也可以比較引用類型。對于基本類型就是比較值,對于引用類型就是比較內存地址
- equals的話,它是屬于java.lang.Object類里面的方法,如果該方法沒有被重寫過默認也是==;我們可以看到String等類的equals方法是被重寫過的,而且String類在日常開發中用的比較多,久而久之,形成了equals是比較值的錯誤觀點
- 具體要看自定義類里有沒有重寫Object的equals方法來判斷
- 通常情況下,重寫equals方法,會比較類中的相應屬性是否都相等
Java中的JUnit單元測試
- 創建Java類,進行單元測試。此時的Java類要求:
- 此類是public的
- 此類提供公共的無參的構造器
- 此類中聲明單元測試方法。此時的單元測試方法的權限是public,沒有返回值,沒有形參
- 此單元測試方法上需要聲明注解:@Test
- 寫完代碼后,左鍵雙擊單元測試方法名,右鍵:run as - JUnit Test
- 說明:如果執行結果沒有任何異常:綠條;如果執行結果出現異常:紅條
14、包裝類(Wrapper)的使用
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| boolean | Boolean |
| char | Character |
Byte、Short、Integer、Long、Float、Double的父類為Number;Boolean、Character的父類為Object
基本類型、包裝類與String類間的轉換
- 基本數據類型 --> 包裝類:自動裝箱
- 包裝類 --> 基本數據類型:自動拆箱
- 基本數據類型 --> String類:String類的valueOf()方法
- String類 --> 基本數據類型:調用相應包裝類的parseXxx(String)靜態方法
- 包裝類 --> String類:包裝類對象的toString()方法
- Integer內部定義了IntegerCache結構,IntegerCache中定義了Integer[],保存了從-128到127范圍的整數。如果我們使用自動裝箱的方式,給Integer賦值的范圍在-128到127范圍內時,可以直接使用數組中的元素,不用再去new了。目的為了提高效率
15、關鍵字:static
- 使用static修飾屬性:靜態變量(或類變量)
- 屬性,按是否使用static修飾,又分為:靜態屬性 vs 非靜態屬性(實例變量)
- 實例變量:我們創建了類的多個對象,每個對象都獨立擁有一套類中的非靜態屬性。當修改其中一個對象的非靜態屬性時,不會導致其他對象中同樣的屬性值修改
- 靜態變量:我們創建了類的多個對象,多個對象共享同一個靜態變量。當通過某一個對象修改靜態變量時,會導致其他對象調用此靜態變量時,是修改過了的
- static修飾屬性的其他說明
- 靜態變量隨著類的加載而加載??梢酝ㄟ^"類.靜態變量"的方式進行調用
- 靜態變量的加載要早于對象的創建
- 由于類只會加載一次,則靜態變量在內存中也只會存在一份:存在方法區的靜態域中
- 屬性,按是否使用static修飾,又分為:靜態屬性 vs 非靜態屬性(實例變量)
- 使用static修飾方法:靜態方法
- 隨著類的加載而加載,可以通過"類.靜態方法"的方式進行調用
- 靜態方法中,只能調用靜態的方法或屬性;非靜態方法中,既可以調用非靜態的方法或屬性,也可以調用靜態的方法或屬性
- static注意點
- 在靜態的方法內,不能使用this關鍵字、super關鍵字
- 開發中,如何確定一個屬性是否要聲明為static的?
- 屬性可以被多個對象所共享的,不會隨著對象的不同而不同
- 類中的常量也常常聲明為static
- 開發中,如何確定一個方法是否要聲明為static的?
- 操作靜態屬性的方法,通常設置為static的
- 工具類中的方法,習慣上聲明為static的
16、類的成員之四:代碼塊(或初始化塊)
- 代碼塊的作用:用來初始化類、對象
- 代碼塊如果有修飾的話,只能使用static
- 分類:靜態代碼塊 VS 非靜態代碼塊
- 靜態代碼塊
- 內部可以有輸出語句
- 隨著類的加載而執行,而且只執行一次
- 作用:初始化類的信息
- 如果一個類中定義了多個靜態代碼塊,則按照聲明的先后順序執行
- 靜態代碼塊的執行要優先于非靜態代碼塊的執行
- 靜態代碼塊內只能調用靜態的屬性、靜態的方法,不能調用非靜態的結構
- 非靜態代碼塊
- 內部可以有輸出語句
- 隨著對象的創建而執行
- 每創建一個對象,就執行一次非靜態代碼塊
- 作用:可以在創建對象時,對對象的屬性等進行初始化
- 如果一個類中定義了多個非靜態代碼塊,則按照聲明的先后順序執行
- 非靜態代碼塊內可以調用靜態的屬性、靜態的方法,或非靜態的屬性、非靜態的方法
總結:由夫及子,靜態先行
屬性賦值的過程
- 默認初始化
- 顯式初始化/在代碼塊中賦值
- 構造器中初始化
- 有了對象以后,可以通過"對象.屬性"或"對象.方法"的方式,進行賦值
顯式初始化和在代碼塊中賦值的順序先后取決于代碼先后順序
17、關鍵字:final
- final可以用來修飾的結構:類、方法、變量
- final用來修飾一個類:此類不能被其他類所繼承。
- final用來修飾方法:表明此方法不可以被重寫
- final用來修飾變量:此時的"變量"就稱為是一個常量
- final修飾屬性:可以考慮賦值的位置有:顯示初始化、代碼塊中初始化、構造器中初始化
- final修飾局部變量:尤其是使用final修飾形參時,表明此形參是一個常量。當我們調用此方法時,給常量形參賦一個實參。一旦賦值以后,就只能在方法體內使用此形參,但不能進行重新賦值。
static final 用來修飾屬性:全局變量
18、關鍵字:abstract
- abstract可以用來修飾的結構:類、方法
- abstract修飾類:抽象類
- 此類不能實例化
- 抽象類中一定要有構造器,便于子類實例化時調用(涉及:子類對象實例化的全過程)
- 開發中,都會提供抽象類的子類,讓子類對象實例化,完成相關的操作
- abstract修飾方法:抽象方法
- 抽象方法只要方法的聲明,沒有方法體
- 包含抽象方法的類,一定是一個抽象類。反之,抽象類中可以沒有抽象方法的。
- 若子類重寫了父類中的所有抽象方法后,此子類才可以實例化;若子類沒有重寫父類中的所有的抽象方法,則此子類也是一個抽象類,需要使用abstract修飾
注意點:abstract不能用來修飾:屬性、構造器等結構;abstract不能用來修飾私有方法、靜態方法、final的方法、final的類
匿名對象
method(new Student()); //匿名對象Worker worker = new Worker(); method1(worker); //非匿名的類非匿名的對象method1(new Worker()); //非匿名的類匿名的對象// 創建匿名子類的非匿名對象 Person p = new Person() {@Overridepublic void eat() {System.out.println("吃東西");} }// 創建匿名子類的匿名對象 method1(new Person() {@Overridepublic void eat() {System.out.println("吃東西");} });19、關鍵字:interface
- Java中,接口和類是并列的兩個結構
- 如何定義接口,定義接口中的成員
- JDK7及以前:只能定義全局變量和抽象方法
- 全局常量:public static final的,但是書寫時,可以省略不寫
- 抽象方法:public abstract的
- JDK8:除了定義全局常量和抽象方法之外,還可以定義靜態方法、默認方法
- JDK7及以前:只能定義全局變量和抽象方法
- 接口中不能定義構造器!意味著接口不可以實例化
- Java開發中,接口通過讓類去實現(implements)的方式來使用。如果實現類覆蓋了接口中的所有抽象方法,則此實現類就可以實例化;如果實現類沒有覆蓋接口中所有的抽象方法,則此實現類仍為一個抽象類。
- Java類可以實現多個接口:彌補了Java單繼承性的局限性。格式:class AA extends BB implements CC,DD,EE
- 接口與接口之間可以繼承,而且可以多繼承。格式:interface AA extends BB,CC
- 接口的具體使用,體現了多態性
- 接口,實際上可以看做是一種規范
JDK8:除了定義全局常量和抽象方法之外,還可以定義靜態方法、默認方法
public interface CompareA {// 靜態方法public static void method1() {System.out.println("CompareA:北京");}// 默認方法public default void method2() {System.out.println("CompareA:上海");}default void method3() {System.out.println("CompareA:上海");} }class SubClass extends SuperClass implements CompareA, CompareB {public void method2() {System.out.println("SubClass:上海");}public void method3() {System.out.println("SubClass:深圳");}// 知識點5:如何在子類(或實現類)的方法中調用父類、接口中被重寫的方法public void myMethod() {method3();//調用了自己定義的重寫的方法super.method3(); //調用的是父類中聲明的//調用接口中的默認方法CompareA.super.method3();CompareB.super.method3();} }public class SubClassTest {public static void main(String[] args) {SubClass s = new SubClass();// 知識點1:接口中定義的靜態方法,只能通過接口來調用// s.method1(); // 編譯錯誤// SubClass.method1(); // 編譯錯誤CompareA.method1();// 知識點2:通過實現類的對象,可以調用接口中的默認方法。// 如果實現類重寫了接口中的默認方法,調用時,仍然調用的是重寫以后的方法。s.method2();// 知識點3:如果子類(或實現類)繼承的父類和實現的接口中聲明了同名同參數的默認方法,// 那么子類在沒有重寫此方法的情況下,默認調用的是父類中的同名同參數的方法(類優先原則)。// 知識點4:如果實現類實現了多個接口,而這多個接口中定義了同名同參數的默認方法,//那么在實現類沒有重寫此方法的情況下,會報錯——接口沖突//這就需要我們必須在實現類中重寫此方法s.method3();} }20、類的內部成員之五:內部類
-
Java中允許將一個類A聲明在另一個類B中,則類A就是內部類,類B稱為外部類
-
內部類的分類:成員內部類(靜態、非靜態) VS 局部內部類(方法內、代碼塊內、構造器內)
-
成員內部類:
- 作為外部類的成員:
- 調用外部類的結構
- 可以被static修飾
- 可以被4種不同的權限修飾
- 作為一個類:
- 類內可以定義屬性、方法、構造器等
- 可以被final修飾,表示此類不能被繼承。言外之意,不使用final就可以被繼承
- 可以被abstract修飾
- 作為外部類的成員:
-
如何實例化成員內部類的對象
- 如何在成員內部類中區分調用外部類的結構
- 開發中局部內部類的使用
- 在局部內部類的方法中(比如:show),如果要調用外部類所聲明的方法(比如:method)中的局部變量(比如:num)的話,要求此局部變量聲明為final的。
- JDK 7及之前版本:要求此局部變量顯式的聲明為final的
- JDK 8及之后版本:可以省略final的聲明
- 成員內部類和局部內部類,在編譯之后,都會生成字節碼文件。格式:
- 成員內部類:外部類$內部類名.class
- 局部內部類:外部類$數字 內部類名.class
三、異常處理
1、異常體系結構
- java.lang.Throwable
- java.lang.Error:一般不編寫針對性的代碼進行處理
- java.lang.Exception:可以進行異常的處理
- 編譯時異常(checked)
- IOException
- FileNotFoundException
- ClassNotFoundException
- IOException
- 運行時異常(unchecked)
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastException
- NumberFormatException
- InputMismatchException
- ArithmeticException
- 編譯時異常(checked)
2、異常處理的方式一:try-catch-finally
try {//可能出現異常的代碼 } catch (異常類型1 變量名1) {//處理異常的方式1 } catch (異常類型2 變量名2) {//處理異常的方式2 } catch (異常類型3 變量名3) {//處理異常的方式3 } ...... finally {//一定會執行的代碼 }try-catch-finally的說明
- 使用try將可能出現異常代碼包裝起來。在執行過程之,一旦出現異常,就會生成一個對應異常類的對象,根據此對象的類型,去catch中進行匹配
- 一旦try中的異常對象匹配到某一個catch時,就進入catch中進行異常的處理。一旦處理完成,就跳出當前的try-catch結構(在沒有寫finally的情況)。繼續執行其后的代碼
- catch中的異常類型如果沒有子父類關系,則誰聲明在上,誰聲明在下無所謂;如果滿足子父類關系,則要求子類一定聲明在父類的上面,否則報錯。
- 常用的異常對象處理的方式:
- String getMessage()
- printStackTrace()
- 在try結構中聲明的變量,在出了try結構以后,就不能再被調用
- try-catch-finally結構可以嵌套
try-catch-finally的體會
- 使用try-catch-finally處理編譯時異常,使得程序在編譯時就不再報錯,但是運行時仍可能報錯。相當于我們使用try-catch-finally將一個編譯時可能出現的異常,延遲到運行時出現。
- 開發中,由于運行時異常比較常見,所以我們通常就不針對運行時異常編寫try-catch-finally了。針對編譯時異常,我們一定要考慮異常的處理。
finally的說明
- finally是可選的
- finally中聲明的是一定會被執行的代碼。即使catch中又出現異常了,try中有return語句,catch中有return語句等情況。
- 像數據庫連接、輸入輸出流、網絡編程Socket等資源,JVM是不能自動的回收的,我們需要自己手動的進行資源的釋放。此時的資源釋放,就需要聲明在finally中。
3、異常處理的方式二:throws + 異常類型
- "throws + 異常類型"寫在方法的聲明處。指明此方法執行時,可能會拋出的異常類型。一旦當方法體執行時,出現異常,仍會在異常代碼處生成一個異常類的對象,此對象滿足throws后異常類型時,就會被拋出。異常代碼后續的代碼,就不再執行!
- try-catch-finally:真正的將異常給處理掉了;throws的方式只是將異常拋給了方法的調用者,并沒有真正將異常處理掉。
- 開發中如何選擇使用try-catch-finally還是使用throws?
- 如果父類中被重寫的方法沒有throws方式處理異常,則子類重寫的方法也不能使用throws,意味著如果子類重寫的方法中有異常,必須使用try-catch-finally方式處理
- 執行的方法a中,先后又調用了另外的幾個方法,這幾個方法是遞進關系執行的。我們建議這幾個方法使用throws的方式進行處理。而執行的方法a可以考慮是用try-catch-finally方式進行處理。
4、自定義異常類
- 繼承于現有的異常結構:RuntimeException、Exception
- 提供全局常量:serialVersionUID
- 提供重載的構造器
5、附加:Eclipse中的快捷鍵
- 補全代碼的聲明:alt + /
- 快速修飾:ctrl + l
- 批量導包:ctrl + shift + o
- 使用單行注釋:ctrl + /
- 使用多行注釋:ctrl + shift + /
- 取消多行注釋:ctrl + shift + \
- 復制指定行的代碼:ctrl + alt + down 或 ctrl + alt + up
- 刪除指定行的代碼:ctrl + d
- 上下移動代碼:alt + up 或 alt + down
- 切換到下一行代碼空位:shift + enter
- 切換到上一行代碼空位:ctrl + shift + enter
- 如何查看源碼:ctrl + 選中指定的結構 或 ctrl + shift + t
- 退回到前一個編輯的頁面:alt + left
- 進入到下一個編輯的頁面(針對與上面那條來說的):alt + right
- 光標選中指定的類,查看繼承樹結構:ctrl + t
- 復制代碼:ctrl + c
- 撤銷:ctrl + z
- 反撤銷:ctrl + y
- 剪切:ctrl + x
- 粘貼:ctrl + v
- 保存:ctrl + s
- 全選:ctrl + a
- 格式化代碼:ctrl + shift + f
- 選中數行,整體往后移動:tab
- 選中數行,整體往前移動:shift + tab
- 在當前類中,顯示類結構,并支持搜索指定的方法、屬性等:ctrl + o
- 批量修改指定的變量名、方法名、類名等:alt + shift + r
- 選中的結構的大小寫的切換:變成大寫:ctrl + shift + x
- 選中的結構的大小寫的切換:變成小寫:ctrl + shift + y
- 調出生成getter/setter/構造器等結構:alt + shift + s
- 顯示當前選擇資源(工程 or 文件)的屬性:alt + enter
- 快速查找:參照選中的Word快速定位到下一個:ctrl + k
- 關閉當前窗口:ctrl + w
- 關閉所有的窗口:ctrl + shift + w
- 查看指定的機構使用過的地方:ctrl + alt + g
- 查找與替換:ctrl + f
- 最大化當前的View:ctrl + m
- 直接定位到當前行的首位:home
- 直接定位到當前行的末位:end
Java高級編程
一、多線程
1、基本概念:程序、進程、線程
1.1、程序
- 概念:是為完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼。
1.2、進程
- 概念:程序的一次執行過程,或是正在運行的一個程序。
- 說明:進程作為資源分配的單位,系統在運行時會為每個進程分配不同的內存區域。
1.3、線程
- 概念:進程可進一步細化為線程,是一個程序內部的一條執行路徑。
- 說明:線程作為調度和執行的單位,每個線程擁有獨立的運行棧和程序計數器(pc),線程切換的開銷小。
- 每個線程,擁有自己獨立的:虛擬機棧、程序計數器
- 多個線程,共享同一個進程中的結構:方法區、堆
1.4、單核CPU和多核CPU的理解
- 單核CPU,其實是一種假的多線程,因為在一個時間單元內,也只能執行一個線程的任務。但是因為CPU時間單元特別短,因此感覺不出來。
- 如果是多核的話,才能更好的發揮多線程的效率。
- 一個Java應用程序java.exe,其實至少有三個線程:main()主線程,gc()垃圾回收線程,異常處理線程。當然如果發生異常,會影響主線程。
1.5、并行與并發
- 并行:多個CPU同時執行多個任務。
- 并發:一個CPU(采用時間片)同時執行多個任務。
1.6、使用多線程的優點
- 背景:以單個CPU為例,只使用單個線程先后完成多個任務(調用多個方法),肯定比用多個線程來完成用的時間更短,為何仍需多線程呢?
- 多線程程序的優點:
- 1、提高應用程序的響應。對圖形化界面更有意義,可增強用戶體驗。
- 2、提高計算機系統CPU的利用率
- 3、改善程序結構。將既長又復雜的進程分為多個線程,獨立運行,利于理解和修改
1.7、何時需要多線程
- 程序需要同時執行兩個或多個任務
- 程序需要實現一些需要等待的任務時,如用戶輸入、文件讀寫操作、網絡操作、搜索等。
- 需要一些后臺運行的程序時。
2、線程的創建和使用
2.1、創建多線程的方式一:繼承Thread類
- 1、創建一個繼承于Thread類的子類
- 2、重寫Thread類的run() --> 將此線程執行的操作聲明在run()中
- 3、創建Thread類的子類的對象
- 4、通過此對象調用start(),start()的作用:
- 1、啟動當前線程
- 2、調用當前線程的run()
- 注意點1:我們不能通過直接調用run()的方式啟動線程
- 注意點2:再啟動一個線程需要重新創建一個線程的對象,不可以讓已經start()的線程再start(),否則會報IllegalThreadStateException異常
2.2、Thread類的有關方法
- void start():啟動當前線程,調用當前線程的run()
- run():通常需要重寫Thread類中的此方法,將創建的線程要執行的操作聲明在此方法中
- String getName():獲取當前線程的名字
- void setName(String name):設置當前線程的名字
- static Thread currentThread():靜態方法,獲取執行當前代碼的線程
- static void yield():釋放當前CPU的執行權
- join():在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,直到線程b完全執行完以后,線程a才結束阻塞狀態
- static void sleep(long millitime):讓當前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態。
- stop():已過時。當執行此方法時,強制結束當前線程。
- boolean isAlive():判斷當前線程是否存活
2.3、線程的調度
- 調度策略
- 時間片
- 搶占式:高優先級的線程搶占CPU
- Java的調度方法
- 同優先級線程組成先進先出隊列,使用時間片策略
- 對高優先級,使用優先調度的搶占式策略
2.4、線程的優先級
- 線程的優先等級
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5 --> 默認優先級
- 涉及的方法
- getPriority():獲取線程的優先級
- setPriority(int newPriority):設置線程的優先級
- 線程創建時繼承父線程的優先級
- 低優先級只是獲得調度的概率低,并非一定是在高優先級線程之后才被調度
2.5、創建多線程的方式二:實現Runnable接口
- 1、創建一個實現了Runnable接口的類
- 2、實現類去實現Runnable中的抽象方法:run()
- 3、創建實現類的對象
- 4、將此對象作為參數傳遞到Thread類的構造器中,創建Thread類的對象
- 5、通過Thread類的對象調用start()
2.6、比較創建線程的兩種方式
- 開發中:優先選擇實現Runnable接口的方式
- 1、實現的方式沒有類的單繼承性的局限性
- 2、實現的方式更適合來處理多個線程有共享數據的情況
- 聯系:public class Thread implements Runnable
- 相同點:兩種方式都需要重寫run(),將線程要執行的邏輯聲明在run()中
3、線程的生命周期
graph LR A(新建) --> |"調用start()"| B(就緒) B --> |"獲取CPU執行權"| C(運行) C --> |"執行完run();調用線程的stop();出現Error/Exception且沒有處理"| D(死亡) C --> |"失去CPU執行權或yield()"| B C --> |"sleep(long time);join();等待同步鎖;wait();suspend()"| E(阻塞) E --> |"sleep()時間到;join()結束;獲取同步鎖;notify()/notifyAll();resume()"| B4、線程的同步
4.1、線程同步方式一:同步代碼塊
synchronized(同步監視器) {// 需要被同步的代碼 }- 1、操作共享數據的代碼,即為需要被同步的代碼
- 2、共享數據:多個線程共同操作的變量。
- 3、同步監視器,俗稱:鎖。任何一個類的對象,都可以充當鎖。
- 要求:多個線程必須要共用同一把鎖。
- 補充:在實現Runnable接口創建多線程的方式中,我們可以考慮使用this充當同步監視器
- 在繼承Thread類創建多線程的方式中,慎用this充當同步監視器,考慮使用當前類充當同步監視器
- 好處:同步的方式,解決了線程的安全問題。
- 局限性:操作同步代碼時,只能有一個線程參與,其他線程等待。相當于一個單線程的過程,效率低。
4.2、線程同步方式二:同步方法
- 同步方法仍然涉及到同步監視器,只是不需要我們顯式的聲明。
- 非靜態的同步方法,同步監視器是:this;靜態的同步方法,同步監視器是:當前類本身
4.3、設計模式:單例模式
// 餓漢式 class Bank {// 1、私有化類的構造器private Bank() {}// 2、內部創建類的對象// 4、要求此對象也必須聲明為靜態的private static Bank instance = new Bank();// 3、提供公共的靜態的方法,返回類的對象public static Bank getInstance() {return instance;} }// 懶漢式方式一:同步方法 class Order {// 1、私有化類的構造器private Order() {}// 2、聲明當前類對象,沒有初始化// 4、此對象也必須聲明為static的private static Order instance = null;// 3、聲明public、static的返回當前類對象的方法public static synchronized Order getInstance() {if (instance == null) {instance = new Order();}return instance;} }// 懶漢式方式二:同步代碼塊 class Order {// 1、私有化類的構造器private Order() {}// 2、聲明當前類對象,沒有初始化// 4、此對象也必須聲明為static的private static Order instance = null;// 3、聲明public、static的返回當前類對象的方法public static Order getInstance() {// 方式一:效率稍差// synchronized (Order.class) {// if (instance == null) {// instance = new Order();// }// return instance;// }// 方式二:效率更高if (instance == null) {synchronized (Order.class) {if (instance == null) {instance = new Order();}}}return instance;} }4.4、線程的死鎖問題
- 死鎖
- 不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖
- 出現死鎖后,不會出現異常,不會出現提示,只是所有的線程都處于阻塞狀態,無法繼續
- 解決方法
- 專門的算法、原則
- 盡量減少同步資源的定義
- 盡量避免嵌套同步
4.5、線程同步方式三:Lock(鎖)
class Window implements Runnable {private int ticket = 100;// 1、實例化ReentrantLockprivate ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {try {// 2、調用鎖定方法lock()lock.lock();if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":" + ticket);ticket--;} else {break;}} finally {// 3、調用解鎖方法:unlock()lock.unlock();}}} }- 面試題:synchronized與lock的異同?
- 相同:二者都可以解決線程安全問題
- 不同:synchronized機制在執行完相應的同步代碼以后,自動的釋放同步監視器;Lock需要手動的啟動同步(Lock()),同時結束同步也需要手動的實現(unlock())
- 優先使用順序:
- Lock --> 同步代碼塊 --> 同步方法
5、線程的通信
-
涉及到的三個方法:
- wait():一旦執行此方法,當前線程就進入阻塞狀態,并釋放同步監視器
- notify():一旦執行此方法,就會喚醒被wait()的一個線程。如果有多個線程被wait,就喚醒優先級高的線程。
- notifyAll():一旦執行此方法,就會喚醒所有被wait的線程
-
說明:
- wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中
- wait(),notify(),notifyAll()三個方法的調用者必須是同步代碼塊或同步方法中的同步監視器,否則會出現IllegalMonitorStateException異常
- wait(),notify(),notifyAll()三個方法是定義在java.lang.Object類中
-
面試題:sleep() 和 wait() 的異同?
- 相同點:一旦執行方法,都可以使得當前的線程進入阻塞狀態
- 不同點:
- 1、兩個方法聲明的位置不同:Thread類中聲明sleep(),Object類中聲明wait()
- 2、調用的要求不同:sleep()可以在任何需要的場景下調用。wait()必須使用在同步代碼塊中
- 3、關于是否釋放同步監視器:如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖
6、JDK5.0新增線程創建方式
6.1、新增方式一:實現Callable接口
- 與使用Runnable相比,Callable功能更強大些
- 相比run()方法,可以有返回值
- 方法可以拋出異常
- 支持泛型的返回值
- 需要借助FutureTask類,比如獲取返回結果
- Future接口
- 可以對具體Runnable、Callable任務的執行結果進行取消、查詢是否完成、獲取結果等。
- FutrueTask是Futrue接口的唯一實現類
- FutrueTask同時實現了Runnable,Future接口。它既可以作為Runnable被線程執行,又可以作為Future得到Callable的返回值
6.2、新增方式二:使用線程池
- 背景:經常創建和銷毀、使用量特別大的資源,比如并發情況下的線程,對性能影響很大
- 思路:提前創建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創建銷毀、實現重復利用。
- 好處:
- 提高響應速度(減少了創建新線程的時間)
- 降低資源消耗(重復利用線程池中線程,不需要每次都創建)
- 便于線程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大線程數
- keepAliveTime:線程沒有任務時最多保持多長時間后會終止
- ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor
- void execute(Runnable command):執行任務/命令,沒有返回值,一般用來執行Runnable
- Future submit(Callable task):執行任務,有返回值,一般用來執行Callable
- void shutdown():關閉連接池
- Executors:工具類、線程池的工廠類,用于創建并返回不同類型的線程池
- Executors.newCachedThreadPool():創建一個可根據需要創建新線程的線程池
- Executors.newFixedThreadPool(n):創建一個可重用固定線程數的線程池
- Executors.newSingleThreadPool():創建一個只有一個線程的線程池
- Executors.newScheduledThreadPool(n):創建一個線程池,它可安排在給定延遲后運行命令或者定期地執行
7、附加:關于鎖的操作
7.1、釋放鎖的操作
- 當前線程的同步方法、同步代碼塊執行結束
- 當前線程在同步代碼塊、同步方法中遇到break、return終止了該代碼塊、該方法的繼續執行。
- 當前線程在同步代碼塊、同步方法中出現了未處理的Error或Exception,導致異常結束
- 當前線程在同步代碼塊、同步方法中執行了線程對象的wait()方法,當前線程暫停,并釋放鎖
7.2、不會釋放鎖的操作
- 線程執行同步代碼塊或同步方法時,程序調用Thread.sleep()、Thread.yield()方法暫停當前線程的執行
- 線程執行同步代碼塊時,其他線程調用了該線程的suspend()方法將該線程掛起,該線程不會釋放鎖(同步監視器)
- 應盡量避免使用suspend()和resume()來控制線程
二、Java常用類
1、字符串相關的類
1.1、字符串相關的類:String
- String聲明為final的,不可被繼承
- String實現了Serializable接口:表示字符串是支持序列化的;實現了Comparable接口:表示String可以比較大小
- String內部定義了final char[] value用于存儲字符串數據
- String:代表不可變的字符序列。簡稱:不可變性。
- 1、當對字符串重新賦值時,需要重新指定內存區域賦值,不能使用原有的value進行賦值。
- 2、當對現有的字符串進行連接操作時,也需要重新指定內存區域賦值,不能使用原有的value進行賦值
- 3、當調用String的replace()方法修改指定字符或字符串時,也需要重新指定內存區域賦值,不能使用原有的value進行賦值
- 通過字面量的方式(區別于new)給一個字符串賦值,此時的字符串聲明在字符串常量池中
- 字符串常量池中是不會存儲相同內容的字符串的
- 常量與常量的拼接結果在常量池。且常量池中不會存在相同內容的常量。
- 只要其中有一個是變量,結果就在堆中。
- 如果拼接的結果是調用intern()方法,返回值就在常量池中
1.2、字符串相關的類:String常用方法
- int length():返回字符串的長度:return value.length
- char charAt(int index):返回某索引處的字符return value[index]
- boolean isEmpty():判斷是否是空字符串:return value.length == 0
- String toLowerCase():使用默認語言環境,將String中的所有字符轉換為小寫
- String toUpperCase():使用默認語言環境,將String中的所有字符轉換為大寫
- String trim():返回字符串的副本,忽略首部空白和尾部空白
- boolean equals(Object obj):比較字符串的內容是否相同
- boolean equalsIgnoreCase(String anotherString):與equals方法類似,忽略大小寫
- String concat(String str):將指定字符串連接到此字符串的結尾。等價于用"+"
- int compareTo(String anotherString):比較兩個字符串的大小
- String substring(int beginIndex):返回一個新的字符串,它是此字符串從beginIndex開始截取到最后的一個子字符串
- String substring(int beginIndex, int endIndex):返回一個新的字符串,它是此字符串從beginIndex開始截取到endIndex(不包含)的一個子字符串
- boolean endsWith(String suffix):測試此字符串是否以指定的后綴結束
- boolean startsWith(String prefix):測試此字符串是否以指定的前綴開始
- boolean startsWith(String prefix, int toffset):測試此字符串從指定索引開始的子字符串是否以指定的前綴開始
- boolean contains(CharSequence s):當且僅當此字符串包含指定的char值序列時,返回true
- int indexOf(String str):返回指定子字符串在此字符串中第一次出現處的索引
- int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出現處的索引,從指定的索引開始
- int lastIndexOf(String str):返回指定子字符串在此字符串中最右邊出現處的索引
- int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出現處的索引,從指定的索引開始反向搜索
- String replace(char oldChar, char new Char):返回一個新的字符串,它是通過用newChar替換此字符串中出現的所有oldChar得到的
- String replace(CharSequence target, CharSequence replacement):使用指定的字面值替換序列替換此字符串所有匹配字面值目標序列的子字符串
- String replaceAll(String regex, String replacement):使用給定的replacement替換此字符串所有匹配給定的正則表達式的子字符串
- String replaceFirst(String regex, String replacement):使用給定的replacement替換此字符串匹配給定的正則表達式的第一個子字符串
- boolean matches(String regex):告知此字符串是否匹配給定的正則表達式
- String[] split(String regex):根據給定正則表達式的匹配拆分此字符串
- String[] split(String regex, int limit):根據匹配給定的正則表達式來拆分此字符串,最多不超過limit個,如果超過了,剩下的全部都放到最后一個元素中
1.3、String與基本數據類型、包裝類之間的轉換
- String --> 基本數據類型、包裝類:調用包裝類的靜態方法:parseXxx(str)
- 基本數據類型、包裝類 --> String:調用String重載的valueOf(xxx)
1.4、String與char[]之間的轉換
- String --> char[]:調用String的toCharArray()
- char[] --> String:調用String的構造器
1.5、String與byte[]之間的轉換
- String --> byte[]:調用String的getBytes()
- byte[] --> String:調用String的構造器
1.6、String、StringBuffer、StringBuilder三者的異同?
- String:不可變的字符序列;底層使用final char[]存儲
- StringBuffer:可變的字符序列;線程安全的,效率低;底層使用char[]存儲
- StringBuilder:可變的字符序列;JDK 5.0新增的,線程不安全的,效率高;底層使用char[]存儲
1.7、StringBuffer源碼分析
- 擴容問題:如果要添加的數據底層數組盛不下了,那就需要擴容底層的數組。默認情況下,擴容為原來容量的2倍+2,同時將原有數組中的元素復制到新的數組中
- 指導意義:開發中建議大家使用:StringBuffer(int capacity) 或 StringBuilder(int capacity)
1.8、StringBuffer類的常用方法
- StringBuffer append(xxx):提供了很多的append()方法,用于進行字符串拼接
- StringBuffer delete(int start, int end):刪除指定位置的內容
- StringBuffer replace(int start, int end, String str):把[start, end)位置替換為str
- StringBuffer insert(int offset, xxx):在指定位置插入xxx
- StringBuffer reverse():把當前字符序列逆轉
1.9、對比String、StringBuffer、StringBuilder三者的效率
- 從高到低排列:StringBuilder > StringBuffer > String
1.10、String與StringBuffer、StringBuilder之間的轉換
- String --> StringBufferr、StringBuilder:調用StringBuffer、StringBuilder構造器
- StringBufferr、StringBuilder --> String:
- 調用String構造器
- StringBuffer、StringBuilder的toString()
2、JDK 8之前的日期時間API
2.1、java.lang.System類
- System類提供的public static long currentTimeMillis()用來返回當前時間與1970年1月1日0時0分0秒之間以毫秒為單位的時間差。
2.2、java.util.Date類
- 表示特定的瞬間,精確到毫秒
- 構造器:
- Date():使用無參構造器創建的對象可以獲取本地當前時間
- Date(long date)
- 常用方法
- getTime():返回自1970年1月1日 00:00:00 GMT 以來此Date對象表示的毫秒數
- toString():把此Date對象轉換為以下的形式的String:dow mon dd hh:mm:ss zzz yyyy
2.3、java.text.SimpleDateFormat類
- Date類的API不易于國際化,大部分被廢棄了,java.text.SimpleDateFormat類是一個不與語言環境有關的方式來格式化和解析日期的具體類
- 格式化:
- SimpleDateFormat():默認的模式和語言環境創建對象
- public SimpleDateFormat(String pattern):該構造方法可以用參數pattern指定的格式創建一個對象
- public String format(Date date):格式化時間對象date
- 解析:
- public Date parse(String source):從給定字符串的開始解析文本,以生成一個日期
2.4、java.util.Calendar類
- Calendar是一個抽象基類,主要用于完成日期字段之間相互操作的功能
- 獲取Calendar實例的方法
- 使用Calendar.getInstance()方法
- 調用它的子類GregorianCalendar的構造器
- 一個Calendar的實例是系統時間的抽象表示,通過get(int field)方法來取得想要的時間信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY、MINUTE、SECOND
- public void set(int field, int value)
- public void add(int field, int amount)
- public final Date getTime()
- public final void setTime(Date date)
- 注意
- 獲取月份時:一月是0,二月是1,以此類推,12月是11
- 獲取星期時:周日是1,周一是2,以此類推,周六是7
3、JDK 8中新日期時間API
3.1、新日期時間API出現的原因
- 可變性:像日期和時間這樣的類應該是不可變的
- 偏移性:Date中的年份是從1900開始的,而月份都是從0開始
- 格式化:格式化只對Date有用,Calender則不行
- 此外,它們都不是線程安全的;不能處理閏秒等
3.2、LocalDate、LocalTime、LocalDateTime類
- 它們的實例是不可變的對象,分別表示使用ISO-8601日歷系統的日期、時間、日期和時間。
- 創建方法:
- now() / now(ZoneId zone):靜態方法,根據當前時間創建對象/指定時區的對象
- of():靜態方法,根據指定日期/時間創建對象
- get類的方法:
- getDayOfMonth()/getDayOfYear():獲得月份天數(1-31)/獲得年份天數(1-366)
- getDayOfWeek():獲得星期幾(返回一個DayOfWeek枚舉值)
- getMonth():獲得月份,返回一個Month枚舉值
- getMonthValue()/getYear():獲得月份(1-12)/獲得年份
- getHour()/getMinute()/getSecond():獲得當前對象對應的小時、分鐘、秒
- set類的方法:
- withDayOfMonth()/withDayOfYear()/withMonth()/withYear():將月份天數、年份天數、月份、年份修改為指定的值并返回新的對象
- 操作類的方法:
- plusDays()/plusWeeks()/plusMonths()/plusYears()/plusHours():向當前對象添加幾天、幾周、幾個月、幾年、幾小時
- minusMonths()/minusWeeks()/minusDays()/minusYears()/minusHours():從當前對象減去幾月、幾周、幾天、幾年、幾小時
3.3、瞬時:Instant
- 時間線上的一個瞬時點,同樣的,在Java中也是從1970年開始,但以毫秒為單位
- 常用方法:
- now():靜態方法,返回默認UTC時區的Instant類的對象
- ofEpochMilli(long epochMilli):靜態方法,返回在1970-01-01 00:00:00基礎上加上指定毫秒數之后的Instant類的對象
- atOffset(ZoneOffset offset):結合即時的偏移來創建一個OffsetDateTime
- toEpochMilli():返回1970-01-01 00:00:00到當前時間的毫秒數,即為時間戳
3.4、java.time.format.DateTimeFormatter類
- 預定義的標準格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
- 本地化相關的格式。如ofLocalizedDateTime(FormatStyle.LONG)
- 自定義的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss E”)
- 常用方法:
- ofPattern(String pattern):靜態方法,返回一個指定字符串格式的DateTimeFormatter
- format(TemporalAccessor t):格式化一個日期、時間,返回字符串
- parse(CharSequence test):將指定格式的字符序列解析為一個日期、時間
4、Java比較器
4.1、方式一:自然排列:java.lang.Comparable
- Comparable接口強行對實現它的每個類的對象進行整體排序。這種排序被稱為類的自然排序
- 實現Comparable的類必須實現compareTo(Object obj)方法,兩個對象即通過compareTo(Object obj)方法的返回值來比較大小。
- 如果當前對象this大于形參對象obj,則返回正整數
- 如果當前對象this小于形參對象obj,則返回負整數
- 如果當前對象this等于形參對象obj,則返回零
- 實現Comparable接口的對象列表(和數組)可以通過Collections.sort或Arrays.sort進行自動排列。
4.2、方式二:定制排序:java.util.Comparator
- 當元素的類型沒有實現java.lang.Comparable接口而又不方便修改代碼,或者實現了java.lang.Comparable接口的排序規則不適合當前的操作,那么可以考慮使用Comparator的對象來排序
- 重寫compare(Object o1, Object o2)方法,比較o1和o2的大小
- 如果方法返回正整數,表示o1大于o2
- 如果方法返回0,表示相等
- 返回負整數,表示o1小于o2
- 可以將Comparator傳遞給sort方法(如Collections.sort或Arrays.sort),從而允許在排序順序上實現精確控制
5、System類
- System類代表系統,系統級的很多屬性和控制方法都放置在該類的內部。
- 成員變量:
- in:標準輸入流(鍵盤輸入)
- out:標準輸出流(顯示器)
- err:標準錯誤輸出流(顯示器)
- 成員方法:
- native long currentTimeMillis():該方法的作用是返回當前的計算機時間,時間的表達格式為當前計算機時間和GMT時間(格林威治時間)1970年1月1日0時0分0秒所差的毫秒數。
- void exit(int status):該方法的作用是退出程序。其中status的值為0代表正常退出,非零代表異常退出。
- void gc():該方法的作用是請求系統進行垃圾回收。至于系統是否立刻回收,則取決于系統中垃圾回收算法的實現以及系統執行時的情況。
- String getProperty(String key):該方法的作用是獲得系統中屬性名為key的屬性對應的值。
- java.version:java運行時環境版本
- java.home:java安裝目錄
- os.name:操作系統的名稱
- os.version:操作系統的版本
- user.name:用戶的賬戶名稱
- user.home:用戶的主目錄
- user.dir:用戶的當前工作目錄
6、Math類
- abs:絕對值
- acos,asin,atan,cos,sin,tan:三角函數
- sqrt:平方根
- pow(double a, double b):a的b次冪
- log:自然對數
- exp:e為底指數
- max(double a, double b)
- min(double a, double b)
- random():返回0.0到1.0的隨機數
- long round(double a):double型數據a轉換為long型(四舍五入)
- toDegrees(double angrad):弧度–>角度
- toRadians(double angdeg):角度–>弧度
7、BigInteger與BigDecimal
7.1 BigInteger類
- java.math包的BigInteger可以表示不可變的任意精度的整數
- 構造器:
- BigInteger(String val):根據字符串構建BigInteger對象
- 常用方法:
- public BigInteger abs():返回此BigInteger的絕對值的BigInteger
- BigInteger add(BigInteger val):返回其值為(this+val)的BigInteger
- BigInteger subtract(BigInteger val):返回其值為(this-val)的BigInteger
- BigInteger multiply(BigInteger val):返回其值為(this*val)的BigInteger
- BigInteger divide(BigInteger val):返回其值為(this/val)的BigInteger。整數相除只保留整數部分
- BigInteger remainder(BigInteger val):返回其值為(this%val)的BigInteger
- BigInteger[] divideAndRemainder(BigInteger val):返回包含(this/val)后跟(this%val)的兩個BigInteger的數組
- BigInteger pow(int exponent):返回其值為(this的exponent次方)的BigInteger
7.2 BigDecimal類
- BigDecimal類支持不可變的、任意精度的有符號十進制定點數
- 構造器:
- public BigDecimal(double val)
- public BigDecimal(String val)
- 常用方法:
- public BigDecimal add(BigDecimal augend)
- public BigDecimal subtract(BigDecimal subtrahend)
- public BigDecimal multiply(BigDecimal multiplicand)
- public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
三、枚舉類&注解
1、枚舉類的使用
1.1、枚舉類的使用
- 枚舉類的理解:類的對象只有有限個,確定的。我們稱此類為枚舉類
- 當需要定義一組常量時,強烈建議使用枚舉類
- 如果枚舉類中只有一個對象,則可以作為單例模式的實現方式
1.2、如何定義枚舉類
- 方式一:jdk 5.0之前,自定義枚舉類
- 方式二:jdk 5.0,可以使用enum關鍵字定義枚舉類
- 定義的枚舉類默認繼承于java.lang.Enum類
1.3、Enum類的主要方法
- value():返回枚舉類型的對象數組。該方法可以很方便地遍歷所有的枚舉類
- valueOf(String str):可以把一個字符串轉為對應的枚舉類對象。要求字符串必須是枚舉類對象的"名字"。如不是,會有運行時異常:illegalArgumentException
- toString():返回當前枚舉類對象常量的名稱
2、注解的使用
2.1、注解的概述
- 從JDK 5.0開始,Java增加了對元數據的支持,也就是Annotation(注解)
- Annotation其實就是代碼里的特殊標記,這些標記可以在編譯、類加載、運行時被讀取,并執行相應的處理。通過使用Annotation,程序員可以在不改變原有邏輯的情況下,在源文件中嵌入一些補充信息。
- 框架 = 注解 + 反射 + 設計模式
2.2、常見的Annotation示例
- 示例一:生成文檔相關的注解
- 示例二:在編譯時進行格式檢查(JDK內置的三個基本注解)
- @Override:限定重寫父類方法,該注解只能用于方法
- @Deprecated:用于表示所修飾的元素(類,方法等)已過時。通常是因為所修飾的結構危險或存在更好的選擇
- @SuppressWarnings:抑制編譯器警告
- 示例三:跟蹤代碼依賴性,實現代替配置文件功能
2.3、如何自定義注解:參照@SuppressWarnings定義
- 注解聲明為:@interface
- 內部定義成員,通常使用value表示
- 可以指定成員的默認值,使用default定義
- 如果自定義注解沒有成員,表明是一個標識作用
- 如果注解有成員,在使用注解時,需要指明成員的值
- 自定義注解必須配上注解的信息處理流程(使用反射)才有意義
- 自定義注解通常都會指明兩個元注解:Retention、Target
2.4、JDK提供的4種元注解
- 元注解:對現有的注解進行解釋說明的注解
- Retention:指定所修飾的Annotation的生命周期:SOURCE\CLASS(默認值)\RUNTIME,只有聲明為RUNTIME生命周期的注解,才能通過反射獲取
- Target:用于指定被修飾的Annotation能用于修飾哪些程序元素
- Documented:表示所修飾的注解在被javadoc解析時,保留下來
- Inherited:被它修飾的Annotation將具有繼承性
2.5、JDK 8中注解的新特性:可重復注解、類型注解
- 可重復注解:
- 在MyAnnotation上聲明@Repeatable,成員值為MyAnnotations.class
- MyAnnotation的Target和Retention等元注解與MyAnnotations相同
- 類型注解:
- ElementType.TYPE_PARAMETER:表示該注解能寫在類型變量的聲明語句中(如:泛型聲明)
- ElementType.TYPE_USE:表示該注解能寫在使用類型的任何語句中
四、Java集合
1、Java集合框架概述
1.1、集合框架的概述
- 集合、數組都是對多個數據進行存儲操作的結構,簡稱Java容器。
- 說明:此時的存儲,主要指的是內存層面的存儲,不涉及到持久化的存儲
- 數組在存儲多個數據方面的特點:
- 一旦初始化以后,其長度就確定了
- 數組一旦定義好,其元素的類型也就確定了。我們也就只能操作指定類型的數據了。比如:String[] arr;int[] arr1;Object[] arr2;
- 數組在存儲多個數據方面的缺點:
- 一旦初始化以后,其長度就不可修改
- 數組中提供的方法非常有限,對于添加、刪除、插入數據等操作,非常不便,同時效率不高。
- 獲取數組中實際元素的個數的需求,數組沒有現成的屬性或方法可用。
- 數組存儲數據的特點:有序、可重復。對于無序、不可重復的需求,不能滿足。
1.2、集合框架
- Coolection接口:單列集合,用來存儲一個一個的對象
- List接口:存儲有序的、可重復的數據
- ArrrayList、LinkedList、Vector
- Set接口:存儲無序的、不可重復的數據
- HashSet、LinkedHashSet、TreeSet
- List接口:存儲有序的、可重復的數據
- Map接口:雙列集合,用來存儲一對(key-value)一對的數據
- HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
2、Collection接口方法
- 添加
- add(Object obj)
- addAll(Conllection coll)
- 獲取有效元素的個數
- int size()
- 清空集合
- void clear()
- 是否是空集合
- boolean isEmpty()
- 是否包含某個元素
- boolean contains(Object obj):是通過元素的equals方法來判斷是否是同一個對象
- boolean containsAll(Collection c):也是調用元素的equals方法來比較的。拿兩個集合的元素挨個比較。
- 刪除
- boolean remove(Object obj):通過元素的equals方法判斷是否是要刪除的那個元素。只會刪除找到的第一個元素
- boolean removeAll(Collection coll):取當前集合的差集
- 取兩個集合的交集
- boolean retainAll(Collection c):把交集的結果存在當前集合中,不影響c
- 集合是否相等
- boolean equals(Object obj)
- 轉成對象數組
- Object[] toArray()
- 獲取集合對象的哈希值
- hashCode()
- 遍歷
- iterator():返回迭代器對象,用于集合遍歷
拓展:數組 --> 集合:調用Arrays類的靜態方法asList()
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"}); System.out.println(list); // Arrays.asList會優先將其當做一個元素,而不會自動裝箱當做是兩個元素 List arr1 = Arrays.asList(new int[]{123, 456}); System.out.println(arr1.size()); // 1List arr2 = Arrays.asList(new Integer[]{123, 456}); System.out.println(arr2.size());// 2 // 推薦寫法:避免錯誤 List arr3 = Arrays.asList(123, 456); System.out.println(arr3.size()); // 23、Iterator迭代器接口
3.1、Iterator迭代器概述
- Iterator對象稱為迭代器,主要用于遍歷Collection集合中的元素
- GOF給迭代器模式的定義為:提供一種方法訪問一個容器(container)對象中各個元素,而又不需暴露該對象的內部細節。迭代器模式,就是為容器而生。
3.1、Iterator的使用
- 內部的方法:hasNext()和next()
- 集合對象每次調用iterator()方法都得到一個全新的迭代器對象
- 內部定義了remove(),可以在遍歷的時候,刪除集合中的元素。如果還未調用next()或在上一次調用next方法之后已經調用了remove方法,再調用remove會報IllegalStateException
foreach遍歷集合時,內部仍然調用了迭代器
4、Conllection子接口一:List
4.1、List框架
- Collection接口:單列集合,用來存儲一個一個的對象
- List接口:存儲有序的、可重復的數據
- ArrayList:作為List接口的主要實現類:線程不安全,效率高;底層使用Object[] elementData存儲
- LinkedList:對于頻繁的插入、刪除操作,使用此類效率比ArrayList高;底層使用雙向鏈表存儲
- Vector:作為List接口的古老實現類;線程安全的,效率低;底層使用Object[] elementData存儲
- List接口:存儲有序的、可重復的數據
4.2、ArrayList的源碼分析
- ArrayList的源碼分析
- LinkedList的源碼分析
- Vector的源碼分析:JDK 7和JDK 8中通過Vector()構造器創建對象時,底層都創建了長度為10的數組,在擴容方面,默認擴容為原來數組長度的2倍。
4.3、面試題:ArrayList、LinkedList、Vector三者的異同?
- 同:三個類都是實現了List接口,存儲數據的特點相同:存儲有序的、可重復的數據
- 不同:見4.1、List框架
4.4、List接口方法
- List除了從Collection集合繼承的方法外,List集合里添加了一些根據索引來操作集合元素的方法
- void add(int inde, Object ele):在index位置插入ele元素
- boolean addAll(int index, Collection eles):從index位置開始將eles中所有元素添加進來
- Object get(int index):獲取指定index位置的元素
- int indexOf(Object obj):返回obj在集合中首次出現的位置
- int lastIndexOf(Object obj):返回obj在當前集合中末次出現的位置
- Object remove(int index):移除指定index位置的元素,并返回此元素
- Object set(int index, Object ele):設置指定index位置的元素為ele
- List subList(int fromIndex, int toIndex):返回從fromIndex到toIndex位置的子集合
5、Conllection子接口二:Set
5.1、Set接口的框架
- Collection接口:單列集合,用來存儲一個一個的對象
- Set接口:存儲無序的,不可重復的數據
- HashSet:作為Set接口的主要實現類:線程不安全;可以存儲null值
- LinkedHashSet:作為HashSet的子類;遍歷其內部數據時,可以按照添加的順序進行遍歷。對于頻繁的遍歷操作,LinkedHashSet效率高于HashSet
- TreeSet:可以按照添加對象的指定屬性,進行排序
- HashSet:作為Set接口的主要實現類:線程不安全;可以存儲null值
- Set接口:存儲無序的,不可重復的數據
Set接口中沒有額外定義新的方法,使用的都是Collection中聲明過的方法。
5.2、無序性與不可重復性(HashSet為例)
- 無序性:不等于隨機性。存儲的數據在底層數組中并非按照數組索引的順序添加,而是根據數據的哈希值決定的。
- 不可重復性:保證添加的元素按照equals()判斷時,不能返回true。即:相同的元素只能添加一個。
- 前提是哈希值相同,才會進行equals()判斷
5.3、HashSet的源碼分析
- 我們向HashSet中添加元素a,首先調用元素a所在類的hashCode()方法,計算元素a的哈希值,此哈希值接著通過某種算法計算出在HashSet底層數組中的存放位置(即為:索引位置),判斷數組此位置上是否已經有元素:
- 如果此位置上沒有其他元素,則元素a添加成功。 —> 情況1
- 如果此位置上有其他元素b(或以鏈表形式存在的多個元素),則比較元素a與元素b的hash值:
- 如果hash值不相同,則元素a添加成功。 —> 情況2
- 如果hash值相同,進而需要調用元素a所在類的equals()方法:
- equals()返回true,元素a添加失敗
- equals()返回false,則元素a添加成功。 —> 情況3
- 對于添加成功的情況2和情況3而言:元素a與已經存在指定索引位置上數據以鏈表的方式存儲。
- JDK 7:元素a放在數組中,指向原來的元素。
- JDK 8:原來的元素在數組中,指向元素a
- 總結:七上八下
- HashSet底層:數組+鏈表的結構
要求:向Set中添加的數據,其所在的類一定要重寫hashCode()和equals()。
要求:重寫的hashCode()和equals()盡可能保持一致性:相等的對象必須具有相等的散列碼。重寫兩個方法的小技巧:對象中用作equals()方法比較的Field,都應該用來計算hashCode值
5.4、為什么用Eclipse/IDEA重寫hashCode方法,有31這個數字?
- 選擇系數的時候要選擇盡量大的系數。因為如果計算出來的hash地址越大,所謂的“沖突”就越少,查找起來效率也會提高。(減少沖突)
- 并且31只占用了5bits,相乘造成數據溢出的概率較少。
- 31可以由i*31==(i<<5)-1表示,現在很多虛擬機里面都有做相關優化。(提高算法效率)
- 31是一個素數,素數作用就是如果我用一個數字來乘以這個素數,那么最終出來的結果只能被素數本身和被乘數還有1來整除!(減少沖突)
5.5、LinkedHashSet的使用
- LinkedHashSet作為HashSet的子類,在添加數據的同時,每個數據還維護了兩個引用,記錄此數據前一個數據和后一個數據
- 優點:對于頻繁的遍歷操作,LinkedHashSet效率高于HashSet
5.5、TreeSet的使用
- 向TreeSet中添加數據,要求是相同的類的對象
- 兩種排列方法:自然排序(實現Comparable接口)和定制排序(Comparator)
- 自然排序中,比較兩個對象是否相同的標準為:compareTo()返回0,不再是equals()
- 定制排序中,比較兩個對象是否相同的標準為:compare()返回0,不再是equals()
6、Map接口
6.1、Map接口的框架
- Map:雙列數據,存儲key-value對的數據
- HashMap:作為Map的主要實現類;線程不安全的,效率高;可以存儲null的key和value
- LinkedHashMap:保證在遍歷map元素時,可以按照添加的順序實現遍歷。原因:在原有的HashMap底層結構基礎上,添加了一對指針,指向前一個和后一個元素,對于頻繁的遍歷操作,此類執行效率高于HashMap
- TreeMap:保證按照添加的key-value對進行排序;實現排序遍歷。此時考慮key的自然排序或定制排序。底層使用紅黑樹
- Hashtable:作為古老的實現類;線程安全的,效率低;不能存儲null的key和value
- Properties:常用來處理配置文件。key和value都是String類型
- HashMap:作為Map的主要實現類;線程不安全的,效率高;可以存儲null的key和value
HashMap的底層:數組+鏈表(JDK 7及之前)或數組+鏈表+紅黑樹(JDK 8)
6.2、Map結構的理解(以HashMap為例)
- Map中的key:無序的、不可重復的,使用Set存儲所有的key --> key所在的類要重寫equals()和hashCode()
- Map中的value:無序的、可重復的,使用Collection存儲所有的value --> value所在的類型要重寫equals()
- 一個鍵值對:key-value構成了一個Entry對象
- Map中的entry:無序的、不可重復的,使用Set存儲所有的entry
6.3、HashMap的底層實現原理?(以JDK 7為例)
/*** HashMap map = new HashMap();* 在實例化以后,底層創建了長度是16的一對數組Entry[] table* ...可能已經執行過多次put...* map.put(key1, value1):* 首先,調用key1所在類的hashCode()計算key1哈希值,此哈希值經過某種算法計算以后,得到在Entry數組中的存放位置。* 如果此位置上的數據為空,此時的key1-value1添加成功。 ---> 情況1* 如果此位置上的數據不為空,(意味著此位置上存在一個或多個數據(以鏈表形式)),比較key1和已經存在的一個或多個數據的哈希值:* 如果key1的哈希值與已經存在的數據的哈希值都不相同,此時key1-value1添加成功。 ---> 情況2* 如果key1的哈希值和已經存在的某一個數據(key2-value2)的哈希值相同,繼續比較:調用key1所在類的equals(key2)* 如果equals()返回false:此時key1-value1添加成功。---> 情況3* 如果equals()返回true:使用value1替換value2** 補充:關于情況2和情況3:此時key1-value1和原來的數據以鏈表的方法存儲。** 在不斷的添加過程中,會涉及到擴容問題,當超過臨界值(且要存放的位置非空)時,擴容。默認的擴容方式:擴容為原來容量的2倍,并將原有的數據復制過來。*/- JDK 8相較于JDK 7在底層實現方面的不同:
- 1、new HashMap():底層沒有創建一個長度為16的數組
- 2、JDK 8底層的數組是:Node[],而非Entry[]
- 3、首次調用put()方法時,底層創建長度為16的數組
- 4、JDK 7的底層結構只有:數組+鏈表。JDK 8中底層結構:數組+鏈表+紅黑樹。
- 形成鏈表時,依舊符合“七上八下”(JDK 7:新的元素指向舊的元素。JDK 8:舊的元素指向新的元素)
- 當數組的某一個索引位置上的元素以鏈表形式存在的數據個數 > 8 且當前數組的長度 > 64時,此時此索引位置上的所有數據改為使用紅黑樹存儲。
6.4、HashMap源碼中的重要常量
- DEFAULT_INITIAL_CAPACITY:HashMap的默認值,16
- DEFAULT_LOAD_FACTOR:HashMap的默認加載因子:0.75
- threahold:擴容的臨界值,=容器*填充因子:16 * 0.75 => 12
- TREEIFY_THRESHOLD:Bucket中鏈表長度大于該默認值,轉化為紅黑樹:8
- MIN_TREEIFY_CAPACITY:桶中的Node被樹化時最小的hash表容量:64
面試題:負載因子值的大小,對HashMap有什么影響?
1、負載因子的大小決定了HashMap的數據密度
2、負載因子越大密度越大,發生碰撞的幾率越高,數組中的鏈表越容易長,造成查詢或插入時的比較次數增多,性能會下降。
3、負載因子越小,就越容易觸發擴容,數據密度也越小,意味著發生碰撞的幾率越小,數組中的鏈表也就越短,查詢和插入時比較的次數也越小,性能會更高。但是會浪費一定的內容空間。而且經常擴容也會影響性能,建議初始化預設大一點的空間
4、按照其他語言的參考及研究經驗,會考慮將負載因子設置在0.7~0.75,此時平均檢索長度接近于常數
6.5、LinkedHashMap的底層原理
- LinkedHashMap底層使用的結構與HashMap相同,因為LinkedHashMap繼承于HashMap
- 區別就在于:LinkedHashMap內部提供了Entry,替換HashMap中的Node
- HashMap中的內部類:Node
- LinkedHashMap中的內部類:Entry
6.5、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集合
7、Collections工具類
- Collections是一個操作Set、List和Map等集合的工具類
- Collections中提供了一系列靜態的方法對集合元素進行排序、查詢和修改等操作,還提供了對集合對象設置不可變、對集合對象實現同步控制等方法
- 排序操作:(均為static方法)
- reverse(List):反轉List中元素的順序
- shuffle(List):對List集合元素進行隨機排序
- sort(List):根據元素的自然順序對指定List集合元素按升序排序
- sort(List, Comparator):根據指定的Comparator產生的順序對List集合元素進行排序
- swap(List, int, int):將指定list集合中的i處元素和j處元素進行交換
- 查找、替換
- Object max(Collection):根據元素的自然順序,返回給定集合中的最大值
- Object max(Collection, Comparator):根據Comparator指定的順序,返回給定集合中的最大元素
- Object min(Collection)
- Object min(Collection, Comparator)
- int frequency(Collection, Object):返回指定集合中指定元素的出現次數
- void copy(List dest, List src):將src中的內容復制到dest中
- boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替換List對象的所有舊值
- 同步控制
- synchronizedXxx():將指定集合包裝成線程同步的集合,從而可以解決多線程并發訪問集合時的線程安全問題
- synchronizedList(List list)
- synchronizedMap(Map map)
五、泛型
1、為什么要有泛型
1.1、泛型的概念
- 所謂泛型,就是允許在定義類、接口時通過一個標識表示類中某個屬性的類型或者是某個方法的返回值及參數類型。這個類型參數將在使用時(例如,繼承或實現這個接口,用這個類型聲明變量、創建對象時)確定(即傳入實際的類型參數,也稱為類型實參)。
1.2、泛型的引入背景
- 集合容器類在設計階段/聲明階段不能確定這個容器到底實際存的是什么類型的對象,所以在JDK1.5之前只能把元素類型設計為Object,JDK1.5之后使用泛型來解決。因為這個時候除了元素的類型不確定,其他的部分是確定的,例如關于這個元素如何保存,如何管理等是確定的,因此此時把元素的類型設計成一個參數,這個類型參數叫做泛型。
2、在集合中使用泛型
- 集合接口或集合類在JDK5.0時都修改為帶泛型的結構
- 在實例化集合類時,可以指明具體的泛型類型
- 指明完以后,在集合類或接口中凡是定義類或接口時,內部結構(比如:方法、構造器、屬性等)使用到類的泛型的位置,都指定為實例化的泛型類型
- 比如:add(E e) —> 實例化以后:add(Integer e)
- 注意點:泛型的類型必須是類,不能是基本數據類型。需要用到基本數據類型的位置,拿包裝類替換
- 如果實例化時,沒有指明泛型的類型,默認類型為java.lang.Object類型
3、自定義泛型結構
- 泛型類可能有多個參數,此時應將多個參數一起放在尖括號內。比如:<E1,E2,E3>
- 泛型類的構造器如下:public GenericClass(){}
- 實例化后,操作原來泛型位置的結構必須與指定的泛型類型一致。
- 泛型不同的引用不能相互賦值
- 盡管在編譯時ArrayList<String>和ArrayList<Interger>是兩種類型,但是,在運行時只有一個ArrayList被加載到JVM中。
- 泛型如果不指定,將被擦除,泛型對應的類型均按照Object處理,但不等價于Object。經驗:泛型要使用一路都用。要不用,一路都不要用。
- 如果泛型結構是一個接口或抽象類,則不可以創建泛型類的對象
- JDK1.7,泛型的簡化操作:ArrayList<Fruit> flist = new ArrayList<>();
- 泛型的指定中不能使用基本數據類型,可以使用包裝類替換
- 在類/接口上聲明的泛型,在本類或本接口中即代表某種類型,可以作為非靜態屬性的類型、非靜態方法的參數類型、非靜態方法的返回值類型。但在靜態方法中不能使用類的泛型。
- 異常類不能是泛型的
- 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
- 父類有泛型,子類可以選擇保留泛型也可以選擇指定泛型類型:
- 子類不保留父類的泛型:按需實現
- 沒有類型,擦除
- 具體類型
- 子類保留父類的泛型:泛型子類
- 全部保留
- 部分保留
- 子類不保留父類的泛型:按需實現
4、泛型在繼承上的體現
- 雖然類A是類B的父類,但是G<A>和G<B>二者不具備子父類關系,二者是并列關系
- 補充:類A是類B的父類,A<G>是B<G>的父類
5、通配符的使用
- 使用類型通配符:?
- List<?>是List<String>、List<Object>等各種泛型List的父類
- 讀取List<?>的對象list中的元素時,永遠是安全的,因為不管list的真實類型是什么,它包含的都是Object
- 寫入list中的元素時,不行。因為我們不知道元素類型,不能向其中添加對象。
- 唯一的例外是null,它是所有類型的成員
- <? extentds A> (無窮小, A]
- 只允許泛型為A及A子類的引用調用
- <? super A> [A, 無窮大)
- 只允許泛型為A及A父類的引用調用
六、IO流
1、File類的使用
1.1、File類的理解
- File類的一個對象,代表一個文件或一個文件目錄(俗稱:文件夾)
- File類聲明在java.io包下
- File類中涉及到關于文件或文件目錄的創建、刪除、重命名、修改時間、文件大小等方法,并未涉及到寫入或讀取文件內容的操作。如果需要讀取或寫入文件內容,必須使用IO流來完成。
- 后續File類的對象常會作為參數傳遞到流的構造器中,指明讀取或寫入的“終點”
1.2、File的實例化
- 常見構造器
- File(String filePath)
- File(String parentPath, String childPath)
- File(File parentFile, String childPath)
- 路徑的分類
- 相對路徑:相較于某個路徑下,指明的路徑
- 絕對路徑:包含盤符在內的文件或文件目錄的路徑
- 路徑分隔符
- public static final String separator:根據操作系統,動態的提供分隔符
1.3、常用方法
- File類的獲取功能
- public String getAbsolutePath():獲取絕對路徑
- public String getPath():獲取路徑
- public String getName():獲取名稱
- public String getParent():獲取上層文件目錄。若無,返回null
- public long length():獲取文件長度(即:字節數)。不能獲取目錄的長度。
- public long lastModified():獲取最后一次的修改時間,毫秒值
- public String[] list():獲取指定目錄下的所有文件或文件目錄的名稱數組
- public File[] listFiles():獲取指定目錄下的所有文件或文件目錄的File數組
- File類的重命名功能
- public boolean renameTo(File dest):把文件重命名為指定的文件路徑。要想保證返回true,需要源文件在硬盤中存在,且目標文件不能在硬盤中存在
- File類的判斷功能
- public boolean isDirectory():判斷是否是文件目錄
- public boolean isFile():判斷是否是文件
- public boolean exists():判斷是否存在
- public boolean canRead():判斷是否可讀
- public boolean canWrite():判斷是否可寫
- public boolean isHidden():判斷是否隱藏
- File類的創建功能
- public boolean creatNewFile():創建文件。若文件存在,則不創建,返回false
- public boolean mkdir():創建文件目錄。如果文件目錄存在,就不創建了。如果此文件目錄的上層目錄不存在,也不創建。
- public boolean mkdirs():創建文件目錄。如果上層文件目錄不存在,一并創建
- File類的刪除功能
- public boolean delete():刪除文件或文件夾。要刪除一個文件目錄,請注意該文件目錄內不能包含文件或文件目錄
2、IO原理及流的分類
2.1、流的分類
- 按操作數據單位不同分為:字節流、字符流
- 按數據流的流向不同分為:輸入流、輸出流
- 按流的角色的不同分為:節點流、處理流
2.2、流的體系結構
| 抽象基類 | InputStream | OutputStream | Reader | Writer |
| 訪問文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
| 訪問數組 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
| 訪問管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
| 訪問字符串 | StringReader | StringWriter | ||
| 緩沖流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
| 轉換流 | InputStreamReader | OutputStreamWriter | ||
| 對象流 | ObjectInputStream | ObjectOutputStream | ||
| FilterInputStream | FilterOutputStream | FilterReader | FilterWriter | |
| 打印流 | PrintStream | PrintWriter | ||
| 推回輸入流 | PushbackInputStream | PushbackReader | ||
| 特殊流 | DataInputStream | DataOutputStream |
2.3、輸入、輸出的標準化過程
- 輸入過程
- 創建File類的對象,指明讀取的數據的來源。(要求此文件一定要存在)
- 創建相應的輸入流,將File類的對象作為參數,傳入流的構造器中
- 具體的讀入過程
- 關閉流資源
- 輸出過程
- 創建File類的對象,指明寫出的數據的位置。(不要求此文件一定要存在)
- 創建相應的輸出流,將File類的對象作為參數,傳入流的構造器中
- 具體的寫出過程
- 關閉流資源
說明:程序中出現的異常需要使用try-catch-finally處理
3、節點流(或文件流)
3.1、FileReader的使用
- read()的理解:返回讀入的一個字符。如果達到文件末尾,返回-1
- 異常的處理:為了保證流資源一定可以執行關閉操作。需要使用try-catch-finally處理
- 讀入的文件一定要存在,否則就會報FileNotFoundException
3.2、FileWriter的使用
- 輸出操作,對應的File可以不存在。并不會報異常
- File對應的硬盤中的文件如果不存在,在輸出的過程中,會自動創建此文件。
- File對應的硬盤中的文件如果存在:
- 如果流使用的構造器是:FileWriter(file, false) / FileWriter(file):對原有文件的覆蓋
- 如果流使用的構造器是:FileWriter(file, true):不會對原有文件覆蓋,而是在原有文件基礎上追加內容
3.3、文本文件的復制
@Test public void testFileReaderFileWriter () {FileReader fr = null;FileWriter fw = null;try {// 1、創建File類的對象,指明讀入和寫出的文件File srcFile = new File("hello.txt");File destFile = new File("hello2.txt");// 2、創建輸入流和輸出流的對象fr = new FileReader(srcFile);fw = new FileWriter(destFile);// 3、數據的讀入和寫出操作char[] cbuf = new char[5];int len; // 記錄每次讀入到cbuf數組中的字符的個數while ((len = fr.read(cbuf)) != -1) {// 每次寫出len個字符fw.write(cbuf, 0, len);}} catch (IOException e) {e.printStackTrace();} finally {// 4、關閉流資源if (fw != null) {try {fw.close();} catch (IOException e) {e.printStackTrace();}}if (fr != null) {try {fr.close();} catch (IOException e) {e.printStackTrace();}}} }3.4、FileInputStream/FileOutputStream的使用
- 對于文本文件(.txt, .java, .c, .cpp),使用字符流處理
- 對于非文本文件(.jpg, .mp3, .mp4, .avi, .doc, .ppt,…),使用字節流處理
說明:
IDEA中:如果大家開發使用JUnit中的單元測試方法測試,相對路徑即為當前Module下。如果使用main()測試,相對路徑即為當前的Project下。
Eclipse中:不管使用單元測試方法還是使用main()測試,相對路徑都是當前的Project下
4、緩沖流
4.1、緩沖流涉及到的類
- BufferedInputStream
- BufferedOutputSteam
- BufferedReader
- BufferedWriter
4.2、作用
- 提高流的讀取、寫入的速度。
- 提高讀寫速度的原因:內部提供了一個緩沖區。默認情況下是8kb
4.3、典型代碼
- 使用BufferedInputStream和BufferedOutputStream:處理非文本文件
- 使用BufferedReader和BufferedWriter:處理文本文件
5、轉換流
5.1、轉換流涉及到的類:屬于字符流
- InputStreamReader:將一個字節的輸入流轉換為字符的輸入流
- 解碼:字節、字節數組 —> 字符數組、字符串
- OutputStreamWriter:將一個字符的輸出流轉換為字節的輸出流
- 編碼:字符數組、字符串 —> 字節、字節數組
說明:編碼決定了解碼的方式
5.2、作用
- 提供字節流與字符流之間的轉換
5.3、典型實現
- 文件編碼的方式(比如:GBK),決定了解析時使用的字符集(也只能是GBK)
5.4、常見的編碼表
- ASCII:美國標準信息交換碼。用一個字節的7位可以表示。
- ISO8859-1:拉丁碼、歐洲碼表。用一個字節的8位表示
- GB2312:中國的中文編碼表。最多兩個字節編碼所有字符
- GBK:中國的中文編碼表升級,融合了更多的中文文字符號。最多兩個字節編碼
- Unicode:國際標準碼,融合了目前人類使用的所有字符。為每個字符分配唯一的字符碼。所有的文字都用兩個字節來表示。
- UTF-8:變長的編碼方法,可用1-4個字節來表示一個字符。
6、其它的流的使用
6.1、標準輸入、輸出流
- System.in:標準的輸入流,默認從鍵盤輸入
- System.out:標準的輸出流,默認從控制臺輸出
- 修改默認的輸入和輸出行為
- System類的setIn(InputStream is)/setOut(PrintStream ps)方式重新指定輸入和輸出的流
6.2、打印流
- PrintStream和PrintWriter
- 提供了一些列重載的print()和println()方法,用于多種數據類型的輸出
- System.out返回的是PrintStream的實例
6.3、數據流
- DataInputStream和DataOutputStream
- 用于讀取或寫出基本數據類型的變量或字符串
7、對象流
7.1、對象流
- ObjectInputStream:內存中的對象 —> 存儲中的文件、通過網絡傳輸出去(序列化過程)
- ObjectOutputSteam:存儲中的文件、通過網絡接受過來 —> 內存中的對象(反序列化過程)
7.2、對象的序列化機制
- 對象序列化機制允許把內存中的Java對象轉換成平臺無關的二進制流,從而允許把這種二進制流持久地保存在磁盤上,或通過網絡將這種二進制流傳輸到另一個網絡節點。
- 當其它程序獲取了這種二進制流,就可以恢復成原來的Java對象
7.3、序列化代碼實現
@Test public void testObjectOutputSteam () {ObjectOutputStream oos = null;try {// 1、造流oos = new ObjectOutputStream(new FileOutputStream("object.dat"));// 2、讀寫過程oos.writeObject(new String("我愛北京天安門"));oos.flush();} catch (IOException e) {e.printStackTrace();} finally {// 3、關閉資源if (oos != null) {try {oos.close();} catch (IOException e) {e.printStackTrace();}}} }7.4、反序列化代碼實現
@Test public void testObjectInputSteam () {ObjectInputStream ois = null;try {ois = new ObjectInputStream(new FileInputStream("object.dat"));Object obj = ois.readObject();String str = (String) obj;System.out.println(str);} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} finally {if (ois != null) {try {ois.close();} catch (IOException e) {e.printStackTrace();}}} }7.5、實現序列化的對象所屬的類需要滿足的條件
- 需要實現接口:Serializable
- 當前類提供一個全局常量:serialVersionUID
- 除了當前類需要實現Serializable接口之外,還必須保證其內部所有屬性也必須是可序列化的。(默認情況下,基本數據類型可序列化)
補充:ObjectOutputSteam和ObjectInputStream不能序列化static和transient修飾的成員變量
8、隨機存取文件流
8.1、隨機存取文件流
- RandomAccessFile
8.2、使用說明
- RandomAccessFile直接繼承于java.lang.Object類,實現了DataInput和DataOutput接口
- RandomAccessFile既可以作為一個輸入流,又可以作為一個輸出流
- 如果RandomAccessFile作為輸出流時,寫出到的文件如果不存在,則在執行過程中自動創建。如果寫出到的文件存在,則會對原文件內容進行覆蓋。(默認情況下,從頭覆蓋)
- 可以通過相關的操作,實現RandomAccessFile“插入”數據的效果。seek(int pos)
8.3、典型代碼
/*** 實現復制功能*/ @Test public void test1 () {RandomAccessFile raf1 = null;RandomAccessFile raf2 = null;try {// 1、造流raf1 = new RandomAccessFile(new File("愛情與友情.jpg"), "r");raf2 = new RandomAccessFile(new File("愛情與友情1.jpg"), "rw");// 2、讀寫過程byte[] buffer = new byte[1024];int len;while ((len = raf1.read(buffer)) != -1) {raf2.write(buffer, 0, len);}} catch (IOException e) {e.printStackTrace();} finally {// 3、關閉資源if (raf1 != null) {try {raf1.close();} catch (IOException e) {e.printStackTrace();}}if (raf2 != null) {try {raf2.close();} catch (IOException e) {e.printStackTrace();}}} } /*** 使用RandomAccessFile實現數據的插入效果*/ @Test public void test3 () {RandomAccessFile raf1 = null;try {raf1 = new RandomAccessFile("hello.txt", "rw");raf1.seek(3); // 將指針調到角標為3的位置// 保存指針3后面的所有數據到StringBuilder中StringBuilder buildr = new StringBuilder((int) new File("hello.txt").length());byte[] buffer = new byte[20];int len;while ((len = raf1.read(buffer)) != -1) {buildr.append(new String(buffer, 0, len));}// 調回指針,寫入“xyz”raf1.seek(3);raf1.write("xyz".getBytes());// 將StringBuilder中的數據寫入到文件中raf1.write(buildr.toString().getBytes());} catch (IOException e) {e.printStackTrace();} finally {if (raf1 != null) {try {raf1.close();} catch (IOException e) {e.printStackTrace();}}} }9、NIO.2中Path、Paths、Files類的使用
9.1、NIO的使用說明
- Java NIO(New IO, Non-Blocking IO)是從Java 1.4版本開始引入的一套新的IO API,可以替代標準的Java IO API。
- NIO與原來的IO同樣的作用和目的,但是使用的方式完全不同,NIO支持面向緩沖區的(IO是面向流的)、基于通道的IO操作
- NIO將以更高效的方式進行文件的讀寫操作
- 隨著JDK 7的發布,Java對NIO進行了極大的擴展,增強了對文件處理和文件系統特性的支持,以至于我們稱他們為NIO.2
9.2、Path的使用
- Path替換原有的File類
- Paths類提供了靜態get()方法用來獲取Path對象:
- static Path get(String first, String … more):用于將多個字符串串連成路徑
- static Path get(URI uri):返回指定uri對應的Parh路徑
- 常用方法:
- String toString():返回調用Path對象的字符串表示形式
- boolean startsWith(String path):判斷是否以path路徑開始
- boolean endsWith(String path):判斷是否以path路徑結束
- boolean isAbsolute():判斷是否是絕對路徑
- Path getParent():返回path對象包含整個路徑,不包含Path對象指定的文件路徑
- Path getRoot():返回調用Path對象的根路徑
- Path getFileName():返回與調用Path對象關聯的文件名
- int getNameCount():返回Path根目錄后面元素的數量
- Path getName(int idx):返回指定索引位置idx的路徑名稱
- Path toAbsolutePath():作為絕對路徑返回調用Path對象
- Path resolve(Path p):合并兩個路徑,返回合并后的路徑對應的Path對象
- File toFile():將Path轉換為File類的對象
9.3、Files工具類
- 作用:操作文件或文件目錄的工具類
- 常用方法:
- Path copy(Path src, Path dest, CopyOption … how):文件的復制
- Path createDirectory(Path path, FileAttribute<?> … attr):創建一個目錄
- Path createFile(Path path, FileAttribute<?> … arr):創建一個文件
- void delete(Path path):刪除一個文件/目錄,如果不存在,執行報錯
- void deleteIfExists(Path path):Path對應的文件/目錄如果存在,執行刪除
- Path move(Path src, Path dest, CopyOption … how):將src移動到dest位置
- long size(Path path):返回指定文件的大小
- 用于判斷:
- boolean exists(Path path, LinkOption … opts):判斷文件是否存在
- boolean isDirectory(Path path, LinkOption … opts):判斷是否是目錄
- boolean isRegularFile(Path path, LinkOption … opts):判斷是否是文件
- boolean isHidden(Path path):判斷是否是隱藏文件
- boolean isReadable(Path path):判斷文件是否可讀
- boolean isWritable(Path path):判斷文件是否可寫
- boolean notExists(Path path, LinkOption … opts):判斷文件是否不存在
- 用于操作內存
- SeekableByteChannel newByteChannel(Path path, OpenOption … how):獲取與指定文件的連接,how指定打開方式
- DirectoryStream<Path> newDirectoryStream(Path path):打開path指定的目錄
- InputStream newInputStream(Path path, OpenOption … how):獲取InputStream對象
- OutputStream newOutputStream(Path path, OpenOption … how):獲取OutputStream對象
七、網絡編程
1、InetAddress類的使用
1.1、實現網絡通信需要解決的兩個問題
- 如何準確地定位網絡上一臺或多臺主機;定位主機上的特定的應用
- 找到主機后如何可靠高效地進行數據傳輸
1.2、網絡通信的兩個要素
- 對應問題一:IP和端口號
- 對應問題二:提供網絡通信協議:TCP/IP參考模型(應用層、傳輸層、網絡層、物理+數據鏈路層)
1.3、通信要素一:IP和端口號
- IP的理解
- IP:唯一的標識Internet上的計算機(通信實體)
- 在Java中使用InetAddress類代表IP
- IP分類:IPv4和IPv6;萬維網和局域網
- 域名:www.baidu.com、www.mi.com
- 域名解析:域名容易記憶,當在連接網絡時輸入一個主機的域名后,域名服務器(DNS)負責將域名轉化成IP地址,這樣才能和主機建立連接。
- 本地回路地址:127.0.0.1 對應著:localhost
- InetAddress類:此類的一個對象就代表著一個具體的IP地址
- 實例化:
- getByName(String host)
- getLocalHost()
- 常用方法:
- getHostName()
- getHostAddress()
- 實例化:
- 端口號:正在計算機上運行的進程
- 要求:不同的進程不同的端口號
- 范圍:被規定為一個16位的整數 0~65535
- 端口號與IP地址的組合得出一個網絡套接字:Socket
1.4、通信要素二:網絡通信協議
| 應用層、表示層、會話層 | 應用層 | HTTP、FTP、Telnet、DNS… |
| 傳輸層 | 傳輸層 | TCP、UDP… |
| 網絡層 | 網絡層 | IP、ICMP、ARP… |
| 數據鏈路層、物理層 | 物理+數據鏈路層 | Link |
1.5、TCP和UDP的區別
- TCP協議:
- 使用TCP協議前,須先建立TCP連接,形成傳輸數據通道
- 傳輸前,采用“三次握手”方式,點對點通信,是可靠的
- TCP協議進行通信的兩個應用進程:客戶端、服務端
- 在連接中可進行大數據量的傳輸
- 傳輸完畢,需要釋放已建立的連接,效率低
- UDP協議:
- 將數據、源、目的封裝成數據包,不需要建立連接
- 每個數據報的大小限制在64K內
- 發送不管對方是否準備好,接收方收到也不確認,故是不可靠的
- 可以廣播發送
- 發送數據結束時無需釋放資源,開銷小,速度快
1.6、TCP三次握手和四次揮手
- 三次握手
- 四次揮手
2、TCP網絡編程
/*** 客戶端發送信息給服務端,服務端將數據顯示在控制臺上*/ public class TCPTest1 {// 客戶端@Testpublic void client() {Socket socket = null;OutputStream os = null;try {// 1、創建Socket對象,指明服務器端的IP和端口號InetAddress inet = InetAddress.getByName("127.0.0.1");socket = new Socket(inet, 8899);// 2、獲取一個輸出流,用于輸出數據os = socket.getOutputStream();// 3、寫出數據的操作os.write("你好,我是客戶端mm".getBytes());} catch (IOException e) {e.printStackTrace();} finally {// 4、資源的關閉if (os != null) {try {os.close();} catch (IOException e) {e.printStackTrace();}}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}//服務端@Testpublic void server () {ServerSocket ss = null;Socket socket = null;InputStream is = null;ByteArrayOutputStream baos = null;try {// 1、創建服務器端的ServerSocket,指明自己的端口號ss = new ServerSocket(8899);// 2、調用accept()表示接收來自客戶端的socketsocket = ss.accept();// 3、獲取輸入流is = socket.getInputStream();// 4、讀取輸入流中的數據baos = new ByteArrayOutputStream();byte[] buffer = new byte[5];int len;while ((len = is.read(buffer)) != -1) {baos.write(buffer, 0, len);}System.out.println(socket.getInetAddress().getHostAddress() + ":" + baos.toString());} catch (IOException e) {e.printStackTrace();} finally {// 5、關閉資源if (baos != null) {try {baos.close();} catch (IOException e) {e.printStackTrace();}}if (is != null) {try {is.close();} catch (IOException e) {e.printStackTrace();}}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}if (ss != null) {try {ss.close();} catch (IOException e) {e.printStackTrace();}}}} } /*** 客戶端發送文件給服務端,服務端將文件保存在本地*/ public class TCPTest2 {// 客戶端@Testpublic void client () {Socket socket = null;OutputStream os = null;FileInputStream fis = null;try {socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);os = socket.getOutputStream();fis = new FileInputStream(new File("beauty.jpg"));byte[] buffer = new byte[1024];int len;while ((len = fis.read(buffer)) != -1) {os.write(buffer, 0, len);}} catch (IOException e) {e.printStackTrace();} finally {if (fis != null) {try {fis.close();} catch (IOException e) {e.printStackTrace();}}if (os != null) {try {os.close();} catch (IOException e) {e.printStackTrace();}}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}//服務端@Testpublic void server () {ServerSocket ss = null;Socket socket = null;InputStream is = null;FileOutputStream fos = null;try {ss = new ServerSocket(9090);socket = ss.accept();is = socket.getInputStream();fos = new FileOutputStream(new File("beauty1.jpg"));byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}} catch (IOException e) {e.printStackTrace();} finally {if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}if (is != null) {try {is.close();} catch (IOException e) {e.printStackTrace();}}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}if (ss != null) {try {ss.close();} catch (IOException e) {e.printStackTrace();}}}} } /*** 從客戶端發送文件給服務器,服務端保存到本地。并返回“發送成功”給客戶端* 并關閉相關的連接*/ public class TCPTest3 {// 客戶端@Testpublic void client (){Socket socket = null;OutputStream os = null;FileInputStream fis = null;InputStream is = null;ByteArrayOutputStream baos = null;try {socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);os = socket.getOutputStream();fis = new FileInputStream(new File("beauty.jpg"));byte[] buffer = new byte[1024];int len;while ((len = fis.read(buffer)) != -1) {os.write(buffer, 0, len);}// 關閉數據的輸出socket.shutdownOutput();// 接收來自于服務器端的數據,并顯示到控制臺上is = socket.getInputStream();baos = new ByteArrayOutputStream();byte[] buffer1 = new byte[20];int len1;while ((len1 = is.read(buffer1)) != -1) {baos.write(buffer1, 0, len1);}System.out.println(baos.toString());} catch (IOException e) {e.printStackTrace();} finally {if (baos != null) {try {baos.close();} catch (IOException e) {e.printStackTrace();}}if (is != null) {try {is.close();} catch (IOException e) {e.printStackTrace();}}if (fis != null) {try {fis.close();} catch (IOException e) {e.printStackTrace();}}if (os != null) {try {os.close();} catch (IOException e) {e.printStackTrace();}}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}// 服務端@Testpublic void server () {ServerSocket ss = null;Socket socket = null;InputStream is = null;FileOutputStream fos = null;OutputStream os = null;try {ss = new ServerSocket(9090);socket = ss.accept();is = socket.getInputStream();fos = new FileOutputStream(new File("beauty2.jpg"));byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}System.out.println("圖片傳輸完成");// 服務器端給予客戶單反饋os = socket.getOutputStream();os.write("你好,美女,照片我已收到,非常漂亮!".getBytes());} catch (IOException e) {e.printStackTrace();} finally {if (os != null) {try {os.close();} catch (IOException e) {e.printStackTrace();}}if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}if (is != null) {try {is.close();} catch (IOException e) {e.printStackTrace();}}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}if (ss != null) {try {ss.close();} catch (IOException e) {e.printStackTrace();}}}} }3、UDP網絡編程
public class UDPTest {// 發送端@Testpublic void sender () {DatagramSocket socket = null;try {socket = new DatagramSocket();String str = "我是UDP方式發送的導彈";byte[] data = str.getBytes();InetAddress inet = InetAddress.getLocalHost();DatagramPacket packet = new DatagramPacket(data, 0, data.length, inet, 9090);socket.send(packet);} catch (IOException e) {e.printStackTrace();} finally {if (socket != null) {socket.close();}}}// 接收端@Testpublic void receiver () {DatagramSocket socket = null;try {socket = new DatagramSocket(9090);byte[] buffer = new byte[100];DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);socket.receive(packet);System.out.println(new String(packet.getData(), 0, packet.getLength()));} catch (IOException e) {e.printStackTrace();} finally {socket.close();}} }4、URL編程
4.1、URL(Uniform Resource Locator)的理解
- 統一資源定位符,對應著互聯網的某一資源地址
4.2、URL的5個基本結構
- 協議
- 主機名
- 端口號
- 資源地址
- 參數列表
4.3、如何實例化
- URL url = new URL("http://localhost:8080/examples/beauty.jpg?username=Tom")
4.4、常用方法
- public String getProtocol():獲取該URL的協議名
- public String getHost():獲取該URL的主機名
- public String getPort():獲取該URL的端口號
- public String getPath():獲取該URL的文件路徑
- public String getFile():獲取該URL的文件名
- public String getQuery():獲取該URL的查詢名
4.5、可以讀取、下載對應的url資源
public static void main(String[] args) {HttpURLConnection urlConnection = null;InputStream is = null;FileOutputStream fos = null;try {URL url = new URL("http://localhost:8080/examples/beauty.jpg");urlConnection = (HttpURLConnection) url.openConnection();urlConnection.connect();is = urlConnection.getInputStream();fos = new FileOutputStream("day10\\beauty3.jpg");byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}System.out.println("下載完成");} catch (IOException e) {e.printStackTrace();} finally {if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}if (is != null) {try {is.close();} catch (IOException e) {e.printStackTrace();}}if (urlConnection != null) {urlConnection.disconnect();}} }八、Java反射機制
1、反射的概述
1.1、本章的主要內容
- Java反射機制概述
- 理解Class類并獲取Class實例
- 類的加載與ClassLoader的理解
- 創建運行時類的對象
- 獲取運行時類的完成結構
- 調用運行時類的指定結構
- 反射的應用:動態代理
1.2、關于反射的理解
- Reflection(反射)是被視為動態語言的關鍵,反射機制允許程序在執行期借助于Reflection API取得任何類的內部信息,并能直接操作任意對象的內容屬性及方法。
框架 = 反射 + 注解 + 設計模式
1.3、體會反射機制的“動態性”
/*** 體會反射的動態性*/ @Test public void test2 () {for (int i = 0; i < 100; i++) {int num = new Random().nextInt(3);String classPath = "";switch (num) {case 0:classPath = "java.util.Date";break;case 1:classPath = "java.lang.Object";break;case 2:classPath = "com.atguigu.java.Person";break;}try {Object obj = getInstance(classPath);System.out.println(obj);} catch (Exception e) {e.printStackTrace();}} } /*** 創建一個指定類的對象* @param classPath 指定類的全類名 */ public Object getInstance(String classPath) throws Exception { Class clazz = Class.forName(classPath); return clazz.newInstance(); }1.4、反射機制能提供的功能
- 在運行時判斷任意一個對象所屬的類
- 在運行時構造任意一個類的對象
- 在運行時判斷任意一個類所具有的成員變量和方法
- 在運行時獲取泛型信息
- 在運行時調用任意一個對象的成員變量和方法
- 在運行時處理注解
- 生成動態代理
1.5、相關API
- java.lang.Class:反射的源頭
- java.lang.reflect.Method
- java.lang.reflect.Field
- java.lang.reflect.Constructor
2、Class類的理解與獲取Class的實例
2.1、Class類的理解
- 類的加載過程
- 程序經過javac.exe命令以后,會生成一個或多個字節碼文件(.class結尾)。接著我們使用java.exe命令對某個字節碼文件進行解釋運行。相當于將某個字節碼文件加載到內存中。此過程就稱為類的加載。加載到內存中的類,我們就稱為運行時類,此運行時類,就作為Class的一個實例。
- 換句話說,Class的實例就對應著一個運行時類
- 加載到內存中的運行時類,會緩存一定的時間。在此時間之內,我們可以通過不同的方式來獲取此運行時類。
2.2、獲取Class實例的幾種方式
@Test public void test3() throws ClassNotFoundException {// 方式一:調用運行時類的屬性:.classClass clazz1 = Person.class;System.out.println(clazz1);// 方式二:通過運行時類的對象,調用getClass()Person p1 = new Person();Class clazz2 = p1.getClass();System.out.println(clazz2);// 方式三:調用Class的靜態方法:forName(String classPath)Class clazz3 = Class.forName("com.atguigu.java.Person");System.out.println(clazz3);// 方式四:使用類的加載器:ClassLoader(了解)ClassLoader classLoader = ReflectionTest.class.getClassLoader();Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");System.out.println(clazz4);System.out.println(clazz1 == clazz2);// trueSystem.out.println(clazz1 == clazz3);// trueSystem.out.println(clazz1 == clazz4);// true }2.3、總結:創建類的對象的方式
- 方式一:new + 構造器
- 方式二:要創建Xxx類的對象,可以考慮:Xxx、Xxxs、XxxFactory、XxxBuilder類中查看是否有靜態方法的存在??梢哉{用其靜態方法,創建Xxx對象
- 方式三:通過反射
2.4、Class實例可以是哪些結構的說明
- class:外部類,成員(成員內部類,靜態內部類),局部內部類,匿名內部類
- interface:接口
- []:數組
- enum:枚舉
- annotation:注解@interface
- primitive type:基本數據類型
- void
3、了解ClassLoader
3.1、類的加載過程(了解)
- 當程序主動使用某個類時,如果該類還未被加載到內存中,則系統會通過如下三個步驟來對該類進行初始化。
- 類的加載:將類的class文件讀入內存,并為之創建一個java.lang.Class對象。此過程由類加載器完成
- 類的鏈接:將類的二進制數據合并到JRE中
- 類的初始化:JVM負責對類進行初始化
3.2、類的加載器的作用
- 類加載的作用:將class文件字節碼內容加載到內存中,并將這些靜態數據轉換成方法區的運行時數據結構,然后在堆中生成一個代表這個類的java.lang.Class對象,作為方法區中類數據的訪問入口。
- 類緩存:標準的JavaSE類加載器可以按要求查找類,但一旦某個類被加載到類加載器中,它將維持加載(緩存)一段時間。不過JVM垃圾回收機制可以回收這些Class對象。
3.3、類的加載器的分類
- 引導類加載器:用C++編寫的,是JVM自帶的類加載器,負責Java平臺核心庫,用來裝載核心類庫。該加載器無法直接獲取
- 擴展類加載器:負責jre/lib/ext目錄下的jar包或-D java.ext.dirs指定目錄下的jar包裝入工作庫
- 系統類加載器:負責java -classpath 或 -D java.class.path所指的目錄下的類與jar包裝入工作,是最常用的加載器
3.4、Java類編譯、運行的執行過程
#mermaid-svg-AM35lXhDlGQXMOXi {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-AM35lXhDlGQXMOXi .error-icon{fill:#552222;}#mermaid-svg-AM35lXhDlGQXMOXi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AM35lXhDlGQXMOXi .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-AM35lXhDlGQXMOXi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AM35lXhDlGQXMOXi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AM35lXhDlGQXMOXi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AM35lXhDlGQXMOXi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AM35lXhDlGQXMOXi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AM35lXhDlGQXMOXi .marker.cross{stroke:#333333;}#mermaid-svg-AM35lXhDlGQXMOXi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AM35lXhDlGQXMOXi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AM35lXhDlGQXMOXi .cluster-label text{fill:#333;}#mermaid-svg-AM35lXhDlGQXMOXi .cluster-label span{color:#333;}#mermaid-svg-AM35lXhDlGQXMOXi .label text,#mermaid-svg-AM35lXhDlGQXMOXi span{fill:#333;color:#333;}#mermaid-svg-AM35lXhDlGQXMOXi .node rect,#mermaid-svg-AM35lXhDlGQXMOXi .node circle,#mermaid-svg-AM35lXhDlGQXMOXi .node ellipse,#mermaid-svg-AM35lXhDlGQXMOXi .node polygon,#mermaid-svg-AM35lXhDlGQXMOXi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AM35lXhDlGQXMOXi .node .label{text-align:center;}#mermaid-svg-AM35lXhDlGQXMOXi .node.clickable{cursor:pointer;}#mermaid-svg-AM35lXhDlGQXMOXi .arrowheadPath{fill:#333333;}#mermaid-svg-AM35lXhDlGQXMOXi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AM35lXhDlGQXMOXi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AM35lXhDlGQXMOXi .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-AM35lXhDlGQXMOXi .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-AM35lXhDlGQXMOXi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AM35lXhDlGQXMOXi .cluster text{fill:#333;}#mermaid-svg-AM35lXhDlGQXMOXi .cluster span{color:#333;}#mermaid-svg-AM35lXhDlGQXMOXi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AM35lXhDlGQXMOXi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 源程序 Java編譯器 字節碼 類裝載器 字節碼校驗器 解釋器 操作系統平臺3.5、使用Classloader加載src目錄下的配置文件
@Test public void test2() throws Exception {Properties pros = new Properties();// 讀取配置文件的方式一:// FileInputStream fis = new FileInputStream("src\\jdbc1.properties");// pros.load(fis);// 讀取配置文件的方式二:ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();InputStream is = classLoader.getResourceAsStream("jdbc1.properties");pros.load(is);String user = pros.getProperty("user");String password = pros.getProperty("password");System.out.println("user = " + user + ",password = " + password); }4、反射應用一:創建運行時類的對象
- newInstance():調用此方法,創建對應的運行時類的對象。內部調用了運行時類的空參的構造器
- 要想此方法正常的創建運行時類的對象,要求:
- 1、運行時類必須提供空參的構造器
- 2、空參的構造器的訪問權限得夠。通常,設置為public
- 在JavaBean中要求提供一個public的空參構造器。原因:
- 1、便于通過反射,創建運行時類的對象
- 2、便于子類繼承此運行時類時,默認調用super()時,保證父類有此構造器
5、反射應用二:獲取運行時類的完整結構
5.1、獲取屬性
@Test public void test1() {Class clazz = Person.class;// 獲取屬性結構// getFields():獲取當前運行時類及其父類中聲明為public訪問權限的屬性Field[] fields = clazz.getFields();for (Field f : fields) {System.out.println(f);}System.out.println();// getDeclaredFields():獲取當前運行時類中聲明的所有屬性。(不包含父類中聲明的屬性)Field[] declaredFields = clazz.getDeclaredFields();for (Field f : declaredFields) {System.out.println(f);} }5.2、獲取方法
@Test public void test1 () {Class clazz = Person.class;// getMethods():獲取當前運行時類及其所有父類中聲明為public權限的方法Method[] methods = clazz.getMethods();for (Method m : methods) {System.out.println(m);}System.out.println();// getDeclaredMethods():獲取當前運行時類中聲明的所有方法。(不包含父類中聲明的方法)Method[] declaredMethods = clazz.getDeclaredMethods();for (Method m : declaredMethods) {System.out.println(m);} }5.3、獲取其他結構
/*** 獲取構造器結構*/ @Test public void test1 () {Class clazz = Person.class;// getConstructors():獲取當前運行時類中聲明為public的構造器Constructor[] constructors = clazz.getConstructors();for (Constructor c : constructors) {System.out.println(c);}System.out.println();// getDeclaredConstructors():獲取當前運行時類中聲明的所有構造器Constructor[] declaredConstructors = clazz.getDeclaredConstructors();for (Constructor c : declaredConstructors) {System.out.println(c);} } /*** 獲取運行時類的父類*/ @Test public void test2 () {Class clazz = Person.class;Class superclass = clazz.getSuperclass();System.out.println(superclass); } /*** 獲取運行時類的帶泛型的父類*/ @Test public void test3 () {Class clazz = Person.class;Type genericSuperclass = clazz.getGenericSuperclass();System.out.println(genericSuperclass); } /*** 獲取運行時類的帶泛型的父類的泛型*/ @Test public void test4 () {Class clazz = Person.class;Type genericSuperclass = clazz.getGenericSuperclass();ParameterizedType paramType = (ParameterizedType) genericSuperclass;// 獲取泛型類型Type[] actualTypeArguments = paramType.getActualTypeArguments();for (Type type : actualTypeArguments) {System.out.println(type.getTypeName());} } /*** 獲取運行時類實現的接口*/ @Test public void test5 () {Class clazz = Person.class;Class[] interfaces = clazz.getInterfaces();for (Class c : interfaces) {System.out.println(c);}System.out.println();//獲取運行時類的父類實現的接口Class[] interfaces1 = clazz.getSuperclass().getInterfaces();for (Class c : interfaces1) {System.out.println(c);} } /*** 獲取運行時類所在的包*/ @Test public void test6 () {Class clazz = Person.class;Package pack = clazz.getPackage();System.out.println(pack); } /*** 獲取運行時類聲明的注解*/ public void test7 () {Class clazz = Person.class;Annotation[] annotations = clazz.getAnnotations();for (Annotation annos : annotations) {System.out.println(annos);} }6、反射應用三:調用運行時類的指定結構
6.1、調用指定的屬性
@Testpublic void testField1 () throws Exception {Class clazz = Person.class;// 創建運行時類的對象Person p = (Person) clazz.newInstance();// 1、getDeclaredField(String fieldName):獲取運行時類中指定變量名的屬性Field name = clazz.getDeclaredField("name");// 2、保證當前屬性時可訪問的name.setAccessible(true);// 3、獲取、設置指定對象的此屬性值name.set(p, "Tom");System.out.println(name.get(p));}6.2、調用指定的方法
@Test public void testMethod () throws Exception {Class clazz = Person.class;// 創建運行時類的對象Person p = (Person) clazz.newInstance();// 1、獲取指定的某個方法// getDeclaredMethod():參數1指明獲取的方法的名稱 參數2指明獲取的方法的形參類型Method show = clazz.getDeclaredMethod("show", String.class);// 2、保證當前方法是可訪問的show.setAccessible(true);// 3、調用方法的invoke():參數1方法的調用者 參數2給方法形參賦值的實參// invoke()的返回值即為對應類中調用的方法的返回值Object returnValue = show.invoke(p, "CHN");System.out.println(returnValue);// 如何調用靜態方法Method showDesc = clazz.getDeclaredMethod("showDesc");showDesc.setAccessible(true);// 如果調用的運行時類中的方法沒有返回值,則此invoke()返回null// Object returnVal = showDesc.invoke(null);Object returnVal = showDesc.invoke(Person.class);System.out.println(returnVal); }6.3、調用指定的構造器
@Test public void testConstructor() throws Exception {Class clazz = Person.class;// 1、獲取指定的構造器// getDeclaredConstructor():參數指明構造器的參數列表Constructor constructor = clazz.getDeclaredConstructor(String.class);// 2、保證此構造器是可訪問的constructor.setAccessible(true);// 3、調用此構造器創建運行時類的對象Person per = (Person) constructor.newInstance("Tom");System.out.println(per); }7、反射應用四:動態代理
7.1、代理模式的原理
- 使用一個代理將對象包裝起來,然后用該代理對象取代原始對象。任何對原始對象的調用都要通過代理。代理對象決定是否以及何時將方法調用轉到原始對象上。
7.2、靜態代理
// 被代理類 Class MyThread implements Runnable () {} // 代理類 Class Thread implements Runnable() {} // 代理操作 main () {MyThread t = new MyThread();Thread thread = new Thread(t);thread.start();// 啟動線程;調用線程的run() }- 靜態代理的缺點
- 代理類和目標對象的類都是在編譯期間確定下來,不利于程序的擴展。
- 每一個代理類只能為一個接口服務,這樣一來程序開發中必然產生過多的代理。
7.3、動態代理的特點
- 動態代理是指客戶通過代理類來調用其它對象的方法,并且是在程序運行時根據需要動態創建目標類的代理對象。
7.4、動態代理的實現
- 需要解決的兩個主要問題:
- 問題一:如何根據加載到內存中的被代理類,動態創建一個代理類及其對象 --> 通過Proxy.newProxyInstance()實現
- 問題二:當通過代理類的對象調用方法a時,如何動態的去調用被代理類中的同名方法a --> 通過InvocationHandler接口的實現類及其方法invoke()
九、Java8的其它新特性
1、Java8的新特性概述
- 函數式接口
- Lambda表達式
- 方法引用/構造器引用
- Stream API
- 并行流
- 串行流
- 接口的增強
- 靜態方法
- 默認方法
- Optional類
- 新的時間和日期API
- 其它新特性
- 重復注解
- 類型注解
- 通用目標類型推斷
- JDK的更新
- 集合的流式操作
- 并發
- Arrays
- Number和Math
- IO/NIO的改進
- Reflection獲取形參名
- String:join()
- Files
- 新編譯工具:jjs、jdeps
- JVM中Metaspace取代PermGen空間
2、Lambda表達式
2.1、Lambda表達式使用前后的對比
@Test public void test1 () {Runnable r1 = new Runnable() {@Overridepublic void run() {System.out.println("我愛北京天安門");}};r1.run();// 使用lambda表達式Runnable r2 = () -> System.out.println("我愛北京天安門");r2.run(); } @Test public void test2 () {Comparator<Integer> com1 = new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return Integer.compare(o1, o2);}};System.out.println(com1.compare(12, 21));// Lambda表達式的寫法Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);System.out.println(com2.compare(32, 21));// 方法引用Comparator<Integer> com3 = Integer :: compare;System.out.println(com3.compare(32, 21)); }2.2、Lambda表達式的基本語法
- 舉例:(o1, o2) -> Integer.compare(o1, o2);
- 格式:
- ->:lambda操作符或箭頭操作符
- ->左邊:lambda形參列表(其實就是接口中的抽象方法的形參列表)
- ->右邊:lambda體(其實就是重寫的抽象方法的方法體)
2.3、如何使用:分為六種情況
- 語法格式一:無參,無返回值
- Runnable r1 = () -> {System.out.println("Hello Lambda!");};
- 語法格式二:Lambda需要一個參數,但是沒有返回值
- Consumer<String> con = (String str) -> {System.out.println(str);};
- 語法格式三:數據類型可以省略,因為可由編譯器推斷得出,稱為“類型推斷”
- Consumer<String> con = (str) -> {System.out.println(str);};
- 語法格式四:Lambda若只需要一個參數時,參數的小括號可以省略
- Consumer<String> con = str -> {System.out.println(str);};
- 語法格式五:Lambda需要兩個或以上的參數,多條執行語句,并且可以有返回值
- Comparator<Integer> com = (x, y) -> {System.out.println("實現函數式接口方法!"); return Integer.compare(x, y);};
- 語法格式六:當Lambda體只有一條語句時,return與大括號若有,都可以省略
- Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
總結六種情況:
->左邊:lambda形參列表的參數類型可以省略(類型推斷);如果lambda形參列表只有一個參數,其一對()也可以省略
->右邊:lambda體應該使用一對{}包裹;如果lambda體只有一條執行語句(可能是return語句,省略這一對{}和return關鍵字)
3、函數式接口
3.1、函數式接口的使用說明
- 如果一個接口中,只聲明了一個抽象方法,則此接口就稱為函數式接口
- 我們可以在一個接口上使用@FunctionalInterface注解,這樣做可以檢查它是否是一個函數式接口
- Lambda表達式的本質:作為函數式接口的實例
3.2、Java8中關于Lambda表達式提供的4個基本的函數式接口
| Consumer<T>消費型接口 | T | void | 對類型為T的對象應用操作,包含方法:void accept(T t) |
| Supplier<T>供給型接口 | 無 | T | 返回類型為T的對象,包含方法:T get() |
| Function<T, R>函數型接口 | T | R | 對類型為T的對象應用操作,并返回結果。結果是R類型的對象。包含方法:R apply(T t) |
| Predicate<T>斷定型接口 | T | boolean | 確定類型為T的對象是否滿足某約束,并返回boolean值。包含方法:boolean test(T t) |
3.3、總結
- 何時使用lambda表達式?
- 當需要對一個函數式接口實例化的時候,可以使用lambda表達式
- 何時使用給定的函數式接口?
- 如果我們開發中需要定義一個函數式接口,首先看看在已有的JDK提供的函數式接口是否提供了能滿足需求的函數式接口。如果有,則直接調用即可,不需要自己再自定義了。
4、方法引用
4.1、理解
- 方法引用可以看做是Lambda表達式深層次的表達。換句話說,方法引用就是Lambda表達式,也就是函數式接口的一個實例,通過方法的名字來指向一個方法。
4.2、使用情境
- 當要傳遞給Lambda體的操作,已經有實現的方法了,可以使用方法引用
4.3、格式
- 類(或對象) :: 方法名
4.4、分為如下的三種情況
- 對象 :: 非靜態方法 --> 情況1
- 類 :: 靜態方法 --> 情況2
- 類 :: 非靜態方法 --> 情況3
4.5、要求
- 要求接口中的抽象方法的形參列表和返回值類型與方法引用的方法的形參列表和返回值類型相同!(針對于情況1和情況2)
- 當函數式接口方法的第一個參數是需要引用方法的調用者,并且第二個參數是需要引用方法的參數(或無參數)時:ClassName :: methodName(針對于情況3)
4.6、使用建議
- 如果給函數式接口提供實例,恰好滿足方法引用的使用情境,大家就可以考慮使用方法引用給函數式接口提供實例。如果大家不熟悉方法引用,那么還可以使用lambda表達式。
4.7、使用舉例
// 情況一:對象 :: 實例方法 // Consumer中的void accept(T t) // PrintStream中的void println(T t) @Test public void test1 () {Consumer<String> con1 = str -> System.out.println(str);con1.accept("北京");// 方法引用PrintStream ps = System.out;Consumer<String> con2 = ps::println;con2.accept("beijing"); } // 情況二:類 :: 靜態方法 // Comparator中int compare(T t1, T t2) // Integer中的int compare(T t1, T t2) @Test public void test3 () {Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);System.out.println(com1.compare(12,21));// 方法引用Comparator<Integer> com2 = Integer::compare;System.out.println(com2.compare(12, 3)); } // 情況三:類 :: 實例方法 // Comparator中的int compare(T t1, T t2) // String中的int t1.compareTo(t2) @Test public void test5 () {Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);System.out.println(com1.compare("abc", "abd"));// 方法引用Comparator<String> com2 = String :: compareTo;System.out.println(com2.compare("adb", "abm")); }5、構造器引用與數組引用
5.1、構造器引用格式
- 類名::new
5.2、構造器引用使用要求
- 和方法引用類似,函數式接口的抽象方法的形參列表和構造器的形參列表一致。抽象方法的返回值類型即為構造器所屬的類的類型
5.3、構造器引用舉例
// 構造器引用 // Supplier的T get() // Employee的空參構造器:Employee() @Test public void test1 () {Supplier<Employee> sup = new Supplier<Employee>() {@Overridepublic Employee get() {return new Employee();}};// lambda表達式Supplier<Employee> sup1 = () -> new Employee();System.out.println(sup1.get());// 構造器引用Supplier<Employee> sup2 = Employee :: new;System.out.println(sup2.get()); } // Function中的R apply(T t) @Test public void test2 () {Function<Integer, Employee> func1 = id -> new Employee(id);System.out.println(func1.apply(1001));// 構造器引用Function<Integer, Employee> func2 = Employee :: new;System.out.println(func2.apply(1002)); }5.4、數組引用格式
- 數組類型[]::new
5.5、數組引用舉例
// 數組引用 // Function中的R apply(T t) @Test public void test4 () {Function<Integer, String[]> func1 = length -> new String[length];System.out.println(Arrays.toString(func1.apply(5)));// 數組引用Function<Integer, String[]> func2 = String[] :: new;System.out.println(Arrays.toString(func2.apply(10))); }6、Stream API
6.1、Stream API的理解
- Stream關注的是對數據的運算,與CPU打交道;集合關注的是數據的存儲,與內存打交道
- Java 8提供了一套API,使用這套API可以對內存中的數據進行過濾、排序、映射、歸約等操作。類似SQL對數據庫中表的相關操作。
6.2、注意點
- Stream自己不會存儲元素
- Stream不會改變源對象。相反,他們會返回一個持有結果的新的Stream
- Stream操作是延遲執行的。這意味著他們會等到需要結果的時候才執行
6.3、Stream的使用流程
- Stream的實例化
- 一系列的中間操作(過濾、映射、…)
- 終止操作
6.4、使用流程的注意點
- 一個中間操作鏈,對數據源的數據進行處理
- 一旦執行終止操作,就執行中間操作鏈,并產生結果。之后,不會再被使用。
6.5、步驟一:Stream實例化
// 創建Stream方式一:通過集合 @Test public void test1 () {List<Employee> employees = EmployeeData.getEmployees();// default Stream<E> stream():返回一個順序流Stream<Employee> stream = employees.stream();// default Stream<E> parallelStream():返回一個并行流Stream<Employee> parallelStream = employees.parallelStream(); } // 創建Stream方式二:通過數組 @Test public void test2 () {int[] arr = new int[]{1,2,3,4,5,6};// 調用Arrays類的static <T> Stream<T> stream(T[] array):返回一個流IntStream stream = Arrays.stream(arr); } // 創建Stream方式三:通過Stream的of() @Test public void test3 () {Stream<Integer> stream = Stream.of(1,2,3,4,5,6); } // 創建Stream方式四:創建無限流 @Test public void test4 () {// 迭代// public static <T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)// 遍歷前10個偶數Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);// 生成// public static <T> Stream<T> generate(Supplier<T> s)Stream.generate(Math::random).limit(10).forEach(System.out::println); }6.6、步驟二:中間操作
- 篩選與切片
- filter(Predicate p):接收Lambda,從流中排除某些元素
- distinct():篩選,通過流所生成元素的hashCode()和equals()去除重復元素
- limit(long maxSize):截斷流,使其元素不超過給定數量
- skip(long n):跳過元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空流。與limit(n)互補
- 映射
- map(Function f):接收一個函數作為參數,該函數會被應用到每個元素上,并將其映射成一個新的元素
- mapToDouble(ToDoubleFunction f):接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的DoubleStream
- mapToInt(ToIntFunction f):接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的IntStream
- mapToLong(ToLongFunction f):接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的LongStream
- flatMap(Function f):接收一個函數作為參數,將流中的每個值都換成另一個流,然后把所有流連接成一個流
- 排序
- sorted():產生一個新流,其中按自然順序排序
- sorted(Comparator com):產生一個新流,其中按比較器順序排序
6.7、步驟三:終止操作
- 匹配與查找
- allMatch(Predicate p):檢查是否匹配所有元素
- anyMatch(Predicate p):檢查是否至少匹配一個元素
- noneMatch(Predicate p):檢查是否沒有匹配所有元素
- findFirst():返回第一個元素
- findAny():返回當前流中的任意元素
- count():返回流中元素總數
- max(Comparator c):返回流中最大值
- min(Comparator c):返回流中最小值
- forEach(Consumer c):內部迭代(使用Collection接口需要用戶去做迭代,稱為外部迭代。相反,Stream API使用內部迭代——它幫你把迭代做了)
- 歸約
- reduce(T iden, BinaryOperator b):可以將流中元素反復結合起來,得到一個值。返回T
- reduce(BinaryOperator b):可以將流中元素反復結合起來,得到一個值。返回Optional<T>
- 收集
- collect(Collector c):將流轉換為其他形式。接收一個Collector接口的實現,用于給Stream中元素做匯總的方法(Collector通過Collectors提供實例)
- toList()
- toSet()
- toCollection()
- collect(Collector c):將流轉換為其他形式。接收一個Collector接口的實現,用于給Stream中元素做匯總的方法(Collector通過Collectors提供實例)
7、Optional類的使用
7.1、理解
- 為了解決Java中的空指針問題而生!
- Optional<T>類(java.util.Optional)是一個容器類,它可以保存類型T的值,代表這個值存在。或者僅僅保存null,表示這個值不存在。原來用null表示一個值不存在,現在Optional可以更好的表達這個概念。并且可以避免空指針異常。
7.2、常用方法
@Test public void test1 () {// empty():創建的Optional對象內部的value = nullOptional<Object> op1 = Optional.empty();if (!op1.isPresent()) {// Optional封裝的數據是否包含數據System.out.println("數據為空");}// 如果Optional封裝的數據value為空,則get()報錯。否則,value不為空時,返回value// System.out.println(op1.get()); } @Test public void test2 () {String str = "hello";// of(T t):封裝數據t生成Optional對象,要求t非空,否則報錯Optional<String> op1 = Optional.of(str);// get()通常與of()方法搭配使用。用于獲取內部的封裝的數據valueString str1 = op1.get();System.out.println(str1); } @Test public void test3 () {String str = "beijing";str = null;// ofNullable(T t):封裝數據t賦給Optional內部的value。不要求t非空Optional<String> op1 = Optional.ofNullable(str);// orElse(T t1):如果Optional內部的value非空,則返回此value值。如果value為空,則返回t1String str2 = op1.orElse("shanghai");System.out.println(str2); }總結
以上是生活随笔為你收集整理的尚硅谷Java入门视频教程(在线答疑+Java面试真题)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 直播加热是什么意思?抖音直播加热方法有哪
- 下一篇: 数据结构 | 十大排序超硬核八万字详解【