[ 4w字 ] JavaSE总结(基础+高级+多线程+面试题)
💟 JavaSE溫故而知新
每一個例子每一個圖都會讓你有新的理解。不論是小白初學還是復習用,都會有新的收獲。
文章目錄
- 一、Java基礎
- JDK、JRE、JVM
- **基礎語法**
- Java編碼規范
- 1.注釋
- 2.關鍵字
- 3.數據類型
- 4.變量和常量
- 5.運算符
- 6.流程控制
- 7.方法(函數)
- 8.方法與JVM結合分析
- 嵌套調用分析
- 遞歸調用分析
- 9.數組
- 數組的語法:
- 數組的內存結構
- 二維數組
- **面向對象**
- 1.定義一個Java類
- 2.對象的創建和使用
- 對象的創建:
- 對象的使用
- 3.變量的JVM分析
- 簡單變量 VS 引用
- 成員變量 VS 局部變量
- 4.this
- 5.面向對象的三大特性
- 封裝
- 繼承
- 多態
- 6.super
- 7.JVM分析對象的創建過程
- 8.單繼承
- 9.static和final
- 9.1 static靜態的
- 9.2 final
- 9.3 修飾符總結
- 10.抽象類
- 10.1 抽象的abstract
- 10.2 依賴倒轉原則(弱耦合)
- 11.接口
- JDK1.8+接口的新語法
- 開閉原則
- 接口回調
- 12.內部類
- 二、**Java高級**
- 1.Lambda表達式(語法糖)
- 2.Object類和常用類
- object類
- 包裝類
- 日期處理
- 3.字符串處理String
- 字符串中的常用方法
- String常量池
- StringBuilder
- 4.集合(容器)
- 集合的組成
- :one:Collection
- :two:List
- :three:Set
- :four:Map
- 泛型
- 線程安全的集合
- 隊列Queue
- 5.異常
- 異常的分類
- 異常對象的產生和傳遞
- 異常的處理
- 聲明拋出
- 捕獲異常
- 6.B IO
- IO流的分類
- IO編程的基本順序:
- 字節流
- 寫文件
- 讀文件
- IO流處理異常的方法
- 文件拷貝
- 緩沖字節流
- 對象序列化
- 對象的克隆
- 字符流
- Reader/writer
- File類
- 7.多線程(并發編程)
- 如何創建線程?
- 方法1(初始):
- 方法2(老式):
- 線程的基本狀態
- 線程的等待
- 線程池 JDK5
- 線程池的編碼:
- 用匿名內部類簡化編碼
- Callable和Future接口
- 線程的同步(重點)
- synchronized
- 線程的阻塞狀態
- 線程安全的對象
- 同步方法
- 死鎖
- 8.反射和設計模式
- 類對象
- 三、設計模式
- 1.單例設計模式
- 2.工廠模式
- 四、**常見算法**
- 1.漢諾塔問題
- 2.排序
- 1.冒泡排序
- 五、**常見機試題**
- 六、**常見面試題**
一、Java基礎
🐤 為了簡潔美觀的代碼而奮斗!
- 可讀性
- 可用性
- 高效性
- 可維護性
- 可重用性
- 可拓展性
- 弱耦合性
JDK、JRE、JVM
JDK(java程序開發包)=JRE +Tools
JRE=JVM(虛擬機)+API
javac ``XXX.java 生成 .class文件
java XXX運行
基礎語法
Java編碼規范
1.注釋
/*** @author 王澤* 這里是文檔注釋,可以與命令 javadoc 生成幫助文檔*/public class Jichu01 {public void zhushi(){//這是行內注釋System.out.println("第一節、注釋");} /* 這里是多行注釋這里是多行注釋這里是多行注釋*/}2.關鍵字
(1)用于數據類型。
用于數據類型的關鍵字有 boolean、byte、char、 double、 false、float、int、long、new、short、true、void、instanceof。
(2)用于語句。
用于語句的關鍵字有break、case、 catch、 continue、 default 、do、 else、 for、 if、return、switch、try、 while、 finally、 throw、this、 super。
(3)用于修飾
用于修飾的關鍵字有 abstract、final、native、private、 protected、public、static、synchronized、
transient、 volatile。
(4)用于方法、類、接口、包和異常。
用于方法、類、接口、包和異常的關鍵字有 class、 extends、 implements、interface、 package、import、throws。
還有些關鍵字,如cat、 future、 generic、innerr、 operator、 outer、rest、var等都是Java保留的沒有意義的關鍵字。
另外,Java還有3個保留字:true、false、null。它們不是關鍵字,而是文字。包含Java定義的值。和關鍵字一樣,它們也不可以作為標識符使用。
3.數據類型
?? 八大基本數據類型: // 1 字節 占 8位二進制位
基本數據類型 | 存儲大小 | 取值范圍 | 默認值
byte | 8 位有符號數 |-2^7?27 到 2^7-127?1 | 0
short | 16 位有符號數 |-2^{15}?215 到 2^{15}-1215?1 |0
int | 32 位有符號數 |-2^{31}?231 到 2^{31}-1231?1 |0
long | 64 位有符號數 | -2^{63}?263 到 2^{63}-1263?1 |0L
float | 32 位 | 負數 -3.402823e+38 到 -1.401298e-45,正數 1.401298e-45 到 3.402823e+38 0.0f
double | 64 位 |負數 -1.797693e+308 到 -4.9000000e-324,正數 4.9000000e-324 到 1.797693e+308 0.0d
char |16 位 |0-65535 |’\u0000’
boolean |1 位 |true 和 false |false
👀 注意:
-
整數類型的直接量默認是 int 類型,如果直接量超出了 int 類型的取值范圍,則必須在其后面加上字母 L 或 l,將直接量顯性聲明為 long 類型,否則會導致編譯錯誤。
-
浮點類型的直接量默認是 double 類型,如果要將直接量表示成 float 類型,則必須在其后面加上字母 F 或 f。將 double 類型的直接量賦值給 float 類型的變量是不允許的,會導致編譯錯誤。
-
將小范圍類型的變量轉換為大范圍類型稱為拓寬類型,不需要顯性聲明類型轉換。將大范圍類型的變量轉換為小范圍類型稱為縮窄類型,必須顯性聲明類型轉換,否則會導致編譯錯誤。
-
布爾類型不能轉換成其他基本數據類型,其他基本數據類型也不能轉換成布爾類型。
-
字符類型與數字類型之間可以進行轉換。
將數字類型轉換成字符類型時,只使用整數的低 16 位(浮點數類型將整數部分轉換成字符類型)。
將字符類型轉換成數字類型時,字符的統一碼轉換成指定的數值類型。如果字符的統一碼超出了轉換成的數值類型的取值范圍,則必須顯性聲明類型轉換。
特殊字符與轉義字符序列
💯 例題:
public static void main(String[] args) {int a =4;long b =5L;int c =(int)b;long d = a;char e = 'A';int f = e;System.out.println(c); //5System.out.println(d); //4System.out.println(f); //65/*Unicode 編碼方式char c ='A'; char c = 65; char c='/u0041';// 這三個都是相等的 都是A'A' -----65'a'------97'0'------48*/}對象數據類型:類,接口,數組
4.變量和常量
變量:數據類型 變量名 [=值];
作用域:
- 類變量(靜態變量) static
- 成員變量(實例變量)
- 局部變量(方法內)先賦值再使用
常量 final MAX_A=10;
Java變量和常量的儲存位置
5.運算符
- 算數運算符 + - * / % ++ --
- 賦值運算符 =
- 關系運算符 > < >= <= == != instanceof
- 邏輯運算符 && || !
- 位運算符& | ^ ~ >> << >>>
- 條件運算符 xx? xx :xx
- 拓展運算符 += -= *=...
6.流程控制
順序結構 程序默認
選擇結構
- if 單選擇
- if-else 雙選擇
- if-else if-else 多選擇結構
- switch case
循環結構
- while
- do…while
- for
- 增強for循環
幾個關鍵字的使用 break 、continue
break語句在switch中是用來中止case的。實際上break語句在for、while、do···while循環語句中,用于強行退出當前循環,為什么說是當前循環呢,因為break只能跳出離它最近的那個循環的循環體,假設有兩個循環嵌套使用,break用在內層循環下,則break只能跳出內層循環,如下:
for(int i=0; i<n; i++) { // 外層循環for(int j=0; j<n ;j++) { // 內層循環break;} }continue語句只能用于for、while、do···while循環語句中,用于讓程序直接跳過其后面的語句,進行下一次循環。
例:輸出10以內的所有奇數
1 public class ContinueDemo {2 3 public static void main(String[] args) {4 int i = 0;5 6 while(i < 10) {7 i++;8 9 if(i%2 == 0) { // 能被2整除,是偶數 10 continue; // 跳過當前循環 11 } 12 13 System.out.print(i + " "); 14 } 15 } 16 17 }這里if條件判斷是否為偶數,如果是偶數則執行continue,直接跳出本次循環,不進行continue后的步驟(即不執行輸出語句),然后下一次循環為奇數,輸出i
9*9乘法表
public static void main(String[] args) {for (int i =1;i <= 9;i++){for (int j=1;j <= i;j++){System.out.print(j+"*"+i+"="+(i*j)+"\t");}System.out.println("");}}return語句可以從一個方法返回,并把控制權交給調用它的語句。
7.方法(函數)
完成特定功能的一部分獨立存在的代碼
-
函數的定義
修飾符 (static) 返回值類型 方法名(參數表){方法體; } -
方法的調用
函數名(參數表) -
方法的參數
(數據類型 形參名1,數據類型 形參名2)形參:是一個在方法內部有效的局部變量賦值的時候會發生在函數調用的時候,也就是實參 例如:static void xiaoWang(){xiaoliu("排骨",8); }static void xiaoliu(String eat,int x){System.out.println("今天給老公做了"+eat+",還買了"+x+"瓶啤酒");} -
方法的返回值
return:程序流程會返回到方法的調用點。
8.方法與JVM結合分析
棧空間:用來存儲函數內部調用時產生的數據。(局部變量)
棧幀:每次函數調用時的數據 局部變量表
嵌套調用分析
public static void main(String[] args){int a =10;m1(a); } static void m1(int b){m2(b); } static void m2(int c){int d = 20; }運行時JVM分析:
局部變量隨棧幀共存亡
遞歸調用分析
當把一個大問題分解成若干個小問題,發現小問題的求解方式和大問題相同,這時候就可以用遞歸來解決。
/*這樣會導致棧溢出*/ public static void main(String[] args){m1(); } static void m1(){m1(); }遞歸必備:回歸條件
計算n的階乘
package com.jichu;/*** @author 王澤* 計算n的階乘* n! = n* (n-1)!*/public class Fact {public static void main(String[] args) {System.out.println(fact(4));}/**計算n的階乘*/static int fact(int n){if (n==1){ return 1;}int result = n*fact(n-1);return result;}}計算斐波那契數列的第n項
static int fobo (int n){if (n == 1){ return 0;}if (n == 2) {return 1;}return fobo(n-1) + fabo(n-2); }9.數組
數組的語法:
1??數組的定義:
//數據類型[] 數組名;int[] a;2?? 定義后JVM不會為其分配內存,所以我們需要為數組分配空間
——隱式初始化
a = new int[5]; JVM會自動給每個數據給一個默認值 0(數值類型)false(boolean) 空指針null(對象類型) ,不同的數據類型分配不同默認值。——顯示初始化
a = new int[ ]{5,6,8,9}; 數組長度由大括號里的數據決定。合并寫法:
int[] a =new int [5]; //隱式int[] a = {5,6,8,9}; //顯示3?? 使用
數組名[下標]?? 常用數組操作
1.查詢數組長度 數組名.length a.length
2.遍歷數組 (按順序訪問數組中的所有元素)
for(int i = 0; i<a.length;i++){操作..... }數組的內存結構
public static void main(String[] args){int[] a = new int[5]; //只要有new 就是在堆空間開辟空間 }JVM中分析
這個數組在堆中的空間是連續的。
new int[5];就是開辟這些空間,然后將首地址賦值給a
數組的空間在內存中是連續的
- 數組的長度固定,不可改變
- 數組的下標從0開始 (追求最快的尋址速度)
- 查詢效率是最高的,增加元素或刪除元素是最低效率
改變數組長度的方法
- 不可能在原數組中改變
- 只能創建一個新數組
- a=b; 把新數組的地址給原數組
- 再把原數組里的元素賦值到新數組b[i]=a[i];
簡寫:java.util.Arrays.copyOf(原數組,新數組長度);
多個數組變量中可以存儲相同地址
package com.jichu;import java.util.Arrays;/*** @author 王澤*/public class ShuZu {public static void main(String[] args) {int[] a;a = new int[5];int[] d = new int[10];for (int i = 0; i < a.length; i++) {a[i]=i;System.out.println(a[i]);}System.out.println("-----------");a = d;System.out.println(a.length);System.out.println("------------");for (int i = 0; i < a.length; i++) {a[i]=i;System.out.println(a[i]);}}}二維數組
int[][] a; //由若干個整形數組組成 a = new int[3][4]; // 長度為三二維數組的遍歷:
for(int i = 0; i<a.length ; i++){for(int j=0; j<a[i].length ;j++){sout(a[i][j]+"\t");} }二維數組的賦值:
int[][] a={{1,2,3,4},{5,6,7,8},{0,5,6,8}}-
int[][] a =new int[3][]; //不指定二維數組長度的
a[0] = new int[5];
a[1] = new int[3];
a[2] = new int[4];
-
int[][] a =new int[][4]; 這句話是錯誤的,不能空缺高維長度
面向對象
?? 面向對象的思想
世界是由對象組成,對象:一切客觀存在的事物。 # 1.對象的組成: - 屬性 對象有什么 - 方法 對象能做什么,行為 # 2.對象之間的聯系 - is a 繼承關系 - has a 關聯關系(一個對象成為另一個對象的屬性) - use a 依賴關系(一個對象調用另一個對象的方法) # 3.面向對象的思想 - 面對需求,我們先找出解決問題的對象,讓對象之間建立適當的關系。💻 面向對象的編程思想
用計算機中的對象,模擬現實中的對象。
用計算機中類型的概念,對應現實世界中的類的概念
類:對象共性的抽象,人對一類事物的認識。 人(類)-李沁(對象)
1.定義一個Java類
成員變量與局部變量對比:(定義位置不同)
- 成員變量有默認值 在對象創建的過程中,屬性會被自動分配到默認值
- 成員比那里的作用范圍,至少是全類內部
- 成員變量和局部變量可以同名,同名時局部變量優先訪問
方法:行為(對象的功能) 函數
方法聲明:
// 修飾符(多個,順序不重要) 返回值類型 方法名(參數表)拋出的異常 public static void main(String[] args)方法實現:{ }
-
構造方法 特殊的方法
- 沒有返回值類型
- 方法名必須和類名相同
- 不允許手工調用,在對象的構造過程中自動調用
- 如果不寫,會自動生成無參構造
-
方法的重載
一個類的同一個行為,由于參數的不同所造成的實現差異,對對象的調用者屏蔽
方法名相同,參數表不同(只有參數名不同,不構成重載)
理解:例如我們有一個人的類,類里有吃的方法,根據方法的重載傳不同的食物對應不同的吃法。
2.對象的創建和使用
對象的創建:
一個類就相當于自定義的數據類型,創建對象就像基礎數據類型一樣的聲明方法,只不過要用new來創建。
通過類創建對象new
new 類名(); //() ---> 構造方法結合JVM對象的創建過程
對象的使用
//類 對象名 = new 類();Girl baby = new Girl(18,"D+"); //baby 存貯對象的地址 baby.屬性; baby.方法();3.變量的JVM分析
簡單變量 VS 引用
看變量類型
- 8種基本類型的變量 簡單變量 存在棧中,存值
- 引用類型(對象,數組…) 存在棧中的是地址,對象存在堆中
成員變量 VS 局部變量
看定義的位置
- 局部變量:函數內部定義,存在棧空間
- 成員變量:函數外部定義,存在堆空間
案例分析:
4.this
特殊的引用,代表對象自身,或者當前對象
this.屬性/方法 代表調用當前對象的屬性/方法
- 可寫可不寫
- 必須寫的時候:局部變量與成員變量同名的時候,并且想訪問成員變量
5.面向對象的三大特性
封裝
任何對象都會有一個明確的邊界,這個邊界對對象內部的數據起到保護隔離的作用。
| 公共 | public | 允許 | 允許 | 允許 | 允許 |
| 受保護 | protected | 允許 | 允許 | 允許 | 不允許 |
| 默認 | 缺省修飾符 | 允許 | 允許 | 不允許 | 不允許 |
| 私有 | private | 允許 | 不允許 | 不允許 | 不允許 |
屬性設置為private,為屬性提供公開的setXXX 和getXXX方法
繼承
is a 關系
Class A extends B
結合jvm分析:
age屬性如果是私有的不可以訪問。
3?? 方法的覆蓋(方法的重寫)
子類用自己的方法實現,替換掉父類繼承給他的方法實現。
語法要求:
- 方法名相同
- 返回值類型相同
- 參數表相同
- 子類和父類相比,訪問修飾符只能更寬或相同
多態
1?? 基礎語法
子類對象可以賦值給父類引用
Animal a;- 編譯時,只能確定a中存儲的必然是Animal對象 或者是Animal的某個子類對象- 運行時,a中的對象類型才會被確定?? 引用類型 a = new 對象類型(); 對象類型可以是引用類型的子類
編譯看左變,運行看右邊(原理):
?? 只能調用引用類型中聲明的方法
?? 通過引用找到對象,方法的結果是對象中的方法運行時,根據對象類型調用子類覆蓋之后的方法
2?? 強制類型轉換(Java是強類型語言)
上面的存在問題如果我想調用m3(),但是因為引用類型是A,所以我們不能調用。如何解決呢?
引用類型為B,B中還得裝B或者B的子類的對象,但是A是父類。所以引出下面原則:
-
子類引用可以直接賦值給父類引用
Animal a = new Dog();
-
父類引用賦值給子類引用的時候必須強轉
Dog d = (Dog) a;
但是如果Animal a = new Cat(); ,那么運行的時候會拋出類型轉換錯誤。為什么會強轉失敗呢??因為對象類型是無法改變的,我們改變的只是引用。
總結:
1.子類引用可以直接賦值給父類引用
2.父類引用可以通過強制類型轉換賦值給子類引用,保證編譯通過,運行時,可能發生類型轉換異常,對象類型不變,無法通過強轉改變對象的類型
3.沒有繼承關系的兩個類的引用之間,不能賦值,即使強轉也不行,編譯不可能通過。
3?? 引出另一個問題?
Animal a = new Dog(); Person p =(Person) a; //為什么這句話是錯的?如果a中裝的是Person 的子類對象,賦值就可能成功;
實際上a中裝的一定是Animal 或Animal 子類對象
由于Java 是單繼承語言,Animal 的子類一定不是Person的子類,所以肯定不能成功。不可能有一個對象既能裝a又能裝p
4?? instanceof 關系運算符
用在強轉之前,避免類型轉換錯誤
引用 instanceof 類名 布爾表達式
判斷 引用中的對象 和 類名 是否兼容
Animal a = new Dog(); a instanceof Dog // true a中引用的對象是不是dog?? a instanceof Cat //false a instanceof Animal //true在強轉之前先用instanceof判斷一下
if(a instanceof Dog){Dog d = (Dog) a; }5?? 多態的典型應用
把不同的子類對象統一放在父類數組中,屏蔽了不同子類的差異。
Animal[] a = new Animal[5]; a[0] = new Dog(); a[1] = new Cat(); a[2] = new Tiger(); a[3] = new Wolf(); a[4] = new Cock();- for(int i =0; i < a.length ; i++){a[i].eat(); }把多態用在方法的參數上
// m(A a) m 方法允許使用A或者A 的某個子類對象作為實參 public static void main(String[] args){feed(new Dog());feed(new Cat()); } static void feed(Animal a){a.eat(); }把多態用在方法的返回值上
public static void main(String[] args){Animal a= getAnimal(); } static Animal getAnimal(){return new Cat(); }6.super
-
引用:指向父類對象
可以訪問父類的屬性(少見)調用父類被覆蓋的方法(常見)
-
用在構造方法中,構造父類對象
1)程序員主動在構造方法第一行寫了super(…)
2)程序員主動的在構造方法的第一行寫了this() ,那么真正第一個執行的還是super
3)構造方法第一行不是super也不是this 那么編譯器會自動為該構造方法第一行添加super
====>任何一個構造方法第一行一定是super()
最先執行的一定是構造父類對象
構造子類對象,必須先構造父類對象,如果代碼沒有執行調用父類哪個構造方法,默認調用父類無參構造方法。
建議給每一個類都寫一個無參構造。
7.JVM分析對象的創建過程
A ---- B ------ C (A是爺爺B是爸爸C是兒子)
創建C對象 --先分配空間
8.單繼承
一個類只能有一個直接父類
為了保證類之間會形成簡單的樹狀結構。
但是java可以通過其他一些方式實現多繼承例如接口
9.static和final
private,static,final都不能和abstract同時出現
9.1 static靜態的
-
成員變量(static修飾的成員變量在內存中有獨自一塊,屬于類)
- 靜態屬性,全類公有,不屬于任何對象。
- 可以直接用 類名.屬性 來訪問
-
方法(靜態方法)
- 可以直接用類名調用
- 靜態方法只能訪問類的靜態成員
- 不能出現this
- 子類可以用靜態方法覆蓋父類的靜態方法 沒有多態
-
初始代碼塊
- 初始代碼塊:初始化過程中會執行,先于構造方法執行
- 靜態初始代碼塊:在類加載的時候執行一次
- 類加載:讀取.class文件并保存到jvm中的過程,在jvm運行的過程中,一個類只會加載一次
? 什么時候會類加載?
第一次創建該累的對象或第一次訪問類的靜態成員 或 加載子類時,也可能是需要先加載父類。
如果僅僅是聲明類的引用,不需要進行類加載
? 類加載的過程?
- 如有必要,先加載父類
- 為類的靜態屬性分配空間,分配默認值
- 按照代碼的順序初始化靜態屬性或是運行靜態初始代碼塊。
9.2 final
-
類 final不能被繼承
-
方法 final修飾的方法不能被子類覆蓋
-
變量 成員變量+ 局部變量
final修飾變量 ===> 常量。只有一次賦值機會。
當final修飾成員變量的時候,系統就不會提供默認值,就必須在初始化屬性或構造方法中賦值一次
9.3 修飾符總結
10.抽象類
10.1 抽象的abstract
-
類
- 抽象類只能聲明引用,不能創建對象
- 供子類繼承
- 例如動物,交通工具 他們的對象都是其子類的對象
-
方法
抽象方法沒有方法體只有聲明,只聲明了對象具有什么功能,沒有定義對象如何實現該功能。
-
抽象類和抽象方法的關系
- 如果一個類具有抽象方法,那么這個類就必須是抽象類
- 抽象類中可以有或沒有抽象方法
-
子類必須==實現(特殊的重寫)==父類中的抽象方法(不想成為抽象類的話)
10.2 依賴倒轉原則(弱耦合)
繼承與多態與抽象的組合延伸
優化后:
抽象類可以作為標準,約定了對象應該具備的方法 規格
隔離了標準的使用者 和標準的實現者 從而實現弱耦合
11.接口
特殊的抽象類 interface
- 所有的方法都是 公開抽象方法 public abstract
- 所有的屬性都是公開靜態的常量 Public static final
- 沒有構造方法
實現接口:implements
接口與抽象類的區別:
- 一個類可以在繼承另外一個類的同時,實現多個接口
- 接口之間可以定義多個繼承關系
多繼承下的多態
JDK1.8+接口的新語法
利用接口實現多繼承。java中的類之間是單繼承。
類:一個累的主要共性 放在父類中
接口:一個類的次要共性,附加信息。
例如手機實現相機游戲機兩個接口。
? 由于接口中都是抽象方法,所以接口作為次要類型,卻不能把方法實現繼承給實現類。
JDK8版本中 接口的語法
JDK9版本中 接口還可以定義私有方法。
開閉原則
一個軟件要對修改關閉,對擴展開放,提高程序的可維護性。
造成可維護性低下的原因是不斷修改代碼,不改代碼,通過更換,拓展軟件組件來滿足需求的變化。
例如我們可以用多態,然后覆蓋原有方法。
接口回調
我們要讓代碼變通用,我寫類,被別人調用。
案例:
interface Callback{ //回調方類A的接口boolean Consider(int money); //思考是否付錢,就是方法cvoid PayFor(int money); //付錢,就是方法c} class TaxiDriver{ //調用方類Bpublic int Answer(Callback callback){ //回答多少錢,就是方法bSystem.out.println("去那的話要100塊");if(callback.Consider(100) == true){callback.PayFor(100);}return 100;} } class Passenger implements Callback{ //回調方類A@Overridepublic boolean Consider(int money) {boolean result = true;if(money > 80){System.out.println(money+"太貴了,您看80行不行?");result = false;}return result;}@Overridepublic void PayFor(int money) {System.out.println("好的,這是您的"+money);}public void TakeTaxi(TaxiDriver td){ //詢問多少錢,就是方法aSystem.out.println("師傅,去天一多少錢?");td.Answer(this);} } public class CallBackTest { @Testpublic void test(){TaxiDriver td = new TaxiDriver();Passenger passenger = new Passenger();passenger.TakeTaxi(td);} }12.內部類
在類的里面可以再定義一個類。
還有一個匿名內部類。
編譯后會形成獨立的.class文件,與外部類分別獨立存在
1?? 成員內部類?
可以訪問外部類的私有成員
用外部類名.this.xxx 訪問外部類的屬性或方法
2?? 靜態內部類?
只能訪問外部類的靜態成員
3?? 局部內部類
定義在方法內部;與局部變量有相同的作用范圍。
可以訪問外部類的私有成員
可以訪問外部類的有效的局部變量,編譯器會將該變量變為final,其值不能給變(只能訪問外部類的局部常量)
4?? 匿名內部類
- 特殊的局部內部類
- 必須用在繼承某個類,或實現某個接口
- 只會創建一次對象
- 可以訪問外部類的私有成員和局部變量
- 不能寫構造方法(因為沒有類名)只有默認公開無參構造方法
匿名內部類的優點:不會打斷程序的思路。不需要寫實現類再創建對象。大量的接口回調都會使用匿名內部類。
缺點:降低代碼的可讀性
二、Java高級
💃 高級篇,很重要!!面試常客!!!
1.Lambda表達式(語法糖)
# 代碼的參數化-----> 形式上,代碼可以直接作為參數傳遞。 # 只能用于函數式接口 # 增加可讀性本質上就是匿名內部類的新穎寫法,如果一個接口中只有一個抽象方法,這個接口稱為**“函數式接口”**。
1??.語法:
# (參數表) ->{方法實現} //利用匿名顳部類創建接口的實現類對象 - 1.參數表中,參數類型也可以省略 - 2.參數表中,如果只有一個參數,圓括號也可以省略 - 3.如果方法的實現只有一條語句(無返回值),‘{}’也可以省略 - 4.如果方法實現只有一條語句,且該方法有返回值,且只有return語句,那么return和{}都可以省略。例如:
Test 是一個interface,里面僅有一個方法test(Student s);/*1.匿名內部類初始寫法*/ Test t1 = new Test(){public boolean test(Student s){return s.getAge()==18;} }/*2.簡化寫法*/ Test t2 = (Student s)->{return s.getAge()==18;};/*3.簡化參數列表*/ Test t3 = s->{return s.getAge()==18;};/*3.簡化方法體*/ Test t4 = s->s.getAge()==18;/*4.在參數中使用*/ /*4.1 以前內部類的使用方法*/ stu = find(ss,new Test(){public boolean test(Student s){return s.getAge()==18;}});/*4.2 lambda 表達式參數使用*/stu = find(ss, s->s.getAge()==18);2.Object類和常用類
💃 每一個開發者都在改變世界。
object類
java中所有類的父親 如果一個類沒有指定父類,默認繼承Object類。
根據多態,如果我們創建一個Object類型的引用,那么可以代表任何對象!
🚆Object o = 任何對象 不包括8中基本類型
🚋 Object類中定義的方法是所有Java對象都具有的方法(public 或 protected)【11個方法】
下面介紹Object中的方法:
| getClass() | 獲得對象的實際類型 | o1.getClass()==o2.getClass()判斷o1和o2類型是否相同 |
| finalize() | 對象垃圾回收時調用 | 對象的全生命周期里最后一個方法就是finalize()。編碼中沒啥用😂 |
| toString() | 返回對象的字符串形式 | 通常重寫此方法,打印o就是打印o.toString() |
| equals() | 判斷兩個對象的內容是否相同 | 首先重寫equals方法,equals(e1.equals(e2)); |
| hashCode() | 返回對象的哈希碼 |
注釋:
-
getClass 與 instanceof 的區別:instanceof判斷對象是不是某一個類型。例如 a instanceof Animal 不知道是狗還是貓。
-
垃圾回收:Java中有垃圾自動收集的機制,由JVM中的垃圾收集器自動回收垃圾對象,釋放對應的堆空間.
? 什么對象會被認為是垃圾對象??
答:零引用算法,如果一個對象沒有任何引用指向它,則這個對象就是垃圾對象。
? 垃圾對象何時被回收?
答:在不得不回收,內存耗盡,無法創建新對象時候,才會一次性回收之前被認定為垃圾對象的對象。
-
System.gc(); 喚醒垃圾收集器,有延遲回收策略,sun公司JDK可能不會啟動。就像是你媽叫你去洗碗,你可以不去,也可以去,僅僅是告訴你。
-
toString 返回對象的字符串形式,通常重寫toString()
-
equals() 與 == 的區別就是,==比的是對象的地址,而equals() 才是比較的真實的對象。
😲 引用之間用==判斷的是地址是否相同(是否指向同一個對象)
🌏 用equals() 判斷得是對象內容是否相同
Object類中的equals方法,依然比較地址,如果我們想讓其比較對象內容,我們需要重寫
//this 和 o對象比較內容 public boolean equals(Object o){//判斷this和o是不是同一個對象if(this == o) return true;//判斷 o是不是 nullif(o == null) return false;//判斷this和o是不是同一個類if(this.getClass() != o.getClass()) return false;//對o做強制類型轉換,變為this類型Employee e = (Employee)o;//逐個比較屬性,基本數據類型用==比較,對象類型用equals比if(this.age == e.age && this.name.equals(e.name)) return true;else return false;//注意這個不是遞歸,兩個equals不是一個,是字符串中的equals }
包裝類
Object o = 任何對象,但是基本數據類型不屬于對象的范圍,所以Object o = 10 是錯誤的!所以我們需要將基本類型轉換。
為8中基本數據類型 各自提供一個類。這時候Object o = 任何數據了。
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
1?? 基本類型和包裝類之間的轉換
-
int——>Integer
/* 不一定要用構造方法獲得對象,可以用靜態方法,用構造方法會造成內存的浪費。而且Integer類提供的靜態方法還能直接返回Integer的;*/Integer a = Integer.valueOf(i); Integer b = Integer.valueOf(i); // a==bInteger.valueOf(i); 獲得一個Integer對象
Integer類內部會預先創建-128 ~ 127 256個對象,如果在這個區間內,不會產生新對象。
-
Integer——>int
a.intValue(); //拆箱
-
從jdk5開始,自動裝箱拆箱,由編譯器自動處理基本類型和包裝類型的轉換。
2?? 基本類型和String之間的轉換
-
int——> String
int i =12;
String s=i+""; 或者 String s=String.valueOf(i);
-
String——>int(重點)
String s =“1234”;
int i = Integer.parseInt(s);
3?? 包裝類和String之間的轉換
-
Integer——>String
Integer a = 1234;
String s = a.toString();
-
String——>Integer
String s =“1234”;
Integer a = Integer.valueOf(s);
案例:統計成績,為了區分0分與缺考。
class Student{Integer score =null;/*對象類型默認是null,如果不給賦值成績那就是null,那就是缺考。如果是0分那么就是0;*/ }日期處理
util包里有一個日期類Class Calendar
3.字符串處理String
凡是沒有數值含義的屬性都不要做成數值,例如手機號。
public final class String我們不能更改,只能用。本質上一個字符串就是一個字符數組的封裝。
字符串中的常用方法
1?? 與字符數組相關的方法
-
String(char[] cs) 利用字符數組cs來構造String
-
toCharArray()把字符串轉正字符數組
把字符串全部變為大寫:因為 大寫+32=小寫String str="HelloWorld";char[] cs = str.toCharArray();for(int i =0; i<cs.length; i++){if(cs[i] >= 'a' && cs[i] <='z'){cs[i] -= 32;}String str2 = new String(cs);System.out.println(str2);}
2??基礎的方法
- toUpperCase(); //轉大寫
- toLowerCase(); //轉小寫
- charAt(int index); //獲得字符串下標
- length() //獲得字符串的長度
- trim() //去掉字符串前后的換行符和空格符
- equals() //比較兩個字符串中的內容
- equalsIgnoreCase(String str) //判斷當前字符串和str內容是否相同,忽略大小寫。例如驗證碼驗證的時候。
3?? 與子串相關
- starsWith(String str) / endsWith(String str) //判斷當前字符串是否以str這個子串開頭 /結尾
- contains(String str) //判斷當前字符串是否包含子串str s.contains("BC");s中有沒有BC子串
- indexOf(String str) 返回str子串在當前字符串中第一次出現的下標,不存在返回-1
- substring(int fromIndex) 找出當前字符串從fromIndex開始,到結尾的子串
- substring(int fromIndex,int endIndex) 返回從from到end的子串
- replace(String str1,String str2) 將字符串中str1替換為str2
- split(String str) 以str為分隔符,將字符串切割為String[]
String常量池
把相同內容的字符串只保留一份。
?? 引入字符串常量池:
一個String對象的內容是不可變的,只有不可變才可以讓多個引用指向同一個對象。如果更改就創建新的對象,引用指向新的。
串池:字符串常量池,用來存放字符串對象,以供多個引用共享(通常存儲在方法區)
創建一個String對象,先去字符串常量池里找,有的話直接引用賦值,沒有的話在串池創建。
如果有new 那么一定是在堆空間中分配內存!
intern() 返回字符串在串池中的對應字符串地址
我們如果用數組的方式創建了String,肯定用到了new String() 這時候肯定在堆空間中,我們可以用intern()來找串池對象地址,然后把引用賦值。s4 = s4.intern();
String s3 =new String(cs).intern();StringBuilder
當字符串的內容改變時,只會創建新的對象。
顯然,費時且占地兒。
用StringBuilder完成字符串的拼接,內容變化時,不會產生新的對象
/*案例:統計兩種方式的運行時間比較*/ static long stringAppend(){long t1 = System.nanoTime();String s= "";for(int i =1; i<= 100000; i++){s +="A";}long t2 =System.nanoTime();return t2-t1; }/*StringBuilder拼接*/ static long stringBuilderAppend(){long t1 = System.nanoTime();String s= "";StringBuilder sb = new StringBuilder(s);for(int i =1; i<=100000; i++){sb.append("A");}s = sb.toString();long t2 =System.nanoTime();return t2-t1; }草,這個結果太慢了,創建對象,垃圾回收太慢了(主要是垃圾回收):
明顯StringBuilder比加的方式快太多了。
4.集合(容器)
集合:用來存儲對象的對象(容器) 。在生活中例如書架…
Object[] 最最最基礎的集合。但是數組有局限性。
1.數組長度是固定的,數組擴充時需要復雜的拷貝操作。
2.數組在元素的插入和刪除時使用不方便
集合:對基礎數據結構的封裝,由Java類庫提供。
集合的組成
1??Collection
-
接口特點:元素是對象(Object)
-
常用方法
方法說明 add(Object o) 把對象o放入當前集合 addAll(Collection c) 把集合c中所有的元素放入當前集合 clear() 清空集合 contains(Object o) 判斷集合中是否存在元素o remove(Object o) 在當前集合中刪除元素o size() 獲取集合的長度 toArray() 將當前集合轉換為Object[] forEach() 遍歷當前集合 -
遍歷
-
迭代遍歷(陳舊了)
/** *Iterator 迭代器 * hasNext() 看集合中還有沒有下一個元素 *next() 從迭代器中拿出下一個沒被遍歷的元素 */ Iterator it =list.Iterator(); while(it.hasNext()){Object o = it.next();String s = (String)o;System.out.println(s.toUpperCase()); } -
for-each jdk1.5
/** *把list中的每個元素放在o里 */ for(Object o : list){String s=(String)o;System.out.println(s.toUpperCase()); } -
自遍歷 jdk1.8
class MyConsumer implements Consumer{public void accept(Object o){System.out.println(o)} } list.forEach(new MyConsumer()); //通用的邏輯//=====>終極版list.forEach(o ->System.out.println(o)); //list.forEach(System.out::println); 方法引用 -
實現類
沒有直接實現類,但是它的子接口有實現類
2??List
Collection的子接口
-
接口特點:元素是有順序,有下標的,元素是可以重復的。
-
常用方法
方法說明 add(int pos,Object o) 把元素o插入到當前集合的pos下標上 get(int pos) 獲得集合中pos下標的元素 indexOf(Object o) 獲得元素o在集合中的下標,如果不存在,返回-1 remove(int pos) 刪除集合中pos下標的元素 set(int pos,Object o ) 把元素o設置到當前集合的pos下標上 -
遍歷
-
下標遍歷
for(int i = 0;i<list.size();i++){Object o =list.get(i);String s =(String) o;System.out.println(s.toUpperCase); } -
迭代遍歷
-
for-each
-
forEach();
-
實現類
1.ArrayList 底層用數組實現 查詢快 ,增刪慢
2.LinkedList 用鏈表實現 查詢慢,增刪快
3.Vector 數組實現 1.0古老版本(舍棄)
與ArrayList的區別:Vector線程安全(不安全好),并發效率低
例如高速公路一直發生事故,那就限制路上只能有一個車。這就是Vector線程安全的樣子。
3??Set
Collection的子接口
-
接口特點:元素是無順序,無下標的,元素的內容不可重復
-
常用方法:無特有,Collection中的方法
-
遍歷:
- 迭代遍歷
- for-each遍歷
- forEach();
-
實現類
存儲原理:不同的實現類,有不同的方法保證元素內容不同
-
HashSet(散列表)查詢也快,增刪也快,占據空間稍大。為了最好的利用,我們要讓其均勻分配。盡量保證不同對象返回不同整數。
底層鏈表數組,不按下標順序存放,是根據hashCode() 與長度取余,放到相應的地方。
-
LinkedHashSet HashSet的子類,元素遍歷的時候可以按順序遍歷
-
TreeSet 自動對元素進行排序,根據排序規則過濾重復元素(很少用)
假如遇到相同的怎么辦???
會先對s3和s4進行內容的比對,如果equals為true,就拒絕加入。如果內容不相同,s4也會放在2下標,并且s3和s4形成鏈表。
現在問題來了:哈希碼不同,即使內容相同也會放入set集合里。這就不能構成不重復。
解決辦法就是我們重寫hashCode(),讓內容相同的哈希碼也相同。
public int hashCode(){return age; //例如用年齡作為哈希碼,那么兩個人就會比較return name.hashCode()+age; }如果將自定義的對象放入HashSet,為了保證內容不重復,我們需要覆蓋equals對象,保證內容相同的對象返回true;還要覆蓋hashCode方法,保證內容相同的對象返回相同的整數,盡量保證不同對象返回不同整數。
4??Map
-
接口特點
元素是鍵值對 鍵對象:key 值對象:value
key:無順序,內容不可重復
value:可重復
-
常用方法
方法說明 put(k key,V value) 把key-value鍵值對放入Map,如果key已經存在,新的value覆蓋原有的value V get( k key) 查找key所對應的value containsKey(K key) 判斷Map中是否存在key這個鍵 containsValue(V value) 判斷Map是否存在value這個值 size() 返回Map的長度,鍵值對的個數 remove(K key) 刪除key 所對應的鍵值對 keySet() 返回Map中所有key的集合 就是一個Set values() 返回map中所有值得集合 就是一個Collection -
遍歷
-
keySet()
Set<Integer> ks = map.keySet(); for(Integer key :ks){String calue = map.get(key);System.out.println(key +":"+value); } -
values() 遍歷所有的值組成的集合
Collection<String> vs = map.values(); for(String value :vs){sout(value); } -
自遍歷
map.forEach( (k,v)->System.out.println(K+"---"+v) ); -
實現類
-
HashMap
與hashSet類似,就是hashMap有值。 鏈表數組,高版本中修改了,用的紅黑樹。在原始基礎上做了優化。
-
linkedHashMap HashMap的子類,維護鍵值對的添加順序
-
TreeMap 自動對key做排序
-
Hashtable 1.0線程安全 慢 不允許用null作為key 或value
-
Properties Hashtable的子類 key和value都是string,通常用語早起的配置文件處理
總結圖:
藍線表示學習的時候對比記憶。
泛型
集合存儲是Object,無法對元素的類型做出約束,導致類型不安全。
利用泛型,可以預定集合中元素的類型。
interface A <T,V>{void m1(); } /*使用的時候*/ class B implements A<Double , String>{/*誰使用,誰指定*/ } --------------------------------------------------------------集合中使用泛型: interface LIst<E>{void add(E o);E get(int pos); } List<String> ls=new ArrayList<String>(); ls.add("abc"); String s=ls.get(0);LIst ls = new ArrayList();
必須:Type1==Typ2
List ls = new ArrayList();
既然前后必須一致,那么后面可以省略;
LIst ls = new ArrayList<>(); Jdk7
線程安全的集合
ArrayList 所有方法都不是同步方法。
Vector 所有方法都是同步方法。
1?? ConcurrentHashMap?
如何既保證線程安全,又保證并發效率很高?
JDK7 以前,采用分段鎖。控制鎖的力度,把Map分為16個片段,針對每個片段加鎖。
(舉例:map是一個大廁所,有很多小隔間,一個人進去直接把大門鎖了,顯然影響效率,而ConcurrentHashMap就相當于鎖小隔間的門,只會鎖其中的一個片段。)
JDK8以后,采用CAS算法(比較交換),無鎖算法,存儲效率非常接近于HashMap.
2?? CopyOnWriteArrayList 永遠不會改變原始集合中的數據
適用于讀操作。并發效率接近于ArrayList,線程安全
- 讀操作:獲取集合的數據(不需要加鎖)
- 寫操作:改變集合的數據( 復制新的集合來實現寫,效率低 )
3??CopyOnWriteArraySet 原理和CopyOnWriteArrayList一致
隊列Queue
隊尾進,對頭出。
1??LinkedList 同時也實現了隊列接口
Queue<String> queue = new LinkedList<>();線程不安全的類。
2?? ConcurrentLinkedQueue
線程安全的 CAS無鎖算法
BlockingQueue 阻塞隊列,隊列的子接口:當隊列為空的時候,從隊列中取元素的線程會阻塞,而當隊列滿的時候,向隊列中添加元素的線程會阻塞
- 有界隊列:ArrayBlockingQueue有上限,存在滿的問題,當隊列滿的時候,添加元素線程會阻塞
- 無界隊列:LinkedBlockingQueue
集合總結:
5.異常
提高程序的容錯性:解決程序運行時的問題
異常的分類
避免異常:盡可能使異常不出現,不發生
處理異常:當異常發生時,應對這種異常
Throwable 所有異常的父類
? |- error 錯誤 嚴重的底層錯誤,不可避免 無法處理
? |- Exception 異常 可以處理
? |- RuntimeException 子類 運行時異常 未檢查異常 可以避免(空指針、數組下標越界、類型轉換異常) 可以處理可以不處理
? |-非 RuntimeException子類 已檢查異常 不可避免,需要處理異常
異常對象的產生和傳遞
異常對象的產生:
throw 異常對象:拋出一個異常
拋出異常:方法以異常對象作為返回值,返回等同于return
后面的語句就不打印了
異常對象的傳遞
沿著方法調用鏈,沿著方法的調用鏈,逐層返回,直到JVM
這是中止運行,jvm會顯示異常名,異常內容和異常路徑
自定義異常:
如果想寫已檢查異常需要extends Exception
如果想寫未檢查異常需要extends RuntimeException
異常的處理
聲明拋出
public void method() throws IOException
throws 異常類名1,異常類名2,…異常類名n
意思:本方法中出現IO異常,我不做任何處理,拋給上層調用處理!
方法覆蓋時,子類不能比父類拋出種類更多的異常
捕獲異常
try{語句1語句2...//遇到異常 去catch處理 } catch(異常類型 e){} catch(異常類型 e){e.printStackTrace();//打印異常對象的棧追蹤信息 } finally{這里面的無論有沒有異常都運行 } ......可以同時捕獲父類異常和子類異常,但是必須先捕獲子類異常,再捕獲父類異常。
Java中,有三種try-catch結構:
try{} catch(){}try{} catch(){} finally{}try{} finally{} //無法捕獲異常,利用finally必須執行的特點完成特定操作6.B IO
Java程序的輸入和輸出
IO流的分類
1?? 流的概念:用來傳遞東西的對象
2?? 流的分類:
-
流的方向:輸入流(讀取數據)/輸出流(寫數據)
-
數據單位: 字節流(以字節為單位,可以處理一切數據)
字符流(字符為單位 = 2字節 ,用來處理文本數據)
-
按照流的功能:
節點流:實際負責數據的傳輸
過濾流:為節點流增強功能
IO編程的基本順序:
字節流
InputStream :字節輸入流 抽象類,字節流的父類
OutputStream: 字節輸出流 抽象類,字節流的父類
子類:文件字節流
- FileInputStream/FileOutputStream
寫文件
/*輸出流*/ public class IOStream {public static void main(String[] args) {try {OutputStream outputStream = new FileOutputStream("a.txt");outputStream.write('A'); //向a.txt中寫一個AoutputStream.close(); //關閉流(這個不該寫在這里!后面有講)} catch (FileNotFoundException e) {System.out.println("文件沒找到");} catch (IOException e) {e.printStackTrace();}} }OutputStream outputStream = new FileOutputStream("a.txt",true);追加的方式來打開流
如果wirte(‘王’);漢字已經超過了字節的范圍
讀文件
先不考慮異常的情況下讀文件:
public class DuFile {public static void main(String[] args) throws IOException {InputStream inputStream = new FileInputStream("a.txt");while(true){int a = inputStream.read();if(a == -1){break;}System.out.println((char) a);}inputStream.close();}}IO流處理異常的方法
常規處理:
public class IOStream {public static void main(String[] args) {OutputStream outputStream = null;try {outputStream = new FileOutputStream("a.txt",true);outputStream.write('D'); //向a.txt中寫一個A} catch (IOException e) {e.printStackTrace();} finally {if (outputStream != null){try {outputStream.close(); //關閉流} catch (IOException e) {e.printStackTrace();}}}} } 代碼缺點:太多,麻煩 jdk1.7 提供了 try-with-resourcetry-with-resource:帶資源的都需要關閉,一定在finally中。
try(定義資源 必須是實現類 AutoCloseable接口的對象){ 代碼} catch(Exception e){} //定義在try里的資源會自動關閉寫文件異常處理寫法:
public class IOStream {public static void main(String[] args) { // OutputStream outputStream = null; // try { // outputStream = new FileOutputStream("a.txt",true); // outputStream.write('D'); //向a.txt中寫一個A // } catch (IOException e) { // e.printStackTrace(); // } finally { // if (outputStream != null){ // try { // outputStream.close(); //關閉流 // } catch (IOException e) { // e.printStackTrace(); // } // } // }try(OutputStream outputStream = new FileOutputStream("a.txt",true)){outputStream.write('A');} catch (IOException e) {e.printStackTrace();}}}讀文件異常處理
public class DuFile {public static void main(String[] args) {try (InputStream inputStream = new FileInputStream("a.txt")) {while (true) {int a = inputStream.read();if (a == -1) {break;}System.out.println((char) a);}} catch (IOException e) {e.printStackTrace();}} }文件拷貝
public class FileCopy {public static void main(String[] args) {FileOutputStream fos = null;FileInputStream fis = null;//要復制的文件String filename = "a.txt";try{fis = new FileInputStream(filename);fos = new FileOutputStream("new"+filename);while(true){int a = fis.read();if (a == -1){break;}fos.write(a);}} catch (IOException e) {e.printStackTrace();}finally {if( fis !=null) {try {fis.close();} catch (IOException e) {e.printStackTrace();}}if( fos !=null) {try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}}IO效率最重要!,但是上面的代碼執行效率很低,接下來就是提升性能的方法——加一個過濾流
緩沖字節流
過濾流:只能給節點流增加功能
PrintStream可以取代BufferedOutputStream帶緩沖的字節輸出流,這個就是sout使用的。
System.out.println
- System:類名 java.long.System
- System.out:System類中公開靜態常量,是PrintStream類的對象
- println:方法名
BufferedInputStream/BufferedOutputStream 緩沖功能,提高I/O效率
一個流中的構造參數是另外一個流,這個流就是一個過濾流
flush() //關流或者清空緩沖區都可以讓緩沖區的該去哪去哪 對于緩沖輸出流
改造后的文件拷貝:
public class FileCopy {public static void main(String[] args) {FileOutputStream fos = null; //節點流FileInputStream fis = null; //節點流BufferedOutputStream out = null; //輸出緩沖流,這是過濾流BufferedInputStream in = null; //輸入緩沖流//要復制的文件String filename = "a.txt";try{fis = new FileInputStream(filename);in = new BufferedInputStream(fis); //加強節點流的功能,帶緩沖fos = new FileOutputStream("new"+filename);out = new BufferedOutputStream(fos);//帶緩沖的輸出流while(true){int a = in.read(); //用換出流讀數據,存到緩沖區if (a == -1){break;}out.write(a); //緩沖輸出流王外寫}} catch (IOException e) {e.printStackTrace();}//關流的時候只需要關最外層的流,例如過濾流包裹節點流,關過濾流就行finally {if( in !=null) {try {in.close();} catch (IOException e) {e.printStackTrace();}}if( out !=null) {try {out.close();} catch (IOException e) {e.printStackTrace();}}}}}對象序列化
我們想把對象寫入文件要怎么寫呢?
把對象通過流來傳輸叫做對象的序列化。
有些對象可以序列化,有些不能。生活中的例子,例如我搬家的時候,有的可以拆成小的然后搬,有的卻不行。
**只有實現了Serializable的接口才能序列化。**就是個標記,告訴IO可以切(序列化)
可以給對象加transient修飾屬性,稱為臨時屬性不參與序列化
我們有相應的過濾流:ObjectOutputStream/ObjectInputStream
有out.wirteObject();方法是寫對象的。
readObject(); 讀對象。
對象的克隆
就是在java堆空間中復制一個新對象;
最簡單的辦法就是用Object中的clone(); 但是要重寫,修改修飾符。而且對象必須是一個支持克隆的對象實現Cloneable接口。
注意:他的克隆方式:淺拷貝
teacher指向的同一個對象。
淺拷貝:對象復制,但是對象內的屬性不會復制
深拷貝:對象復制,里面的屬性也復制,就會有兩個teacher對象。
如何實現深拷貝?對象序列化。這樣的地址完全不同
字符流
專門處理char,String數據,而且可以方便的處理字符的編解碼
‘A’ ----> 65 :編碼 str.getBytes("GBK")
65 ----> ‘A’ :解碼 String( ,"GBK")
不同國家有不同字符集,當編碼格式與解碼格式不同就會出現亂碼現象。
- ASCII 美國 字符集:英文字符
- ISO-8859-1 西歐字符
- GB2312 GBK簡體中文
- Big5 繁體中文
- Unicode 全球統一的編碼方式
- UTF-16(java采用的編碼)
- UTF-8 (常用的)
Reader/writer
抽象類 字符流的父類
子類:FileReader/FileWriter 文件字節流,節點流
BufferedReader/BufferdWriter 過濾流,緩沖作用
PrintWriter帶緩沖的字符輸出流,通常可以取代BufferdWriter
讀:
技巧:先創建字節流然后用InputStreamReader/OutputStreamWriter過濾流把字節流構造成字符流 (橋轉換類)
可以在橋轉換的過程中指定編解碼的方式
OutputStream fos = new FileOutputStream("a.txt"); Wrirer fw = new OutputStreamWriter(fos,"GBK");File類
file對象代表了磁盤上的一個文件 或 目錄(文件夾),用來實現一些文件操作。
public class FileTest {public static void main(String[] args) throws Exception {File file = new File("1.txt");file.createNewFile();//創建1.txt文件file.delete();//刪除1.txt //空目錄file.mkdirs(); //創建目錄file.delete(); //刪除目錄System.out.println(file.exists());//file.exists();判斷是否存在file.getAbsolutePath();//獲得絕對路徑file.lastModified();//獲取最后修改時間file.length();//獲取長度File f = new File("D:\\");//file.listFiles();//返回文件數組}}無法訪問文件內容。
7.多線程(并發編程)
注重思想,我們寫一個頁面,如果大量用戶訪問,那么有上億個線程,是否會出現數據出錯,卡頓等。
并發編程:多個任務同時執行
進程:OS中并發的一個任務
線程:在一個進程中,并發的順序執行流程。
并發原理:CPU分時間片交替執行,宏觀并行,微觀串行,由OS負責調度。如今的CPU已經發展到了多核CPU,真正存在并行。
線程的三要素:
- CPU OS負責調度
- 數據 堆空間,棧空間(多線程堆空間共享,棧空間獨立)
- 代碼 (主函數就是主線程)
如何創建線程?
方法1(初始):
實現Runnable接口,實現run方法
創建Runnable對象(任務對象)
通過Runnable對象,創建Thread對象(線程對象)、
現在只是有線程對象:
調用線程對象start()啟動線程(向操作系統申請創建線程)
現在的代碼中就有三個線程了,main,t1,t2
方法2(老式):
線程的基本狀態
1??程序最開始的時候主線程處于運行狀態
2?? 主線程執行創建對象,t1,t2處于初始狀態
3?? 調用start()
4?? main結束,OS調度,運行run里的代碼,選誰呢??
選擇的規則:不同OS不同任務調度策略。但是因為start()耗時,所以經常看見t1先執行,是因為這時候t1還沒準備好
5?? t1時間片到期,t1回到可運行,OS選擇下一個線程(不一定選誰)可能還是t1。
6?? 線程結束
線程的等待
等待狀態:線程執行過程中發現執行不下去了,缺少條件,沒有條件之前進入等待狀態。(例如等紅綠燈)
-
等待數據的輸入 scanner/B io 不確定時間
-
sleep() 限時等待,給休眠時間
線程中的sleep()異常只能用try-catch解決,因為方法覆蓋不能拋出比父類多的異常。
還有一個join() 線程同步中講解。
線程池 JDK5
創建一個線程只為一個任務所服務會造成資源浪費。(頻繁創建銷毀線程就是資源浪費)
線程池的概念:(就像高鐵站出租車區域,來客人就去拉客,送到后還回來繼續等下一個任務,或者辦公室的例子)當一個任務結束時,對應的線程資源不會銷毀,而是回到線程池中等待下一個任務。
**線程池的好處:**可以控制最高并發數
有了線程池之后,Thread類就不推薦使用了!
線程池的編碼:
一個ExecutorService對象就代表一個線程池
newCachedThreadPool();長度不固定的,有幾個任務就有幾個線程
用匿名內部類簡化編碼
executorService.submit(new Runnable() {@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println("CCC"+i);}}});用lambda表達式簡化
executorService.submit(()->{for (int i = 0; i <= 100; i++) {System.out.println("CCC"+i);}});Callable和Future接口
用來取代Runnable接口、
Runnable的缺點:
Callable接口允許線程有返回值,也允許線程拋出異常
Future接口用來接受返回值
案例:計算1~10000的和,用兩個線程,一個算基數,一個算偶數,最后相加。
主要問題:如何獲得返回值
返回值存在Future對象中
package com.gaoji;import java.util.concurrent.*;import static java.util.concurrent.Executors.*;/*** @author 王澤*/public class AddTest {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService es = newFixedThreadPool(2); // es.submit(new task1()); // es.submit(new task2());//如何拿到兩個任務的返回值呢?我們只是扔給了線程池Future<Integer> f1 = es.submit(new task1());Future<Integer> f2 = es.submit(new task1());//...主線程執行自己的任務//獲取返回值,能取到就取,取不到就等待。Integer result1 = f1.get();//從future中取到返回值Integer result2 = f2.get();//從future中取到返回值System.out.println(result1+result2);es.shutdown();}} /**所有基數的和*/ class task1 implements Callable<Integer>{@Overridepublic Integer call(){int result = 0;for (int i = 1; i < 10000; i+=2) {result +=i;}return result;} } /**所有偶數的和*/ class task2 implements Callable<Integer>{@Overridepublic Integer call(){int result = 0;for (int i = 2; i < 10000; i+=2) {result +=i;}return result;} }線程的同步(重點)
兩個線程同時往一個集合中放元素,
當一個線程對另一個線程join()的時候,這個線程就會等待狀態
例如:主線程中寫t1.join() t1結束,主線程才繼續。
t1.join(); //mian線程等待t1結束引出問題案例分析:
正確的過程(假設t1先啟動):
實際上的順序
因為sleep,出現了上面的情況,但是不sleep依舊有可能會出現(要執行index++的時候時間片到期了)
多線程共同訪問同一個對象,由于多線程中堆空間共享,如果破壞了不可分割的操作,就可能導致數據不一致。
👨?🚀 臨界資源:被多線程共同訪問的對象
💁?♂ 原子操作:不可分割的操作,要么全部執行要么都不執行
怎么解決呢?——加鎖
synchronized
開發中不鼓勵使用
//對obj對象加鎖的同步代碼塊 synchronized (obj) {}Java中,每個對象都有一個互斥鎖標記(monitor),用來分配一個線程
只有拿到對象obj鎖標記的線程,才能進入到obj加鎖的同步代碼塊。
t2synchronized (obj){t1//即使sleep,t2依舊進不來 } t2想進來進不來,這就是阻塞狀態了必須進入到所標記代碼塊才能執行,線程離開同步代碼塊時,會釋放對象的鎖標記。
// 修電線的代碼: synchronized (閘盒對象){將閘盒變為OFF爬上電線桿檢修電路爬下電線桿將閘盒變為ON } 只有這一套完成后,別人才能拿到閘盒的鑰匙。線程的阻塞狀態
線程安全的對象
該對象成為臨界資源,被多線程訪問時,不會發生原子操作被破壞,導致數據不一致的問題。
鎖的是對象
Girl :對象 Boy:線程 拿到Girl鎖標記的Boy,成為Girl的男朋友synchronized(girl){擁抱;接吻;壁咚;......... }// 分手之后,才能讓別人成為男朋友同步方法
只有獲得對象obj所標記的線程,才能調用obj的同步方法
調用之前競爭obj的鎖標記
方法調用結束時,會釋放obj的鎖標記
public class A{public synchronized void m(){} //同步方法 對this加鎖的同步代碼塊 } A a1 = new A(); A a2 = new A(); a1.m(); //線程獲得a1的鎖標記 a1.m();//線程獲得a2的鎖標記 synchronized void m(){} ====== void m(){synchronized(this){} }死鎖
synchronized不僅僅會造成并發效率低,還會造成死鎖問題。
synchronized(a){//t1synchronized(b){} } ------死鎖了synchronized(b){//t2synchronized(a){} }死鎖靠線程之間的通信來解決,t1調用Object中的wait(),會釋放t1所有鎖標記,進入等待狀態。 t2調用notify,通知所有調用過wait()的線程,離開等待狀態。
wait() 和sleep() 有什么區別??
都會讓線程進入等待狀態,wait會釋放鎖標記,sleep不會釋放鎖標記。
(電話亭打電話,你在里面睡著了,鎖依舊是鎖著,這是sleep,如果無人接聽,你先出去讓別人打,這就是wait)
避免多線程訪問臨界資源,造成數據不一致的辦法:
8.反射和設計模式
反射是底層技術 通常用來實現框架和工具
運行時的類型判定。
目前創建對象都是采用硬編碼new Dog 。
類對象
1??類加載:當JVM第一次使用一個類的時候,它需要將這個類對應的字節碼文件讀入JVM,從而獲取這個類的全部信息(類的父類,擁有哪些屬性,方法,構造方法等等)并保存起來(保存在JVM方法區)
2??類對象:類加載的產物,包含了一個類的全部信息。
3?? 類對象所屬的類叫 Class類
- 類的對象:該類的對象 Dog類的對象---->一只狗
- 類對象:記錄類信息的對象 Dog類對象,相當于動物園的牌子
- 類加載:就相當于你不認識這個動物,你把這個動物是啥記憶在腦子里
??獲得類對象的三種方式:
-
類名.class 直接獲取類對象,也可以獲取到8種基本類型的對象
-
類的對象.getClass() 獲取類對象,例如你去動物園看見一直候,你問他你啥對象??🐒:自己看牌子!
-
class.forName(“類的全名”) 通過類名字符串獲得類對象
一個類的類對象只能有一個,在JVM中只會產生一個
拿到了類對象之后 獲取類的信息
類對象.getName() //查看類名
類對象.getSuperclass().getName() //查看父類名
Class[] cs = 類對象.getInterfaces();//獲得一個類實現的所有接口
屬性方法構造方法又會被封裝
getMethods() //獲取所有公開的方法的Method對象(子類父類都可以拿到)
getDecalredMethods()//獲得本類中所有方法的Method對象
getMethod()/getDeclaredMethod() //根據方法名和參數表,獲得類中的某個方法
Class:封裝了類的信息
Method:封裝了方法的信息
3??獲取類的對象我們可以對類對象調用newInstance() 通過類對象,創建類的對象(手里有個美女介紹,我直接newInstance 直接來一個,美女的對象)
String classname="com.girl.FullMoneyGirl"; //類名 Class c = Class.forName(classname); //獲得類對象 Object o = c.newInstance(); //創建類的對象4?? 調用方法
利用Method對象.invoke() 通過Method調用該方法
String methodname="look"; //方法名 Method m = c.getMethod(methodname); //通過類對象c獲取方法 m.invoke(o);調用對象的私有方法
String methodname="ppp"; //方法名 Method m = c.getDeclaredMethod(methodname); //通過類對象c獲取方法 m.setAccessible(true); m.invoke(o);三、設計模式
🗡共有23種設計模式,好書推薦:《java與模式》
1.單例設計模式
編寫一個類,這個類只能有一個對象。
1?? 餓漢式
ClassA a1 = ClassA.newInstance(); //獲得對象class ClassA{private static ClassA instance = new ClassA(); //先創建一個對象,作為靜態的屬性,ClassA類的唯一對象public static ClassA newInstance(){return instance;}//調用靜態方法 返回已經創建的對象private ClassA(){} //防止用戶用構造方法創建對象導致不是單例 }應用場景:比較常用,不會有并發問題,但是浪費了存儲空間
2??懶漢
對象的創建時機不一樣,當需要對象時,創建對象,有并發效率問題
class ClassB{private static ClassB newInstance(){if(instance == null) instance = new ClassB();return instance;}private ClassB(){} }如果是多線程調用的話,這種懶漢模式就不是單例的。考慮并發的問題,導致數據不一致。
/*啟動兩個線程,創建懶漢模式*/ new Thread(()->ClassB.newInstacnce()).start(); new Thread(()->ClassB.newInstacnce()).start();正確寫法:class ClassB{private static ClassB instance = null;public static synchronized ClassB newInstance(){if(instance == null) instance = new ClassB();return instance;}private ClassB(){} }但是加了鎖之后,就降低了并發效率。
3?? 集合了餓漢式懶漢式的優點
//類加載的時候線程安全,只有需要的時候才創建對象。 //Holder只會加載一次 class ClassC{static class Holder{static ClassC instance = new ClassC();} public static ClassC newInstance(){return Holder.instance;}private ClassC(){} }2.工廠模式
使用反射進行對象創建,IO流讀取文件
開閉原則:軟件對修改關閉,對拓展開放。
利用工廠創建對象。
public static Animal createAnimal(){String classname = null;Properties ps = new Properties();try(FileInputStream fis = new FileInputStream("config.txt")){ps.load(fis);classname = ps.getProperty("animal");class c = Class.forName(classname);Object o = c.newInstance();return (Animal)o;}catch(Exception e){e.printStackTrace();return null;} }四、常見算法
1.漢諾塔問題
? 有A B C 三個柱子,和若干個大小不同的圓盤,起初圓盤都在A柱上,小盤在上。現在要把A柱的盤子轉到B柱上。(每次只能移動一個,且小盤子在上面)
思考過程:把大問題化小。如果A柱上有兩個盤子,先把A上的小盤子移動到C,然后把A 上的大盤子移動到B ,然后把C柱上的小盤子移動到B。 這樣就是每次移動一個盤子。下面是步驟:
先移動(n-1)個盤子 從A 到 C -----小問題
將最底層大盤子移動到B
n-1 個盤子 從C 到 B -------大問題
設計函數
package com.jichu;/*** @author 王澤*/public class HanNuoTa {public static void main(String[] args) {transfer(5,'A','B','C');}/*** 漢諾塔方法:* n:移動幾個盤子*form:從哪個柱子離開*to:移動到哪個柱子*temp:借助哪個柱子* */static void transfer( int n, char from , char to, char temp){//返回條件if (n == 0) {return;}//把n-1個盤子從 from 移動到temp 借助 totransfer(n-1,from,temp,to);//將大盤子從from 移動到 toSystem.out.println(from+"----->"+to);//把n-1 個從temp 移動到 to 借助 fromtransfer(n-1,temp,to,from);}}2.排序
1.冒泡排序
用相鄰的兩個元素比較,按照排序要求對調位置。
👀 我們會發現,每一輪冒泡排序會把最大(最小)的排到最后,第二次冒泡排序就不包含最后一個。n個元素要做n-1次排序。
package com.suanfa.paixu;/*** @author 王澤*/public class MaoPao {public static void main(String[] args) {int[] a = {3,8,6,9,2,7};System.out.println("數組的長度=="+a.length);//冒泡排序for (int i = 0; i < a.length-1; i++) {//相鄰元素比較for (int j = 0; j <a.length-1-i ; j++) {if(a[j]>a[j+1]){int temp = a[j];a[j]=a[j+1];a[j+1] = temp;}}}//打印for (int i = 0; i < a.length; i++) {System.out.println(a[i]+" ");}}}五、常見機試題
🤑 主要用于提升編碼能力,重要的編碼在于集合與IO。更新中。。。。
六、常見面試題
🌓本章節羅列javaSE相關高頻面試題,每一道題基本都可以在上面的筆記中找到答案。面試題目答案可私聊。
總結
以上是生活随笔為你收集整理的[ 4w字 ] JavaSE总结(基础+高级+多线程+面试题)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pycharm中更新pip版本的问题
- 下一篇: Mac 系统引导过程概述 BootCa