JDK8
復習用
Java前置知識
-
JavaSE
Java Standard Edition 標準版
支持面向桌面級應用(如Windows下的應用程式)的 Java平臺,提供了完整的Java核心API
此版本以前稱為 J2SE
-
JavaEE
Java Enterprise Edition 企業版
一套用于企業環境下的應用程式的應用方案(包含:Servlet、Jsp),主要針對 Web應用程式的開發
此版本以前稱為 J2EE
-
JavaME
Java Micro Edition 小型版
支持 Java程式 運行在移動端的版本,對 JavaAPI 有所精簡,并且加入了針對移動端的支持
此版本以前稱為 J2ME
-
JDK
Java Development Kit Java開發工具包
JDK = JRE + Java開發工具(java、javac、javap、javadoc等)
-
JRE
Java Runtime Environment Java運行環境
JRE = JVM + Java核心類別庫
-
JVM
Java Virtual Machine Java虛擬機
JVM 是一個虛擬的計算機,具有指令集并使用不同的存盤區域,負責執行指令,管理資料、記憶體、暫存器
對于不同的平臺有不同的虛擬機,屏蔽了底層運行平臺的差別,實作了“一次編譯,到處運行”
-
Java運行簡單的大致流程
Java檔案 通過 javac 編譯為 Class檔案,再通過 java指令 交給 JVM虛擬機 執行
-
Java中特殊的轉義字符
\r: 游標定位到行首public class Main { public static void main(String[] args) { System.out.println("aaaaaaa\rbb"); } }-
在 IDEA 中 會清空這一行并把游標放到這一行的首位
上方代碼在IDEA中輸出:bb
-
在 windows終端 中,只會幫游標定位到該行的行首
上方代碼在Windows終端中輸出:bbaaaaa
-
-
Java程式 中
+號的使用- 當
+號 的 左邊和右邊 都是數值型時,視為加法運算 - 當
+號 的 左邊和右邊 有一方是字串時,視為拼接運算 - 運算順序:從左往右
System.out.println(100 + 98); // 輸出: 198 System.out.println("100" + 98); // 輸出:10098 System.out.println(100 + 3 + "hello"); // 輸出:103hello System.out.printIn("hello" + 100 + 3); // 輸出:hello1003-
當
+號 的 左邊和右邊 有一方是char字符時,視為字符編碼值的運算由于 Java 中 char 底層存盤的是 對應字符的Unicode碼值,列印時會對照Unicode表列印出對應的字符
因此,對char字符的運算相當于對char字符對應的Unicode碼值進行運算
但當有一方是字串時,仍然視為拼接運算
System.out.println(1 + 'a'); // 98 System.out.println('a' + 1); // 98 System.out.println('a' + 'b'); // 195 (97+98=195) System.out.println('a' + "bc"); // abc
- 當
-
JavaAPI
- API : Application Programming Interface 應用程式編程介面
- JavaAPI : Java提供的基本編程介面
- JavaAPI檔案 : Java語言提供了大量的基礎類,Oracle公司也為這些基礎類提供了相應的AP檔案用于告訴開發者如何使用這些類,以及這些類里包含的方法
- switch陳述句 中運算式的值,必須是:byte、short、int、char、這4個基本資料型別的包裝類物件、String物件、列舉物件
Java變數
不論使用哪種高級程式語言撰寫程式,變數都是程式的基本組成單位
-
變數三要素:
型別、名稱、值
-
變數相當于記憶體中一個存盤空間的表示,即 宣告變數時就是先開辟了一定空間的空房間,變數相當于門牌號,通過 門牌號 可以找到目標房間,從而放入、取出房間中的填充物(也就是變數值),一個房間是有最大容量限制的,超過這個容量就會導致溢位
- 變數型別的不同,其占用記憶體空間的大小也不同
- 此區域有自己的變數名、資料型別,且可以在同一型別內不斷變化
- 變數必須先宣告后使用
- 變數在同一個作用域內,不能重名
-
資料型別
-
基本資料型別
-
數值型
-
整數型別
- byte : 1個位元組
- short : 2個位元組
- int : 4個位元組
- long : 8個位元組
-
浮點數型別
- float : 4個位元組
- double : 8個位元組
浮點數計算陷阱:
2.7 和 8.1/3 的值在計算機中會不相等
因為在二進制方面 8.1/3 是無限小數,double的精度太低,導致在記憶體中存盤的 2.7 和 8.1/3 的值在二進制層面不相等
-
-
字符型
- char : 2個位元組(Java中可以使用2個char來表示1個字符,用于彌補單個char存盤空間小的限制)
-
布爾型
- boolean : 1個位元組
-
-
參考資料型別
- 類
- 介面
- 陣列
-
-
字符型別
-
字符型別(char)可以表示單個字符,占用兩個位元組(可以存放漢字),多個字符使用字串(String類)
-
String類底層仍然是多個char
-
Java中的char本質存盤的是Unicode表中,對應字符的整數碼值,因此一些特殊符號(
\n、\t)也可以賦值給char,使對應的char在列印時具備對應特殊符號的功能
-
-
布爾型別
- 布爾型別也叫boolean型別,booolean型別資料只允許取值true和false,不允許賦予null值
- boolean型別占1個位元組
- boolean型別適于邏輯運算(if、while、do-while、for等)
-
基本資料型別的相互轉換
-
自動型別轉換
flowchart LR byte --> short --> int char --> int int --> long --> float --> double當java程式在進行賦值或者運算時,精度小的型別自動轉換為精度大的資料型別,這個就是自動型別轉換
-
注意事項
-
有多種型別的資料混合運算時,系統首先自動將所有資料轉換成容量最大的那種資料型別,然后再進行計算
-
當我們把精度(容量)大的資料型別賦值給精度(容量)小的資料型別時,就會報錯,反之就會進行自動型別轉換
-
(byte,short)和char之間不會相互自動轉換
flowchart LR byte & short x--無法自動轉換---x char -
byte、short、char他們三者可以計算,在計算時首先轉換為int型別
-
boolean不參與轉換
-
自動提升原則:運算式結果的型別自動提升為運算元中最大的型別
-
-
-
強制型別轉換
自動型別轉換的逆程序,將容量大的資料型別轉換為容量小的資料型別
使用時要加上強制轉換符(),但可能造成精度降低或溢位,格外要注意
-
資料型別從大—>小(或多型的向下轉型)時,就需要使用到強制轉換
-
強轉符號只針對于最近的運算元有效,實際中經常會使用小括號提升優先級
-
char型別可以保存int的常量值,但不能保存int的變數值,需要強轉
char c1 = 100; //ok int m = 100; //ok char c2 = m; //錯誤 char c3 = (char)mi; //ok System.out.printIn(c3):/100對應的字符 -
byte、short、char型別在進行運算時,會被當做int型別處理
-
易錯題
short s = 12; //ok s = s - 9; // 錯誤 int -> short byte b = 10;//ok b = b + 11; // 錯誤 int -> byte b = (byte)(b+11); //正確,使用了強轉 char c = 'a'; // ok int i = 16; // ok float d = .314F; // ok double result = c i d; // ok float -> double byte b = 16;// ok short s = 14;// ok short t = s + b; // 錯誤 int -> short
-
-
基本資料型別 和 String類 的轉換
-
轉 String類
int aInt = 1; String aStr = 1 + ""; -
轉 基本資料型別
String aStr = "1"; int aInt = Integer.parseInt(aStr); -
String類 轉 char
String類底層維護的就是char陣列,因此只需要取出陣列中的某個索引位置的值即可
String str = "Hello world!"; char e = str.charAt(1); // 陣列索引下標從0開始 -
注意事項
-
在將String型別轉成基本資料型別時,要確保String型別能夠轉成有效的資料
比如我們可以把"123",轉成一個整數,但是不能把"hello"轉成一個整數
-
若String類無法轉換為數值基本型別,會拋出例外:java.lang.NumberFormatException
-
String類 轉 boolean 時,除了字串為
true時,得到true的boolean值,其他情況均會得到false的boolean值String str1 = "Hello world!"; boolean b1 = Boolean.parseBoolean(str1); System.out.println(b1); // 輸出 : false String str2 = null; boolean b2 = Boolean.parseBoolean(str2); System.out.println(b2); // 輸出 : false
-
-
-
Java運算子
-
運算子是一種特殊的符號,用以表示資料的運算、賦值和比較等
Java中存在的運算子:算術運算子、關系運算子 [比較運算子]、邏輯運算子、位運算子、賦值運算子、三元運算子
-
測驗算術運算子
運算子 運算 范例 結果 + 正號 + 7 7 - 負號 - 7 -7 + 加號 9 + 9 18 - 減號 9 - 9 0 * 乘號 9 * 9 81 / 除號(保留整數部分) 10 / 3 3 % 取模(保留小數部分) 10 % 3 1 ++ 自增(前):先運算后取值 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; + 字串相加(拼接) "hello" + "world" "helloworld" - 注意:
-
取模 的 本質:
a % b = a - a / b * b -
自增、自減 在前在后 的簡化理解:看作一個整體
例如下面的例子
-
將
x++看作一個整體,因為自增在后,所以此時x++整體的值為 5 ,x的值為6 -
將
++y看作一個整體,因為自增在前,所以此時++y整體的值為6,y的值為6
int x = 5; int y = 5; if (x++==6 & ++y==6){ // F & T -> F x=11; } System.out.print("x -> " + x + "\t"); // 6 System.out.println("y -> " + y); // 6 -
-
- 注意:
-
關系運算子(比較運算子)
關系運算子的 結果 都是 boolean型別,即 都是 true 或 false
常用于 條件判斷 中
運算子 運算 范例 結果 == 相等于 8 == 7 fasle != 不等于 8 != 7 true < 小于 8 < 7 false > 大于 8 > 7 true <= 小于等于 8 <= 7 false >= 大于等于 8 >= 7 true instanceof 檢查左邊(物件)是否右邊(類)相等 或 子類關系 "hello world" instanceof String true -
邏輯運算子、位運算子
用于連接多個條件(多個關系運算式,關系運算式也就是前面關系運算子所連接的一串運算式)
最終的結果也是一個 boolean 值
-
& : 邏輯與
左邊、右邊同為 true 時,結果為 true,否則為 false
-
&& : 短路與
在滿足 邏輯與 的基礎上,添加了短路機制:當左邊為 false 時,不會計算右邊,直接回傳 false
-
| : 邏輯或
左邊、右邊同為 false 時,結果為 false,否則為 true
-
|| : 短路或
在滿足 短路或 的基礎上,添加了短路機制:當左邊為 true 時,不會計算右邊,直接回傳 true
-
^ : 邏輯異或
左邊、右邊 不同 時,回傳 true
左邊、右邊 相同 時,回傳 false
-
! : 取反
對 ! 右側的 boolean值進行取反
true –> false
false –> true
a b a & b a && b a | b a || b a ^ b !a true true true true true ture false false true false false false true true true false false true false false true true true true false false false false false false false true -
復習題
//題1 public static void test01(){ int x = 5; int y = 5; if (x++==6 & ++y==6){ // F & T -> F x=11; } System.out.print("x -> " + x + "\t"); // 6 System.out.println("y -> " + y); // 6 } //題2 public static void test02(){ int x = 5; int y = 5; if (x++==5 | ++y==5){ // T | F -> T x=11; } System.out.print("x -> " + x + "\t"); // 11 System.out.println("y -> " + y); // 6 } //題3 public static void test03(){ int x = 5; int y = 5; if (x++==6 && ++y==6){ // F && (短路不運算) x=11; } System.out.print("x -> " + x + "\t"); // 6 System.out.println("y -> " + y); // 5 } //題4 public static void test04(){ int x = 5; int y = 5; if (x++==5 || ++y==5){ // T || (短路不運算) x=11; } System.out.print("x -> " + x + "\t"); // 11 System.out.println("y -> " + y); // 5 }
-
-
賦值運算
賦值運算子就是將某個運算后的值,賦給指定的變數
-
基本賦值運算子
int a = 1; // 此處的 = 號,便是基本賦值運算子,可以理解為: 有一個存盤int的4位元組空間,空間名稱為a,現在將 1 這個值存放到這個空間中 -
復合賦值運算子
int a = 10; int b = 20; a += b; // 等價于: a = a + b; a -= b; // 等價于: a = a - b; a *= b; // 等價于: a = a * b; a /= b; // 等價于: a = a / b; a %= b; // 等價于: a = a % b; -
賦值運算子的特點
-
運算順序從右往左
-
賦值運算子
- 左邊 只能是變數
- 右邊 可以是變數、運算式、常量值
-
復合賦值運算子會進行型別轉換
byte b = 3; b += 2; // 等價 b = (byte)(b + 2); b++; // b = (byte)(b+1);
-
-
-
三元運算子
-
基本語法 :
條件運算式 ? 運算式1 : 運算式2當條件運算式為 true 時,回傳 運算式1 的 結果
當條件運算式為 false 時,回傳 運算式2 的 結果
-
舉例 :
int a = 10; int b = 20; int res = a > b ? a++ : --b; // res -> 21 ; a -> 10 ; b -> 21 ; -
細節
-
三元運算子回傳基本資料型別時,會自動轉換為運算式中精度最高的型別
int a = 10; double b = 10.1; System.out.println(true ? a : b); // 輸出 : 10.0
-
-
-
運算子優先級
所謂優先級就是運算式運算中的運算順序
下表中的運算子中,上一行運算子總優先于下一行
只有單目運算子、賦值運算子是從右向左運算的
運算方向 運算子 . () {} ; , 右 -> 左 ++ -- ~ !(data type) 左 -> 右 * / % 左 -> 右 + - 左 -> 右 << >> >>> 位移 左 -> 右 < > <= >= instanceof 左 -> 右 == != 左 -> 右 & 左 -> 右 ^ 左 -> 右 | 左 -> 右 && 左 -> 右 || 左 -> 右 xx ? xx : xx(三元運算子) 右 -> 左 = *= /= %= 右 -> 左 += -= <<= >>= 右 -> 左 >>>= &= ^= |= -
識別符號的命名規范
Java對各種變數、方法和類等命名時使用的字符序列稱為識別符號
凡是自己可以起名字的地方都叫識別符號-
識別符號的命名規則(必須遵守)
-
26個英文字母大小寫(Java中區分大小寫)、0-9、_ 、$ 組成
最好不要用 $
-
數字 不能 開頭
-
不可以 使用 保留字、關鍵字,但能夠包含 保留字、關鍵字
-
長度 不受限制
-
不能包含 空格
-
-
識別符號的命名規范(約定俗成)
-
包名
多單詞組成時所有字母都小寫
com.zhumei.study
-
類名、介面名
多單詞時使用大駝峰
XxxYyyZzz
-
變數名、方法名
多單詞時使用小駝峰
xxxYxxZzz
-
常量名
字母全大寫
多單詞時使用下劃線分隔
XXX_YYY_ZZZ
-
-
Java流程控制
-
順序控制
沒什么好說的,就是從上往下按代碼順序執行
-
分支控制
-
單分支 if
flowchart TD A[Start] --> B{條件運算式} B -->|true| C[代碼塊] C --> D[end] B -->|false| Dif (條件運算式) { // 條件運算式的值為 ture 則執行此代碼塊中的代碼(false則不執行) 代碼; } -
雙分支 if - else
flowchart TD A[Start] --> B{條件運算式} B -->|true| C[代碼塊1] C --> E[end] B -->|false| D[代碼塊2] D --> Eif (條件運算式) { // 條件運算式的值為 ture 則執行此代碼塊中的代碼1(false則不執行) 代碼1; } else{ // 條件運算式的值為 false 則執行此代碼塊中的代碼2(true則不執行) 代碼2; } -
多分枝 if - else if - … - else
flowchart TD A[Start] --> B{條件運算式1} B -->|true| C[代碼塊1] C --> D[end] B -->|false| E{條件運算式2} E -->|true| F[代碼塊2] F --> D E -->|false| G{條件運算式n} G -->|true| H[代碼塊n] G -->|false| I[代碼n+1] H --> D I --> Dif (條件運算式1) { 代碼1; // 條件運算式1為true則執行代碼1,并不再繼續執行與條件運算式1處于同一分支的運算式與其對應的代碼 } else if (條件運算式2){ 代碼2; // 條件運算式2為true則執行代碼2,并不再繼續執行與條件運算式2處于同一分支的運算式與其對應的代碼 } else if (條件運算式N){ 代碼n; // 條件運算式n為true則執行代碼n,并不再繼續執行與條件運算式n處于同一分支的運算式與其對應的代碼 } else{ 代碼n+1; // 若所有的運算式都為false,則執行此代碼塊 } -
嵌套分支
在 一個分支結構 中又 完整的 嵌套了 另一個完整的分支結構
建議不超過3層,嵌套層數與可讀性呈負相關,層數越多可讀性越差
if (條件運算式a){ 代碼1; if (條件運算式b){ 代碼2; }else if (條件運算式c){ 代碼3; }else { 代碼4 } } -
switch分支
- 在Java中:
- switch運算式的值可以是變數(byte、short、int、char、這4個基本資料型別的包裝類物件、String物件、列舉物件)
- case后的值不能為變數,必須是常量
- default陳述句是可選的,若沒有default陳述句又沒有匹配上對應的case中的常量,就表示不會執行switch中的任何代碼(switch中的運算式是一定會執行的)
- 在case中的代碼不寫break,會導致case穿透
switch(運算式){ case 常量1: 代碼1; break; case 常量2: 代碼2; break; case 常量n: 代碼n; break; default: 代碼default; break; }
- 在Java中:
-
-
回圈控制
-
for回圈
-
for回圈的四要素:
- 回圈變數初始化
- 回圈條件
- 回圈操作(需要回圈的代碼)
- 回圈變數迭代
-
回圈變數初始化、回圈條件、回圈變數迭代,都可以不放在for回圈的括號中
-
回圈條件是一個回傳布林值的運算式,若在for回圈中沒有寫回圈條件,則默認為true
-
退出for回圈的方式:
- 回圈條件結果為false
- 執行break陳述句
- 執行return陳述句
for(回圈變數初始化;回圈條件;回圈變數迭代){ 需要回圈的代碼; }
-
-
while回圈
while回圈和for回圈一樣,也有相同的回圈四要素,只不過放的位置和for回圈不一樣
簡單來說,可以把while回圈看作另一種寫法的for回圈
不必糾結寫法上的細枝末節,重點在于知道執行的代碼在哪種情況下被回圈了幾次
回圈變數初始化; while(回圈條件){ 需要回圈的代碼; 回圈變數迭代; }
-
do…while回圈
和while回圈相比,do…while回圈會先執行一次尋妖回圈的代碼,再進行
回圈變數初始化; do{ 需要回圈的代碼; 回圈變數迭代; }while(回圈條件);
-
多重回圈控制
將一個回圈放在另一個回圈中,外層回圈和內層回圈均可使用for回圈、while回圈、do…while回圈
一般建議多重回圈不超過3層,層數越多,代碼可讀性越差
for (int i = 0; i < 10; i++) { // 外層回圈 for (int j = 0; j < 20; j++) { // 內層回圈 System.out.print("*"); // 需要xun } System.out.println(); // 表示換行 }- 經典題
-
列印 九九乘法表
for (int i = 1; i <= 9; i++) { // 表示輸出多少行 for (int j = 1; j <= i; j++) { // 表示每行輸出多少列 System.out.print(j + "*" + i + "=" + j * i); System.out.print("\t"); } System.out.println(); // 換行 } -
列印 實心 / 空心 三角形
//1. 實心三角 for (int i = 1; i <= 6; i++) { // 表示列印多少行 for (int j = 1; j <= 6-i; j++) { // 每一列要列印的空格 System.out.print(" "); } for (int j = 1; j <= (2*i-1) ; j++) { // 每一列要列印的*號數量 System.out.print("*"); } System.out.println(); } //2. 空心三角 for (int i = 1; i <= 6; i++) { // 表示列印多少行 for (int j = 1; j <= 6-i; j++) { // 每一列要列印的空格 System.out.print(" "); } for (int j = 1; j <= (2*i-1) ; j++) { // 每一列要列印的*號數量 //當要列印的是 最后一行 或者 這一列第一個和最后一個 時,輸出* if (i==6||j==1||j==(2*i-1)){ System.out.print("*"); }else { // 其余輸出空格 System.out.print(" "); } } System.out.println();// 換行 } -
列印 空心 / 實心 菱形
//3. 實心菱形 for (int i = 1; i <= 12; i++) {// 控制列印的行數 if (i <= 6) { //列印菱形的上半部分 for (int j = 1; j <= (6 - i); j++) { System.out.print(" "); } for (int j = 1; j <= (2 * i - 1); j++) { System.out.print("*"); } } else { //列印菱形的下半部分 for (int j = 1; j <= (i - 6); j++) { System.out.print(" "); } for (int j = 1; j <= (2 * (12 - i) - 1); j++) { System.out.print("*"); } } System.out.println(); // 換行 } //4. 空心菱形 for (int i = 1; i <= 12; i++) {// 控制列印的行數 if (i <= 6) { //列印菱形的上半部分 //1. 先列印空格 for (int j = 1; j <= (6 - i); j++) { System.out.print(" "); } //2. 列印邊框和內部空格 for (int j = 1; j <= (2*i-1) ; j++) { if (j == 1 || j == (2 * i - 1)){ System.out.print("*"); }else { System.out.print(" "); } } } else { //列印菱形的下部分 //1. 列印空格 for (int j = 1; j <= (i - 6); j++) { System.out.print(" "); } //2. 列印邊框和內部空格 for (int j = 1; j <= (2 * (12 - i) - 1); j++) { if (j == 1 || j == (2 * (12 - i) - 1)){ System.out.print("*"); }else { System.out.print(" "); } } } System.out.println(); // 換行 }
-
- 經典題
-
break跳轉控制陳述句
break陳述句用于終止某個陳述句塊的執行,一般使用在switch或者回圈[for,while,do-while]中
//使用方式1 for (int i = 0; i < 10; i++) { if (i == 5) { break; // 退出最近的某個回圈體 } } //使用方式2 label1: for (int i = 0; i < 10; i++) { label2: for (int j = 0; j < 10; j++) { if (j == 5) { break label1; // 退出了label1標簽所標識的for回圈 } System.out.println("j -> " + j); } } -
continue跳轉控制陳述句
continue陳述句用于結束本次回圈,繼續執行下一次回圈,
//使用方式1 for (int i = 0; i < 10; i++) { //當i=5時,不會執行后續的輸出陳述句,而是進入下一次回圈 if (i == 5) { continue; // 結束本次回圈,進入下一次回圈 } System.out.println("i -> " + i); } //使用方式2 label1: for (int i = 0; i < 4; j++) { label2: for (int j = 0; j < 10; i++) { if (i == 5) { continue label1; // 執行下一次 label1 標簽所指的For回圈(for i的回圈) } System.out.println("j = " + j + "\t"); } } -
return跳轉控制陳述句
return 使用在方法,表示跳出所在的方法,意味著所在方法算為執行完畢
return寫在主方法中,則會退出Java程式
-
易錯陷阱題
-
求出1-1/2+1/3-1/4..1/100的和
易錯點:
-
1/2 在Java中等于0,需要使用1.0/2才能得到0.5
所以在做
/號時,除數與被除數必須有一個為浮點型
double res = 0.0; double temp = 0.0; for (double i = 1.0; i <= 100; i++) { if (i%2==0){ //符號為負 temp = -(1/i); }else { //符號為正 temp = 1/i; } res += temp; } System.out.println(res); -
-
-
Java陣列
-
簡介
陣列可以存放多個同一型別的資料
陣列也是一種資料型別,是參考型別
當陣列存盤基本資料型別時,默認值為基本資料型別對應的默認值
當陣列存盤參考資料型別(如:包裝類物件、String類物件)時,默認值為null
資料就是在記憶體空間中連續存盤的同一型別資料
陣列索引從0開始
注意:陣列是參考資料型別,變數中存盤的是陣列在堆中的地址
-
陣列的幾種初始化方式
-
方式一
double[] a = new double[5]; // 直接分配記憶體空間 a[0] = 1d; double b[]; // 先宣告 b = new double[5]; // 再分配記憶體空間 b[0] = 2d; // double[] a 和 double a[] 是一樣的,建議使用 double[] -
方式二
double[] c = {1.0,2.0,3.0}; // 靜態初始化
-
-
陣列反轉
有很多方法,比如:
- 開辟一個同樣長度的陣列,便利舊陣列,在新陣列中從后往前放值
- 陣列第一個和最后一個進行交換位置,第二個和倒數第二個,直到陣列完全反轉
Integer[] arrayInter = {1, 2, 3, 4, 5, 6, 7}; for (int i = 0; i < arrayInter.length / 2; i++) { Integer temp = arrayInter[i]; arrayInter[i] = arrayInter[arrayInter.length - 1 - i]; arrayInter[arrayInter.length - 1 - i] = temp; } for (int i = 0; i < arrayInter.length; i++) { System.out.print(arrayInter[i] + "\t"); } -
陣列排序/查找
有多種陣列排序和查找的演算法,這里演示冒泡排序
-
冒泡排序
冒泡排序(Bubble Sorting)的基本思想是:通過對待排序序列從后向前(從下標較大的元素開始),依次比較相鄰元素的值,若發現逆序則交換,使值較大的元素逐漸從前移向后部,就象水底下的氣泡一樣逐漸向上冒,
舉例:(從小到大)
int[] arrayInt = {41, 23, 65, 21, 78, 45}; int temp = 0; for (int i = 0; i < arrayInt.length - 1; i++) { for (int j = 0; j < arrayInt.length - 1 - i; j++) { if (arrayInt[j] > arrayInt[j+1]){ temp = arrayInt[j]; arrayInt[j] = arrayInt[j+1]; arrayInt[j+1] = temp; } } } for (int i = 0; i < arrayInt.length; i++) { System.out.print(arrayInt[i] + "\t"); } -
二分查找
在演算法筆記中
-
-
二維陣列
-
幾種定義方式
int[][] a、int[] a[]、int a[][]都可以,但建議使用int[][] a-
方式一
int[][] a = new int[2][3]; int[][] b; b = new int[2][3]; -
方式二
int[][] a = new int[10][]; a[1] = new int[1]; a[2] = new int[2]; a[3] = new int[3]; -
方式二
int[][] a = { {1,2,3,4}, {1,2,3}, {1,2}, {1} };
-
-
易錯點
int[] x,y[]; // x是一維陣列,y是二維陣列// 初學時會有點搞混匿名內部類和陣列靜態初始化的代碼 //1. 陣列的靜態初始化 String[] arrayStr = new String[]{"a","b"}; //2. 匿名內部類 String arrayStr = new String(){...}; // 注意: 這里編譯會報錯,因為String為final類 (為了通過編譯報錯來確定這是創建匿名內部類的方式)
-
Java面向物件
-
Java物件在記憶體中的存在形式(簡化)
class Cat { private String color; private String name; private int age; public Cat(String color, String name, int age) { this.color = color; this.name = name; this.age = age; } } public static void main(String[] args) { Cat cat = new Cat(new String("blue"), "tom", 6); }
-
JVM記憶體結構的簡單分析
- 堆疊:存盤基本資料型別(區域變數)
- 堆:存盤物件
- 方法區:常量池,類檔案
-
Java創建物件的流程(簡化)
- 先加載對應類的資訊(加載屬性和方法資訊,且只會加載一次)
- 在堆中分配空間,進行默認初始化
- 將物件在堆中的地址賦值給堆疊中的區域變數
-
成員方法傳參機制
本質:都是值傳遞,即 拷貝一份副本然后將副本值傳遞給目標方法
方法回傳值也是同理,本質都是值拷貝,回傳的是基本資料型別時拷貝的就是其值,回傳的是參考資料型別時拷貝的是就是其在堆記憶體中的地址
-
方法遞回呼叫
在演算法筆記中有較為詳細的介紹
- 使用遞回的重要知識點
-
執行一個方法時,就會創建一個新的受保護的獨立空間(堆疊空間)
方法的區域變數是獨立的,不會相互影響
-
如果方法中使用的是參考型別變數(比如陣列,物件),就會共享該參考型別的資料,
-
遞回必須向退出遞回的條件逼近,否則就是無限遞回,出現 StackOverflowError 堆疊溢位,即堆疊空間被撐爆了
-
當一個方法執行完畢,或者遇到return就會回傳,遵守誰呼叫,就將結果回傳給誰
-
- 使用遞回的重要知識點
-
方法多載
java 中允許同一個類中,多個同名方法的存在,但要求 形參串列不一致
- 多載的好處
- 減輕了 起名 、記名 的麻煩
- 多載的要求
- 方法名 必須 相同
- 形參串列 必須 不同(型別、個數、順序 至少一個不同)(形參名無要求)
- 回傳型別 沒有要求
- 多載的好處
-
方法重寫
方法重寫(覆寫)就是子類有一個方法,和父類的某個方法的名稱、回傳型別、引數一樣,那么我們就說子類的這個方法重寫了父類的方法
- 達成方法重寫的要求
- 子類方法的 形參串列、方法名 必須 要和父類方法的 形參串列、方法名 完全一致
- 子類方法的 回傳型別,必須 要和 父類方法的回傳型別一致 或 是父類方法回傳型別的子類
- 子類方法 不能 縮小父類方法的訪問權限(public > protected > 默認 > private)
- 達成方法重寫的要求
-
可變引數
java 允許將同一個類中多個同名同功能但引數個數不同的方法,通過可變引數的形式,封裝成一個方法
簡單點就是說方法可以傳入無限多個指定型別的引數值
public String study(String... arrayStr){ String str = ""; for (int i = 0; i < arrayStr.length; i++) { str += arrayStr[i]; } return str; }- 一些細節
- 可變引數的實參可以是 0個 或 任意多個
- 可變引數的本質 就是 陣列
- 可變引數 和 普通引數 一起放在形參串列時,必須保證 可變引數型別 放在最后
- 一個形參串列中只能出現一個可變引數
- 一些細節
-
作用域
-
Java中作用域的分類
-
全域變數:也就是屬性,作用域為整個類體
全域變數(屬性)可以不賦值,就可以直接使用,因為有默認值
-
區域變數:除了屬性之外的其他變數,作用域為定義它的代碼塊中
區域變數沒有默認值,必須賦值后使用
-
-
一些細節
-
屬性和區域變數可以重名,訪問時遵循就近原則
-
在同一個作用域中,比如在同一個成員方法中,兩個區域變數,不能重名
-
生命周期不同
-
屬性生命周期較長,伴隨著物件的創建而創建,伴隨著物件的銷毀而銷毀
-
區域變數,生命周期較短,伴隨著它的代碼塊的執行而創建,伴隨著代碼塊的結束而銷毀
即:在一次方法呼叫程序中
-
-
作用域范圍不同
- 全域變數/屬性:可以被本類使用,或其他類使用(通過物件呼叫)
- 區域變數:只能在本類中對應的方法中使用
-
修飾符不同
- 全域變數/屬性可以加修飾符
- 區域變數不可以加修飾符
-
-
-
構造器
構造方法又叫構造器(constructor),是類的一種特殊的方法,它的主要作用是完成對新物件的初始化
-
特點
- 方法名 和 類名 相同
- 沒有回傳值
- 在創建物件時,系統會自動的呼叫該類的構造器完成物件的初始化
-
一些細節
-
一個類可以定義多個不同的構造器,即構造器多載
-
構造器是用來完成物件初始化的,不是用于創建物件的
-
在創建物件時,系統自動的呼叫該類的構造方法
-
如果程式員沒有定義構造器,系統會自動給類生成一個默認無參構造器(默認構造器)
-
一旦定義了自己的構造器,默認的構造器就覆寫了
即:不能再使用默認的參構造器,除非顯式的定義一下
-
-
-
this關鍵字
Java虛擬機會給每個物件分配this,代表當前物件
即:每個實體都有一個隱藏的變數this,存盤了該物件在堆記憶體中的地址
-
JVM記憶體圖(簡化)
Cat cat = new Cat( new String("blue"), // 堆中String物件的地址 "tom", // 常量池中的地址 6 // 基本資料型別的值 );
-
一些注意事項
-
this 不能在類定義的外部使用,只能在類定義的方法中使用
-
this 關鍵字可以用來訪問本類的屬性、方法、構造器
-
this 用于區分當前類的屬性和區域變數
-
訪問成員方法的語法:this.方法名(引數串列)
-
訪問構造器語法:this(引數串列)
即只能在構造器中訪問另外一個構造器,且必須放在第一條陳述句
this() 和 super() 都需要放在構造器的第一行,所以無法同時使用
-
在哪個物件的方法中呼叫,this就代表哪個物件
看的不是編譯型別,而是運行型別,
后期框架中使用了大量的設計模式,導致了很難判斷this到底是指的哪個物件,最好的辦法是使用IDEA的debug
-
-
-
訪問修飾符
java 提供四種訪問控制修飾符號,用于控制方法和屬性(成員變數)的訪問權限(范圍)
-
四種訪問修飾符
訪問級別 訪問修飾符 同類 同包 子類 不同包 公開 public √ √ √ √ 受保護 protected √ √ √ × 默認 沒有修飾符 √ √ × × 私有 private √ × × × -
實際開發中,當某個方法需要 A類 的實體傳入,但 A類 的 構造方法的protected
此時,就可以使用匿名內部類的方式
package com.test; // 注意和 MainApp類 在不同包下 public class A { protected A(){} }package com.zhumei; // 注意和 A類 在不同包下 import com.test.A; public class MainApp { public static void main(String[] args) { MainApp mainApp = new MainApp(); mainApp.test01(new A(){}); // new A(){} 相當于創建了一個 MainApp$1 extends A 類(繼承了A類),這樣就突破了protected修飾符導致的無法使用不同包下類的構造器 } public void test01(A a){ System.out.println(a.getClass()); } } -
使用修飾符的注意事項
-
修飾符可以用來修飾類中的屬性,成員方法以及類
-
只有 默認修飾符 和 public修飾符 才能修飾類
-
子類繼承了父類的方法、欄位后,呼叫父類的方法、欄位需要遵守父類設定的訪問修飾符的規則
即:父類設定為private的欄位、方法,子類無法通過正常方式進行訪問使用(可以使用反射爆破的方式強行使用)
-
-
-
面向物件三大特征
-
封裝
封裝(encapsulation)就是把抽象出的資料[屬性]和對資料的操作(方法)封裝在一起
資料被保護在內部,程式的其它部分只有通過被授權的操作(方法),才能對資料進行操作
- 封裝的好處
- 隱藏具體的實作細節
- 可以對封裝的資料進行校驗
- 封裝的實作步驟
- 將資料私有化(private)
- 提供一個公共的(public)set方法,對外提供修改資料的方式,也可對外界提供的資料進行校驗
- 提供一個公共的(public)get方法,對外提供獲取資料的方式,也可對外界進行權限判斷來不同的值
- 封裝的好處
-
繼承
繼承可以解決代碼復用,讓我們的編程更加靠近人類思維
當多個類存在相同的屬性和方法時,可以從這些類中抽象出父類
在父類中定義這些相同的屬性和方法,所有的子類不需要重新定義這些屬性和方法,只需要通過 extends 來宣告繼承父類即可
-
super關鍵字
super代表父類的參考,用于訪問父類的屬性、方法、構造器(但受到父類訪問修飾符的限定)
注意:
- super的訪問不限于直接父類,而是所有繼承的類,使用super若在父類中找不到會去找父類的父類,直到找到為止 或 所有的父類均沒有而報錯
- 若在父類中找到了,但由于修飾符關系無法直接訪問,則會直接報錯,而不會繼續向更上層的父類進行尋找
-
一些細節:
-
子類會擁有父類的所有資訊(欄位、方法),但是會受到父類訪問修飾符的影響,對父類私有、默認訪問修飾符修飾的欄位和方法都無法直接使用
可以通過父類提供的其他可訪問方法來間接獲取、修改、使用欄位或方法
-
final類無法被繼承
-
子類必須呼叫父類的構造器,完成父類的初始化
當創建子類物件時,不管使用子類的哪個構造器,默認情況 下總會去呼叫父類的無參構造器
如果父類沒有提供無參構造器,則必須在子類的構造器中用 super 去指定使用父類的哪個構造器完成對父類的初始化作業,否則編譯不通過
-
-
如果希望指定去呼叫父類的某個構造器,則顯式的呼叫一下 : super(引數串列)
-
super(引數串列)在使用時必須放在構造器第一行
super() 和 this() 都只能放在構造器第一行,因此這兩個方法不能共存在一個構造器
-
this 和 super
public class MainApp { public static void main(String[] args) { B b = new B(); } }class A{ public A(){ System.out.println("A -> null"); // 無參構造器 } public A(String str){ System.out.println("A -> String"); // 有參構造器 } } class B extends A{ public B(){ this("str"); System.out.println("B -> null"); // 無參構造器 } public B(String str){ super(str); System.out.println("B -> String"); // 有參構造器 } }輸出:
A -> String
B -> String
B -> null
-
-
java 所有類都是 Object 類的子類, Object 是所有類的基類
-
父類構造器的呼叫不限于直接父類,將一直往上追溯直到 Object 類(頂級父類)
-
子類最多只能繼承一個父類(指直接繼承),即 java 中是單繼承機制
-
不能濫用繼承,子類和父類之間必須滿足 is-a 的邏輯關系
-
-
創建繼承類實體時,JVM記憶體分析(簡化)
class GrandPa{ String name = "大頭爺爺"; protected int age = 70; } class Father extends GrandPa{ String name = "大頭爸爸"; private String job = "程式員"; public String getJob(){ return this.job; } } class Son extends Father{ String name = "大頭兒子"; }public class MainApp { public static void main(String[] args) { Son son = new Son(); // 研究創建一個有繼承關系的類的實體時,JVM中的記憶體分布圖(簡化) } }創建實體的流程(簡化):
-
首先會加載類資訊進入到記憶體中
先從頂級父類開始加載,比如這里會先加載 Object類,然后是 GrandPa類,接著是 Father類,最后是 Son類
-
在堆中開辟一個空間用于存盤son實體
這個空間會分成幾個部分,用于存盤從不同類中繼承而來的資訊
比如這里主要會劃出3個關鍵的部分:Son類、Father類、GrandPa類,每個部分都相互獨立,即使有相同的欄位名也不會造成沖突
-
將堆中的地址賦值給在堆疊中的變數
-
-
注意:
若父類方法中有獲取欄位值的行為,該值只會去父類本身或父類的父類去找,并不會從子類中向上找
public class MainApp { public static void main(String[] args) { B b = new B(); b.study1(); // 輸出的是10 } } class A { public int number = 10; public void study() { System.out.println(this.number); // this.number 會去本類(A類),以及Object類中尋找 (若A類中沒有的話) } } class B extends A { public int number = 120; }
-
-
多型
方法或物件具有多種形態
比如說你可以是大學生的同時,也可以是高中生的課外輔導班老師,具體是什么身份要看在哪一種場景中做什么事情
多型是面向物件的第三大特征,多型是建立在封裝和繼承基礎之上的
-
實作多型的三大條件
- 必須在繼承體系下
- 子類必須對父類中的方法進行重寫
- 通過父類的參考呼叫重寫的方法
-
方法的多型
重寫和多載就是多型的體現
呼叫的方法名是同一個,但是會根據 呼叫方的物件不同、傳入的引數不同 導致最終呼叫的方法也不同
-
多載:在呼叫方法時,傳入不同的引數,就會呼叫不同的方法
-
重寫:在呼叫方式時,根據物件的不同,就會呼叫不同的方法
-
-
物件的多型
一個編譯型別能夠表達多個不同的運行型別,真正運行時根據真正的運行型別來確定運行的結果,這就體現了多型
class A { public String study() { return "a"; } } class B extends A { @Override public String study() { return "b"; } } public class MainApp { public static void main(String[] args) { //這就是多型的體現,編譯型別A既可以表達A實體物件也可以表達B實體物件 A a = new B(); // A是編譯型別,B是運行型別 String str = a.study(); } }-
多型的向上轉型
父類的參考指向了子類的物件
即:子類的物件地址,保存在了父型別別的變數中
通過向上轉型,子類可以呼叫父類中的所有成員(但要遵守訪問權限的限制),但是不能呼叫子類的特有成員
最終的運行效果,仍然是看子類的具體實作,從運行型別開始查找然后呼叫,與之前繼承的實體方法呼叫一致
class A { public String study() {return "a";} } class B extends A { @Override public String study() {return "b";} } public class MainApp { public static void main(String[] args) { // 父類的參考指向了子類的物件 A a = new B(); // A是編譯型別,B是運行型別 } } -
多型的向下轉型
將父類的參考交給子類的型別
即:原本父類變數中保存的是子類物件的地址,現在將地址交給子類變數
只能強轉父類的參考,不能強轉父類的物件
要求父類的參考必須指向的是當前目標型別的物件
當向下轉型后,可以呼叫子型別別中所有的成員
class A { public String study() {return "a";} } class B extends A { @Override public String study() {return "b";} } public class MainApp { public static void main(String[] args) { // 父類的參考指向了子類的物件 A a = new B(); // A是編譯型別,B是運行型別 // 父類的參考交給子類的型別(向下轉型) B b = (B)a; // B是編譯型別,B是運行型別 } }
-
-
Java的動態系結機制
- 當呼叫物件方法時,該方法會和該物件的記憶體地址(運行型別)系結
- 當呼叫物件屬性時,沒有動態系結機制,哪里宣告哪里使用
public class MainApp { public static void main(String[] args) { A a = new B(); System.out.println(a.study()); // 動態系結機制,先去 子類(B類) 中尋找sutdy方法,沒找到再去父類中找 System.out.println(a.number); // 直接在編譯型別A類(或A類的父類)中尋找對應的欄位,欄位(屬性)沒有動態系結機制 } } class A { public int number = 10; public String study() { return "a"; } } class B extends A { public int number = 120; @Override public String study() { return "b"; } } -
多型的常見應用
-
多型陣列
public class MainApp { public static void main(String[] args) { Person[] person = new Person[3]; person[0] = new Student(); // 編譯型別 -> Person ; 運行型別 -> Student person[1] = new Teacher(); // 編譯型別 -> Person ; 運行型別 -> Teacher person[2] = new Student(); // 編譯型別 -> Person ; 運行型別 -> Student for (int i = 0; i < person.length; i++) { String name = person[i].name(); System.out.println(name); } } } interface Person{ String name(); } class Student implements Person{ @Override public String name() {return "學生";} } class Teacher implements Person{ @Override public String name() {return "教師";} } -
多型引數
public class MainApp { public static void main(String[] args) { test(new Teacher()); test(new Student()); } public static void test(Person person){ String name = person.name(); // name方法的具體運行邏輯是根據運行型別決定的 System.out.println(name); } } interface Person{ String name(); } class Student implements Person{ @Override public String name() { return "學生"; } } class Teacher implements Person{ @Override public String name() { return "教師"; } }
-
-
-
-
類變數、類方法 (static)
-
類變數
類變數也叫靜態變數/靜態屬性,是該類的所有物件共享的變數
任何一個該類的物件去訪問它時,取到的都是相同的值
同樣任何一個該類的物件去修改它時,修改的也是同一個變數
-
類變數的定義
訪問修飾符 static 資料型別 變數名(推薦)static 訪問修飾符 資料型別 變數名public static int staticInt = 1; // 推薦 static public double staticDouble = 1.0; // 不推薦 -
類變數的訪問
類名.類變數名(推薦)物件名.類變數名public static double staticDouble = 1.0; double d = A.staticDouble; // 推薦 A a = new A(); double d2 = a.staticDouble; // 不推薦 -
一些注意事項
- 類變數 與 實體變數 區別
- 類變數是該類的所有物件共享
- 實體變數是每個物件獨享
- 訪問類變數時,同樣需要遵守訪問修飾符的限制
- 類變數是在類加載時就初始化了,即使沒有創建物件,只要類加載了就可以使用類變數
- 類變數的生命周期是隨類的加載開始,隨著類消亡而銷毀
- 類變數 與 實體變數 區別
-
-
類方法
類方法也叫靜態方法
當方法中不涉及到任何和物件相關的成員,則可以將方法設計成靜態方法提高開發效率
-
類方法的定義
訪問修飾符 static 資料回傳型別 方法名(){}推薦static 訪問修飾符 資料回傳型別 方法名(){}public static String test(){ return "this is static method :)"; } -
類方法的呼叫
類名.類方法名推薦物件名.類方法名public class MainApp { public static void main(String[] args) { String str = A.test(); // 呼叫類方法 } } class A{ public static String test(){ return "this is static method :)"; } } -
一些注意事項
-
類方法和普通方法都是隨看類的加載而加載,將結構資訊存盤在方法區
-
類方法中不允許使用和物件有關的關鍵字
如:this、super
-
類方法中只能訪問:類變數、類方法
-
普通成員方法中可以訪問靜態成員和非靜態成員
-
-
-
-
Java程式中的main方法
public static void main(String[] args) { ...;//代碼 }-
public : Java虛擬機需要呼叫類的main方法,所以該方法必須是public
-
static : Java虛擬機在執行main方法時不必創建物件,所以該方法必須是static
-
void : Java虛擬機只會呼叫該方法,并不會對該方法的回傳值做出任何處理,所以該方法必須是void
-
main : Java虛擬機只認定這個方法名,因此必須是main作為方法名
-
String[] args : Java虛擬機會將執行java命令時傳遞的引數(以空格為分隔符)封裝在String陣列中,并作為實參傳遞給main方法
例如:
-
運行 :
java test 資料a 資料b 資料c -
輸出 :
資料a
資料b
資料c
public class MainApp { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println(args[i]); } } }
-
-
-
代碼塊
代碼化塊又稱為初始化塊,屬于類中的成員(即是類的一部分),類似于方法,將邏輯陳述句封裝在方法體中,通過 { } 包圍起來,但和方法不同,沒有方法名,沒有回傳,沒有引數,只有方法體
代碼塊不能通過物件或類顯式呼叫,而是 加載類 或 創建物件 時 隱式呼叫
Java中代碼塊分為:靜態代碼塊 和 普通代碼塊(非靜態代碼塊)
-
代碼塊的定義語法
-
修飾符可選,且修飾符只能寫static
-
最后的分號可以選擇省略
[修飾符]{ 代碼; }; -
-
普通代碼塊
普通代碼塊相當于另外一種形式的構造器,即 對構造器的補充機制(做初始化的操作)
當所有的構造器中都有重復的陳述句時,就可以提取到普通代碼塊中,提高代碼的復用率
- 當一個類中有多個普通代碼塊時,按代碼中從上到下的順序依次執行
- 同樣,類中非靜態欄位的初始化 與 普通代碼塊的優先級也是一致的,因此遵循按定義順序執行
- 類的構造器中最前面隱含了 super() 和 呼叫普通代碼塊,super() 在行首,呼叫普通代碼塊的代碼其次
-
靜態代碼塊
普通代碼塊用于對實體的初始化,靜態代碼塊則是對類進行初始化(初始化靜態屬性)
隨著類的加載而執行,由于類只會加載一次所以靜態代碼塊也只會執行一次
- 當一個類中有多個靜態代碼塊時,按代碼中從上到下的順序依次執行
- 同樣,類中靜態欄位的初始化 與 靜態代碼塊的優先級也是一致的,因此遵循按定義順序執
-
類加載的時機
-
創建實體物件的時候(new)
-
創建子類實體物件的時候,其父類也會被加載
-
使用類的靜態成員(靜態變數、靜態方法)的時候
呼叫類中 final static 對應的常量時,底層編譯器做了優化,并不會導致類的加載(注意:常量值不是通過new創建時才滿足類不會被加載)
-
-
不同位置代碼塊的執行順序
同級別時,按照代碼中的定義順序進行執行
- 父類的靜態代碼塊、父類靜態欄位的初始化
- 子類的靜態代碼塊、子類靜態欄位的初始化
- 父類的代碼塊、父類非靜態欄位的初始化
- 父類的構造器
- 子類的代碼塊、子類非靜態欄位的初始化
- 子類的構造器
public class MainApp { public static void main(String[] args) { B b = new B(); } } class A{ static { System.out.println("A -> static{}"); // 1 } public A(){ System.out.println("A -> public A{}"); //4 } { System.out.println("A -> normal{}"); // 3 } } class B extends A{ static { System.out.println("B -> static{}"); // 2 } public B(){ System.out.println("B -> public B{}"); // 6 } { System.out.println("B -> normal{}"); // 5 } }
-
-
final關鍵字
final 可以修飾 類、屬性、方法、區域變數
-
常見使用 final 的場景
-
不希望類被繼承時,使用final修飾類
final類中的方法和屬性不必再加fianl修飾符
-
不希望父類中的某個方法被子類重寫,可以使用final修飾父類中的這個方法
-
不希望父類中的某個屬性的值被修改,可以使用final修飾父類中的這個屬性
-
不希望某個區域變數被修改,可以使用final修飾
-
-
final 的一些細節和注意事項
-
final修飾的屬性(叫常量),一般使用 XX_XX_XX 來命名
-
final修飾的屬性在定義時,就必須賦值,且無法再修改
注意:final鎖住的是目標變數空間中存盤的值
-
-
如果是基本資料型別,則會鎖定住基本資料型別的值
-
如果是參考資料型別,則會所定住對應的地址值(物件地址)
不會鎖住目標物件封裝的屬性值
對 fianl屬性(非靜態) 賦值的位置可以是: - 定義時-
構造器
- 代碼塊
對 final屬性(靜態)賦值的位置可以是:
- 定義時
-
靜態代碼塊
-
final類無法被繼承,但可以被實體化
-
final方法無法被重寫,但是可以被繼承
-
final 不能修飾 構造器
-
final 搭配 static 效率會提高,呼叫 final static 的常量不會導致類的加載(編譯器做的優化)
注意:常量值不是通過new創建時才滿足類不會被加載
-
包裝類 和 String類 都是 final類
-
-
抽象類
當父類的一些方法不能確定時,可以用 abstract關鍵字,將這些不確定的方法交給子類去實作
用 abstract 來 修飾方法 就成為了抽象方法
用 abstract 來 修飾類 就成為了抽象類
-
抽象類的價值更多作用在于,即 設計者設計好后,讓子類繼承并實作
-
抽象類
一旦類中包含了抽象方法,則該類必須宣告為抽象類
抽象類中,不一定包含抽象方法
public abstract class Father { } -
抽象方法
public abstract void test(); // 抽象方法是沒有主體代碼塊的 (即 : {...} ) -
一些細節
-
抽象類不能被實體化
-
抽象類中不一定包含abstract方法,但abstract方法一定要在抽象類中
注意:抽象類的本質還是類,普通類中有的抽象類中都可以有,唯一的區別就是抽象類無法被實體化
-
abstract 只能修飾 類、方法
-
子類繼承了抽象類,則必須實作抽象類的所有方法,除非子類也是抽象類
-
abstract 不能和 private、final、static 一起使用,因為這些關鍵字的作用和 abstract的作用 相互違背
-
-
-
介面
介面就是給出一些沒有實作的方法,封裝到一起,到某個類要使用的時候,在根據具體情況把這些方法寫出來
介面是更加抽象的抽象的類,介面體現了程式設計的多型和高內聚低偶合的設計思想
- JDK7 : 抽象類里的方法可以有方法體,介面里的所有方法都沒有方法體
- JDK8 : 介面類可以有靜態方法,默認方法
一些注意事項
-
介面不能被實體化
-
介面中所有的方法都默認被 public abstract 修飾
-
抽象類實作介面,可以不必全部實作介面中的方法;普通類實作介面,必須全部實作介面中的方法
一個類(抽象類、普通類)可以實作多個介面
-
介面中的屬性,默認都被 public static final 修飾(必須初始化)
-
介面不能繼承其他類,但可以繼承多個別的介面
介面實作 和 類繼承
- 繼承:解決代碼的復用性和可維護性,Java中只能單繼承,必須滿足 is - a 關系
- 介面:設計好各種規范(方法),Java中可多實作,只需要滿足 like - a 關系
-
內部類
類的內部又完整的嵌套了另一個類結構
被嵌套的類稱為 內部類(inner class),
嵌套其他類的類稱為 外部類(outer class)
內部類最大的特點就是可以直接訪問私有屬性,并且可以體現類與類之間的包含關系
class Outer{ // 外部類 class Inner{ // 內部類 } } class Other{ // 外部其他類 }-
4大內部類
- 定義在區域位置上
- 區域內部類
- 匿名內部類
- 定義在成員位置上
- 成員內部類
- 靜態內部類
- 定義在區域位置上
-
區域內部類
區域內部類是定義在外部類的區域位置(方法、代碼塊中)
區域內部類擁有類的所有特性(JDK8不支持main方法定義在區域內部類中,且不能定義靜態成員)
- 區域內部類的作用域:僅僅在定義它的方法、代碼塊中(外部其他類無法訪問區域位置)
- 外部其他類、外部類 與 區域內部類的相互訪問
- 區域內部類 訪問 外部類成員(直接訪問,因為屬于同類中)
- 區域內部類 訪問 外部其他類,直接new
- 外部類 訪問 區域內部類,創建區域內部類物件再訪問(注意必須在作用域內創建物件)
- 外部其他類 訪問 區域內部類(無法訪問,因為區域內部類的地位是區域變數)
- 外部類 與 區域內部類 成員重名時,默認遵守就近原則( 外部類.this.成員 來訪問外部類成員)
public class MainApp { public static void main(String[] args) { new Outer().m2(); } } //外部類 class Outer{ private int numberN1 = 123; // 成員變數(私有) private String m1(){ // 成員方法(私有) return "this is Outer class :)"; } public void m2(){ double dN2 = 1.0; // 區域變數 // 區域內部類 final class Inner{ public int numberN1 = 321; public String m1(){ return "this is Inner class :)"; } public void test(){ //訪問本類中的欄位(區域內部類) System.out.println(numberN1); // 321 //訪問外部類中的欄位 System.out.println(Outer.this.numberN1); // 123 //訪問本類中的方法(區域內部類) System.out.println(this.m1()); // this is Inner class :) //訪問外部類中的方法 System.out.println(Outer.this.m1()); // this is Outer class :) //訪問外部其他類的方法 System.out.println(new Other().m4()); // This is Other class :) } } //外部類中訪問區域內部類 Inner inner = new Inner(); inner.test(); } } //外部其他類 class Other{ // 外部其他類 無法訪問區域內部類 : Inner類 public String m4(){ return "This is Other class :)"; } } -
匿名內部類
匿名內部類定義在外部類的區域位置,比如方法中
在代碼中是匿名的,但底層編譯器會賦予一個名字,但對程式員來說是匿名的
注意:匿名內部類中不能定義靜態成員(JDK8)
作用域:處于區域變數的作用域,因此作用域僅在定義它的方法、代碼塊中(外部其他類無法訪問區域位置)
-
使用匿名內部類的基本語法:
語法中包含了2層含義:
- new 類/介面(引數串列) : 表示創建了一個物件
- {類體} : 表示該類擁有的成員變數、成員方法
new 類/介面(引數串列){ 類體 }; -
匿名內部類的本質:編譯器創建一個class類繼承自目標類,然后創建其實體物件
-
使用匿名內部類創建介面實體
interface A{ int test(); } class MainApp{ public static void main(String[] args) { A a = new A(){ @Override public int test() { return 0; } }; // 使用匿名內部類創建了一個物件 (可以簡單的理解為一種語法糖) } }本質:
interface A{ int test(); } class MainApp{ public static void main(String[] args) { class MainApp$1 implements A{ @Override public int test() { return 0; } } // 編譯器創建了一個類繼承了介面A A a = new MainApp$1(); // 使用內部類創建了一個物件 } } -
使用匿名內部類創建類實體
由于匿名內部類的類是由編譯器創建的,因此在類體中是無法定義構造器的,創建實體時傳入的引數會默認走父類的目標構造器
class A{ public A(String str){ System.out.println(str); } } class MainApp{ public static void main(String[] args) { A a = new A("測驗"){}; // 使用匿名內部類創建了一個物件 (可以簡單的理解為一種語法糖) } }本質:
class A{ public A(String str){ System.out.println(str); } } class MainApp{ public static void main(String[] args) { class MainApp$1 extends A{ public MainApp$1(String str) { super(str); } } // 編譯器創建了一個類繼承了介面A A a = new MainApp$1("測驗"); // 使用內部類創建了一個物件 } }
-
-
訪問細節
與區域內部類相同,因為地位都是區域位置
- 區域內部類 訪問 外部類成員(直接訪問,因為屬于同類中)
- 區域內部類 訪問 外部其他類,直接new
- 外部類 訪問 區域內部類,創建區域內部類物件再訪問(注意必須在作用域內才能獲取到物件)
- 外部其他類 訪問 區域內部類(無法訪問,因為區域內部類的地位是區域變數)
- 外部類 與 區域內部類 成員重名時,默認遵守就近原則( 外部類.this.成員 來訪問外部類成員)
-
匿名內部類常見的使用方式:作為引數傳入
public class MainApp{ public static void main(String[] args) { m1(new A() { @Override public int test() { return -1; } }); } public static void m1(A a){ System.out.println(a.test()); // 這里就會使用到動態系結機制,根據傳入物件的不同而得到不同的結果 } } interface A{ int test(); }
-
-
成員內部類
成員內部類是定義在外部類的成員位置,并且沒有static修飾
注意:成員內部類中不能定義靜態成員(JDK8)
-
本質還是一個類,只不過位置在外部類的成員位置
作用域:整個類體
-
成員內部類 與 外部類其他類、外部類的相互訪問
- 成員內部類 訪問 外部類,直接訪問
- 成員內部類 訪問 外部其他類,(非靜態成員)創建物件再訪問,(靜態成員)使用外部其他類名.靜態成員名 訪問
- 外部類 訪問 成員內部類,創建內部類物件再訪問
- 外部其他類 訪問成員內部類,通過外部類的方法、公開的欄位獲取成員內部類實體再訪問
public class MainApp{ public static void main(String[] args) { Other other = new Other(); other.m5(); } } //外部類 class Outer{ private int numberN1 = 123; // 成員變數(私有) private String m1(){ // 成員方法(私有) return "this is Outer class :)"; } final class Inner{ public int numberN1 = 321; public String m1(){ return "this is Inner class :)"; } public void m2(){ System.out.println(numberN1); // 321 System.out.println(Outer.this.numberN1); // 123 System.out.println(m1()); // this is Inner class :) System.out.println(Outer.this.m1()); // this is Outer class :) System.out.println(Other.i); // 100 System.out.println(new Other().m4()); // This is Other class :) } } //外部類訪問成員內部類 public Inner m3(){ Inner inner = new Inner(); System.out.println(inner.numberN1); // 321 System.out.println(this.numberN1); // 123 inner.m2(); return inner; } } //外部其他類 class Other{ public static int i = 100; public String m4(){ return "This is Other class :)"; } //外部其他類訪問成員內部類 public void m5(){ Outer.Inner inner = new Outer().m3(); System.out.println(inner.numberN1); // 321 } }
-
-
靜態內部類
靜態內部類是定義在外部類的成員位置,并且有static修飾
-
相當于靜態成員,靜態成員能做的事它都能做(比如條件訪問修飾符),作用域是整個類
可以訪問外部類中所有的靜態成員
-
靜態內部類 與 外部類其他類、外部類的相互訪問
- 靜態內部類 訪問 外部類(靜態成員),直接訪問
- 靜態內部類 訪問 外部其他類
- (非靜態成員)創建物件再訪問
- (靜態成員)使用外部其他類名.靜態成員名 訪問
- 外部類 訪問 靜態內部類
- (非靜態成員)創建靜態內部類物件再訪問
- (靜態成員)使用靜態內部類名.靜態成員名 訪問
- 外部其他類 訪問 靜態內部類
- (非靜態成員)通過外部類的方法、公開的欄位獲取成員內部類實體再訪問
- (靜態成員)使用外部類名.靜態內部類名.靜態成員名 訪問
public class MainApp{ public static void main(String[] args) { Other other = new Other(); other.m5(); } } //外部類 class Outer{ private static int numberN1 = 123; // 成員變數(私有) private static String m1(){ // 成員方法(私有) return "this is Outer class :)"; } static class Inner{ public int numberN1 = 321; public static double d = 21.0; public String m1(){ return "this is Inner class :)"; } public void m2(){ System.out.println(numberN1); // 321 System.out.println(Outer.numberN1); // 123 System.out.println(m1()); // this is Inner class :) System.out.println(Outer.m1()); // this is Outer class :) System.out.println(Other.i); // 100 System.out.println(new Other().m4()); // This is Other class :) } } //外部類訪問靜態內部類 public Inner m3(){ System.out.println(Inner.d); // 21.0 Inner inner = new Inner(); System.out.println(inner.numberN1); // 321 System.out.println(numberN1); // 123 inner.m2(); return inner; } } //外部其他類 class Other{ public static int i = 100; public String m4(){ return "This is Other class :)"; } //外部其他類訪問靜態內部類 public void m5(){ System.out.println(Outer.Inner.d); // 21.0 Outer.Inner inner = new Outer().m3(); System.out.println(inner.numberN1); // 321 } }
-
-
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/556400.html
標籤:其他
上一篇:Java基礎語法
下一篇:返回列表
