【剑指 Java】第 1 弹:靠这份 Java 基础知识总结,我拿到了满意的 Offer
前言
因為博主是 2021 屆畢業生,當時為了準備秋招,特意總結的 Java 基礎知識面試高頻題,最后也算找到了挺滿意的工作。因此回饋給大家,希望能對大家起到一定的幫助。
0. 入門常識
0.1 Java 特點
0.2 Java 和 C++
- 相同點:兩者均為 OOP 語言,均支持 OOP 的三大特性(封裝、繼承、多態);
- 不同點:
- Java 不存在指針的概念,所以內存更加安全;
- Java 類是單繼承(但是接口可以多繼承),C++ 的類是多繼承;
- Java 中有自動內存管理機制,但是 C++ 中需要開發者手動釋放內存;
- C/C++ 中,字符串和字符數組最后均有一個額外的 \0 標志來表示結束,但 Java 中不存在這一概念;
0.3 JRE 和 JDK
- JRE(Java Runtime Environment),即 Java 運行時環境,是用來運行已經編譯過的 Java 程序所需內容的集合(JVM、Java 類庫、Java 命令等),不能用來開發新程序;
- JDK(Java Development Kit),即 Java 開發工具包,是功能齊全的 Java SDK,包含 JRE 擁有的一切,還有編譯器和其他工具,如果我們想要創建和編譯新程序,就必須使用到它;
0.4 Java 程序編譯過程
我們編譯的源代碼(xxx.java)經 JDK 中的 javac 命令編譯后,成為 JVM 能夠理解的 Java 字節碼(xxx.class),然后經由 JVM 加載,通過解釋器 逐行解釋執行,這就是為什么能經常聽見說 Java 是一門編譯和解釋共存的語言。
其中 JVM 是解釋 Java 字節碼(xxx.class) 的虛擬機,針對不同系統均有特定實現,方便一次編譯,多次運行,即 Java 語言的平臺獨立性;
1. 數據類型
1.1 基本數據類型
| byte | 8 | 1 | Byte | ?27-2^7?27 ~ 27?12^7-127?1 | 0 |
| short | 16 | 2 | Short | ?215-2^{15}?215 ~ 215?12^{15}-1215?1 | 0 |
| char | 16 | 2 | Character | \u0000 ~ \uffff(000 ~ 655356553565535) | u0000 |
| int | 32 | 4 | Integer | ?231-2^{31}?231 ~ 231?12^{31}-1231?1 | 0 |
| long | 64 | 8 | Long | ?263-2^{63}?263 ~ 263?12^{63}-1263?1 | 0L |
| float | 32 | 4 | Float | 3.4e?453.4e^{-45}3.4e?45 ~ 1.4e381.4e^{38}1.4e38 | 0.0f |
| double | 64 | 8 | Double | 4.9e?3244.9e^{-324}4.9e?324 ~ 1.8e3081.8e^{308}1.8e308 | 0.0D |
| boolean | 不確定 | 不確定 | Boolean | true 或 false | false |
注意:
1.2 引用類型
| 數組 | null |
| 類 | null |
| 接口 | null |
1.3 封裝類
基本數據類型都有其對應的封裝類,兩者之間的賦值通過 自動裝箱 和 自動拆箱 來完成;
- 自動裝箱:將基本數據類型裝箱為封裝類;
- 自動拆箱:將封裝類拆箱為基本數據類型;
- 基本類型與對應封裝類的不同
- 基本類型只能按值傳遞,封裝類按引用傳遞;
- 基本類型 會在 棧 中創建,效率較高,但可能存在內存泄露問題;封裝類對象會在堆中創建,其 引用在棧中創建;
1.4 緩存池
以 new Integer(123) 和 Integer.valueOf(123) 為例:
- 通過 new 的方式每次都會創建一個新的對象;
- 通過 valueOf() 的方式則會優先判斷該值是否位于緩存池,如果在的話就直接返回緩存池中的內容,多次調用指向同一個對象的引用;
| Byte | ?27-2^7?27 ~ 27?12^7-127?1 |
| Character | \u0000 ~ \u007F |
| Short | ?27-2^7?27 ~ 27?12^7-127?1 |
| Integer | ?27-2^7?27 ~ 27?12^7-127?1 |
| Boolean | true & false |
2. 字符串 String
2.1 定義
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */private final char value[]; }上述代碼為 Java 8 中 String 的定義,其底層實際上使用的是字符(char)數組,而且由于被聲明為 final,代表著它 不能被繼承。而且一旦初始化之后就不能再去引用其他數組,這樣就保證了 String 的不可變性,也因此 String 是線程安全的。
2.2 不可變性的優點
由于 String 的 hash 值被頻繁使用,它的不可變性使得 hash 值也不可變,此時只需要進行一次計算;
如果一個 String 對象已經被創建過,那么就會優先從字符串常量池中獲取其引用,其不可變性確保了不同引用指向同一 String 對象;
我們經常用 String 作為我們方法的參數,其不變性能夠保證參數不可變;
String 的不可變性讓它天生 具備線程安全,能夠在多個線程中方便使用而不用考慮線程安全問題。
2.3 String vs StringBuffer vs StringBuffer
主要從三個方面對三者進行對比:
| String | 不可變 | 安全 | 操作少量的數據 |
| StringBuffer | 可變 | 安全,內部使用 synchronized 進行同步 | 多線程操作字符串緩沖區下操作大量數據 |
| StringBuilder | 可變 | 不安全 | 單線程操作字符串緩沖區下操作大量數據,性能高于 StringBuffer |
2.4 字符串常量池(String Pool)
String Pool 位于 方法區,通常保存著所有 字符串字面量(literal strings),在編譯期間就被確定。此外,還可以用 String 中的 intern() 方法在運行過程中添加到 String Pool 中。當一個字符串調用 intern() 時,如果 String Pool 中已經存在字面量相同的字符串,則會返回 String Pool 中的引用;如果不存在,則向 String Pool 中添加一個新的字符串,同時返回新字符串的引用;
String s1 = new String("aaa"); String s2 = new String("aaa"); // false 兩個字符串指向不同對象 System.out.println(s1 == s2); String s3 = s1.intern(); String s4 = s1.intern(); // true,常量池中存在字面量相同的字符串,直接取出 System.out.println(s3 == s4);在下面的代碼中,內存分析如下圖:
String str1 = "村雨遙"; String str2 = "村雨遙"; String str3 = new String("村雨遙"); String str4 = new String("村雨遙");// true,兩個引用指向常量池中的同一對象 System.out.println(str1 == str2); // false,兩個引用指向堆中不同對象 System.out.println(str3 == str4);2.5 new String(“xxx”)
使用 new 的方式創建字符串對象,會有兩種不同的情況:
此時會創建兩個字符串對象,“xxx” 屬于字符串字面量,因此在編譯期會在 String Pool 中創建一個字符串對象,用于指向該字符串的字面量 “xxx”;然后 new 會在堆中創建一個字符串對象;
此時只需要創建一個字符串對象,由于 String Pool 中已經存在指向 “xxx” 的對象,所以直接在堆中創建一個字符串對象;
3. 基礎語法
3.1 注釋
- 單行注釋
- 多行注釋
- 文檔注釋
3.2 常見關鍵字
3.3 標識符和關鍵字
- 標識符:用于給程序、類、對象、變量、方法、接口、自定義數據類型等命名;
- 關鍵字:特殊的標識符,被 Java 賦予了特殊含義,只能有特定用途;
- 標識符命名規則(可以參考《阿里巴巴開發手冊》,關注公眾號【村雨遙】回復【資源下載】下載 PDF)
- 標識符由英文字符大小寫(a - z, A - Z)、數字(0 - 9)、下劃線(_)和美元符號($)組成;
- 不能以數字開頭,不能是關鍵字;
- 嚴格區分大小寫;
- 包名:多個單詞組成是所有單詞均小寫;
- 類名和接口:大寫駝峰命名法;
- 變量名和函數名:多個單詞組成時,第一個單詞全小寫,其他單詞采用大寫駝峰命名法;
- 常量名:字母全部大寫,單詞之間用下劃線(_)分割;
3.4 訪問控制符
| public | 😀 | 😀 | 😀 | 😀 |
| protected | 😀 | 😀 | 😀 | 😡 |
| default | 😀 | 😀 | 😡 | 😡 |
| private | 😀 | 😡 | 😡 | 😡 |
3.5 static、final、this、super
static 主要有如下 4 中使用場景:
- 修飾成員變量和成員方法:被 static 修飾的成員屬于類,屬于靜態成員變量,存儲在 Java 內存中的 方法區,不屬于單個對象,被所有對象共享,而且最好通過 類名.靜態成員名/靜態方法名() 調用;
- 靜態代碼塊:定義在類中方法外,先于非靜態代碼塊之前執行(靜態代碼塊 -> 非靜態代碼塊 -> 構造方法) ,而且不管執行多少次創建新對象的操作,靜態代碼只執行一次;
- 靜態內部類:static 要修飾類時,只有修飾內部類這一種用法。 非靜態內部類在編譯后會隱含保存一個引用,用于指向創建它的外部類,但是靜態內部類不存在。即 內部類的創建不用依賴外圍類的創建,同時內部類也只能使用任意外部類的 static 成員變量和方法;
- 靜態導包:用于導入靜態資源,import static 用于指定導入某一類中的靜態資源,然后我們就可以直接使用類中的靜態成員變量和方法;
- 注意:
- abstract 方法不能同時是 static 的,因為 abstract 方法需要被重寫,但 static 方法不可以;
- 不能從 static 方法內部發出對非靜態方法的調用,因為靜態方法只能訪問靜態成員,而非靜態方法的調用需要先創建對象;
- static 不能用于修飾局部變量;
- 內部類與靜態內部類的區別:靜態內部類相對外部類是獨立存在的,在靜態內部類中無法直接訪問外部類中變量和方法。如果要進行訪問,則必須 new 一個外部類對象,使用該對象來進行訪問,但對于靜態變量和靜態方法,能夠直接調用。而普通的內部類作為外部類的一個成員而存在,能夠直接訪問外部類屬性,調用外部類方法。
- 修飾類時,被修飾的類不能被繼承,而且類中所有成員方法均被隱式指定為 final 方法;
- 修飾方法時,表明該方法無法被重寫;
- 修飾變量時,說明該 變量是一個常量。若變量為基本數據類型,則一旦初始化后不能再改變;若變量是引用類型,則初始化后不能指向其他對象;
用于引用類的當前實例,比如我們最常用的構造方法中,注意不能用在 static 方法中;
public class User{int age;public User(int age){this.age = age;} }其中 this.age 說明訪問的是 User 類中的成員變量,而后面的 age 則代表傳入的形參;
用于從子類訪問父類中的變量和方法,注意不能用在 static 方法中;
public class Father{String name;public Father(String name){this.name = name;}public Father(){} } public class Son extends Father{public Son(String name){super();this.name = name + ".jr";} }3.6 continue、break 和 return
| continue | 用于循環結構,指跳出當前循環,進入下一次循環 |
| break | 用于循環結構,指跳出整個循環體,繼續執行循環下面的語句 |
| return | 1. return ; :直接用 return 結束方法執行,用于沒有返回值函數的方法; 2. return value; :return 一個特定值,用于有返回值函數的方法 |
3.7 while 循環與 do 循環
while 循環結構在循環開始前會判斷下一個迭代是否應該繼續,可能一次循環體都不執行;
do……while 會在循環的結果來判斷是否繼續下一輪迭代,至少會執行一次循環體;
3.8 final、finally、finalize
final 既是一個修飾符,也是一個關鍵字,修飾不同對象時,表示的意義也不一樣;
- 修飾類: 表示該類無法被繼承;
- 修飾變量:若變量是基本數據類型,則其數值一旦初始化后就不能再改變,若變量是引用類型,則在其初始化之后便不能再讓其指向另一個對象,但其指向的對象的內容是可變的;
- 修飾方法:表示方法無法被重寫,但是允許重載,private 方法會隱式指定為 final 方法;
- finally 是一個關鍵字,在異常處理時提供 finally 塊來執行任何清除操作,無論是否有異常被拋出或捕獲,finally 塊均會被執行,通常用于釋放資源;
- finally 正常情況下一定會被執行,但是在如下兩種情況下不會執行:
- 對應的 try 未執行,則該 try 塊的 finally 塊并不會被執行;
- 若 try 塊中 JVM 關機,則 finally 塊也不會執行;
- finally 中如果有 return 語句,則會覆蓋 try 或 catch 中的 return 語句,導致兩者無法 return,所以建議 finally 中不要存在 return 關鍵字;
finallize() 是 Object 類的 protected 方法,子類能夠覆蓋該方法以實現資源清理工作;
GC 在回收前均會調用該方法,但是 finalize() 方法存在如下問題:
- Java 語言規范不保證 finalize() 方法會被及時執行,也不保證他們一定被執行;
- finalize() 方法會帶來性能問題,因為 JVM 通常在單獨的低優先線程中完成 finalize 的執行;
- finalize() 方法中,可將待回收對象賦值給 GC Roots 可達的對象引用,從而達到對象再生的目的;
- finalize() 方法最多由 GC 執行一次(但是可以手動調用對象的 finalize 方法);
4. 運算符
4.1 算術運算
| + | 加法 - 相加運算符兩側的值 | A + B 等于 30 |
| - | 減法 - 左操作數減去右操作數 | A – B 等于 -10 |
| * | 乘法 - 相乘操作符兩側的值 | A * B等于200 |
| / | 除法 - 左操作數除以右操作數 | B / A等于2 |
| % | 取余 - 左操作數除以右操作數的余數 | B%A等于0 |
| ++ | 自增: 操作數的值增加1 | B++ 或 ++B 等于 21 |
| -- | 自減: 操作數的值減少1 | B-- 或 --B 等于 19 |
注意:++ 和 -- 可以放在操作數之前,也可以放在操作數之后;位于操作數之前時,先自增/減,再賦值;位于操作數之后,先賦值,再自增/減;總結起來就是 符號在前就先加/減,符號在后就后加/減。
4.2 關系運算符
| == | 檢查如果兩個操作數的值是否相等,如果相等則條件為真。 | (A == B)為假。 |
| != | 檢查如果兩個操作數的值是否相等,如果值不相等則條件為真。 | (A != B) 為真。 |
| > | 檢查左操作數的值是否大于右操作數的值,如果是那么條件為真。 | (A> B)為假。 |
| < | 檢查左操作數的值是否小于右操作數的值,如果是那么條件為真。 | (A <B)為真。 |
| >= | 檢查左操作數的值是否大于或等于右操作數的值,如果是那么條件為真。 | (A> = B)為假。 |
| <= | 檢查左操作數的值是否小于或等于右操作數的值,如果是那么條件為真。 | (A <= B)為真。 |
4.3 位運算符
| & | 如果相對應位都是1,則結果為1,否則為0 | (A&B),得到12,即0000 1100 |
| | | 如果相對應位都是 0,則結果為 0,否則為 1 | 如果相對應位都是 0,則結果為 0,否則為 1 |
| ^ | 如果相對應位值相同,則結果為0,否則為1 | (A ^ B)得到49,即 0011 0001 |
| ? | 按位取反運算符翻轉操作數的每一位,即0變成1,1變成0。 | (?A)得到-61,即1100 0011 |
| << | 按位左移運算符。左操作數按位左移右操作數指定的位數。 | A << 2得到240,即 1111 0000 |
| >> | 按位右移運算符。左操作數按位右移右操作數指定的位數。 | A >> 2得到15即 1111 |
| >>> | 按位右移補零操作符。左操作數的值按右操作數指定的位數右移,移動得到的空位以零填充。 | A>>>2得到15即0000 1111 |
4.4 邏輯運算符
| && | 稱為邏輯與運算符。當且僅當兩個操作數都為真,條件才為真。 | (A && B)為假。 |
| || | 稱為邏輯或操作符。如果任何兩個操作數任何一個為真,條件為真。 | (A || B)為真。 |
| ! | 稱為邏輯非運算符。用來反轉操作數的邏輯狀態。如果條件為true,則邏輯非運算符將得到false。 | !(A && B)為真。 |
4.5 賦值運算符
| = | 簡單的賦值運算符,將右操作數的值賦給左側操作數 | C = A + B將把A + B得到的值賦給C |
| += | 加和賦值操作符,它把左操作數和右操作數相加賦值給左操作數 | C + = A等價于C = C + A |
| -= | 減和賦值操作符,它把左操作數和右操作數相減賦值給左操作數 | C - = A等價于C = C - A |
| *= | 乘和賦值操作符,它把左操作數和右操作數相乘賦值給左操作數 | C * = A等價于C = C * A |
| /= | 除和賦值操作符,它把左操作數和右操作數相除賦值給左操作數 | C / = A,C 與 A 同類型時等價于 C = C / A |
| %= | 取模和賦值操作符,它把左操作數和右操作數取模后賦值給左操作數 | C%= A等價于C = C%A |
| << = | 左移位賦值運算符 | C << = 2等價于C = C << 2 |
| >>= | 右移位賦值運算符 | C >> = 2等價于C = C >> 2 |
| &= | 按位與賦值運算符 | C&= 2等價于C = C&2 |
| ^= | 按位異或賦值操作符 | C ^ = 2等價于C = C ^ 2 |
| |= | 按位或賦值操作符 | C | = 2等價于C = C | 2 |
4.6 條件運算符(? :)
也叫作三元運算符,共有 3 個操作數,且需要判斷布爾表達式的值;
variable x = (expression) ? value if true : value if false4.7 instanceof
用于操作對象實例,檢查該對象是否是一個特定類型(類類型或接口類型);
( Object reference variable ) instanceof (class/interface type)4.8 equals() 和 ==
- ==
基本數據類型用 == 比較的是值,用于引用數據類型時判斷兩個對象的內存地址是否相等,即兩對象是否是同一個對象;
本質來講,由于 Java 中只有值傳遞,所以不管是基本數據類型還是引用數據類型,比較的其實都是值,只不過引用類型變量存的值是對象的地址;
- equals()
作用也是判斷兩個對象是否相等,但是 不能用于基本數據類型變量的比較。存在于 Object() 類中,所以所有類都具有 equals() 方法存在兩種使用情況:
5. 方法
5.1 方法的類型
5.2 重載和重寫
重載就是同樣方法能夠根據輸入的不同,做出不同的處理。重載發生在 編譯期,而且在同一個類中,方法名必須相同,參數類型、參數個數、參數順序不同,返回值和訪問修飾符可以不同。 總的而言:重載就是同一類中多個同名方法根據不同傳參來執行不同的邏輯處理。
重寫是當子類繼承自父類的相同方法,輸入數據一樣,但最終響應不同于父類。重寫發生在 運行期,是子類對父類允許訪問的方法的實現邏輯進行改寫。重寫方法的方法名、參數列表以及返回值必須相同,拋出的異常范圍不超出父類,訪問修飾符的范圍也不能小于父類。此外,若父類方法別 private/final/static 修飾,則子類無法重寫父類方法,但 static 修飾的方法能被再次聲明。構造方法是個特例,不能被重寫。總結起來就是:重寫即子類對父類方法的改造,外部樣子不能改變,但能夠改變內部邏輯。
| 參數列表 | 必須不同 | 必須相同 |
| 返回類型 | 可不同 | 必須相同 |
| 訪問修飾符 | 可不同 | 不能比父類更嚴格 |
| 發生范圍 | 同一類中 | 父子類 |
| 異常范圍 | 可修改 | 可以減少或刪除,不能拋新異常或范圍更廣的異常 |
| 發生階段 | 編譯期 | 運行期 |
5.3 深/淺拷貝
淺拷貝是 按位拷貝對象,會創建一個新對象,該對象具有原始對象屬性值的精確拷貝。 若屬性是基本類型,則拷貝的是基本類型的值;若屬性是引用類型(內存地址),則拷貝的是內存地址。因此,一旦其中任一對象改變了該引用類型屬性,均會影響到對方;
深拷貝會 拷貝所有屬性,同時拷貝屬性指向的動態分配的內存。當對象和它引用的對象一起拷貝是即發生深拷貝,相比于淺拷貝,深拷貝速度較慢同時花銷更大。
淺拷貝后,改變其中任一份值都會引起另一份值的改變;而深拷貝后,改變其中任何一份值,均不會對另一份值造成影響;
5.4 值傳遞
推薦閱讀:https://juejin.im/post/5bce68226fb9a05ce46a0476
5.4.1 形參和實參
5.4.2 值傳遞和引用傳遞
方法被調用時,實參通過形參將其內容副本傳入方法內部,此時形參接收的內容實際上是實參的一個拷貝,因此在方法內對形參的任何操作均只針對于實參的拷貝,不會影響到實參原始值的內容。即 值傳遞的是實參的一個副本,對副本的操作不會影響實參原始值,也即無論形參如何變化,都不會影響到實參的內容。
public static void valueCrossTest(int age,float weight){System.out.println("傳入的age:"+age);System.out.println("傳入的weight:"+weight);age=33;weight=89.5f;System.out.println("方法內重新賦值后的age:"+age);System.out.println("方法內重新賦值后的weight:"+weight); }public static void main(String[] args) {int a=25;float w=77.5f;valueCrossTest(a,w);// a = 25,原始值不收影響System.out.println("方法執行后的age:"+a);// w = 77.5,原始值不收影響System.out.println("方法執行后的weight:"+w) }引用即指向真實內容的地址值,在方法調用時,實參的地址被傳遞給相應形參,在方法體內,形參和實參指向同一個地址內存,因此此時操作形參也會影響到實參的真實內容。
但 Java 中并 不存在引用傳遞,因為 無論是基本類型還是引用類型,在實參傳入形參時,均為值傳遞,即傳遞的都是一個副本,而非實參內容本身。
如果是對基本數據類型的數據進行操作,由于實參原始內容和副本都是存儲實際值,并且處于不同棧區,因此對形參的操作,實參原始內容不受影響。
如果是對引用類型的數據進行操作,分兩種情況,
- 一種是形參和實參保持指向同一個對象地址,則形參的操作,會影響實參指向的對象的內容。
- 另一種是形參被改動指向新的對象地址(如重新賦值引用),則形參的操作,不會影響實參指向的對象的內容。
6. 面向對象
6.1 面向對象 vs 面向過程
推薦閱讀:https://www.zhihu.com/question/27468564/answer/757537214
- 面向對象(Object Oriented)
面向過程是一種 對現實世界理解和抽象的方法,更容易維護、復用、擴展。最主要的特點就是 繼承、封裝、多態,所以 設計出的系統耦合性較低,但比起面向過程性能要低。
- 面向過程(Procedure Oriented)
面向過程是一種 以過程為中心 的編程思想,以正在發生為主要目標進行編程,不同于面向的的是誰受影響。最主要的不同就在于 封裝、繼承、多態,其性能比面向對象更高。
- 總結
面向對象的方式使得每個類都各司其職,最后整合到一起來共同完成一個項目,而面向過程則是讓一個類中的功能越來越多,就像一個全棧工程師能夠一個人搞定所有事。
6.2 封裝、繼承、多態
將客觀事物封裝為抽象的類,同時類能把自己的數據和方法只讓可信的類或對象進行操作,對不可信的類進行信息隱藏。即把屬于同一類事物的共性(屬性與方法)歸到一個類,從而方便使用。
通過 封裝,實現了 專業分工,將能實現特定功能的代碼封裝為獨立實體,供我們在需要時調用。此外,封裝還 隱藏了信息以及實現細節,使得我們通過訪問權限權限符就能將想要隱藏的信息隱藏起來。
可以使用現有類的所有功能,且無需重寫現有類來進行功能擴展,即個性對共性的屬性與方法的接受,并加入特性所特有的屬性與方法。通過繼承的新類叫做 子類/派生類,被繼承的類叫做 父類/基類/超類,具有如下特點:
- 子類擁有父類對象所有屬性和方法,但父類中的私有屬性和方法,子類是無法訪問的;
- 子類可以對父類進行擴展;
- 子類可以用自己的方式來實現父類的方法;
多態是允許 將父對象設置為和一個或多個其子對象相等的技術,賦值后,父對象能夠根據指向的子類對象的特性以不同方式運作,即 父類引用指向子類對象實例,有 重載和重寫 兩種實現方式。具有如下特點:
- 對象類型不可變,但引用類型可變;
- 對象類型和引用類型之間有繼承(類)/實現(接口)的關系;
- 方法具有多態性,但屬性不具有;
- 若子類重寫了父類方法,則真正執行的是子類覆蓋的方法,若子類未覆蓋父類方法,則調用父類的方法。
6.3 成員變量 vs 局部變量 vs 靜態變量
| 成員變量 | 1、 屬于類 2、能被訪問控制符、static、final 等修飾 | 堆 | 與對象共存亡 | 有,基本數據類型為對應默認值,而對象統一為 null | 對象調用 | 實例變量 |
| 局部變量 | 1、屬于方法(方法中的變量或參數) 2、不能被訪問控制符及 static 修飾,但可以被 final 修飾 | 棧 | 與方法共存亡 | 無,必須定義賦值后使用 | ||
| 靜態變量 | 1、屬于類 2、被 static 修飾,被所有類對象共用 | 方法區 | 與類共存亡 | 同成員變量初始化值 | 類名調用(推薦)、對象調用 | 類變量 |
6.4 構造方法的特點
6.5 抽象類 & 接口
-
接口中所有方法默認是 public,而且不能有實現(Java 8 之前,Java 8 開始可以有默認實現);
-
接口中所有變量均為 static、final,不能有其他變量;
-
一個類可以實現多個接口(通過 implements 關鍵字),而且接口自身可以通過 extends 來擴展多個接口;
-
接口是對行為的抽象,屬于行為規范;
- 抽象類中既可以有抽象方法,也可以有非抽象的方法;
- 一個類只能實現一個抽象類;
- 抽象方法可以被 public、protected、default 修飾,但不能用 private,否則不能被重寫;
- 抽象是對類的抽象,是一種模板設計;
6.6 Object 類中常見方法
| public final native Class<?> getClass() | 用于返回當前運行時對象的 Class 對象,使用了final 關鍵字修飾,故不允許子類重寫 |
| public native int hashCode() | 用于返回對象的哈希碼,主要使用在哈希表中,比如 JDK 中的 HashMap |
| public boolean equals(Object obj) | 用于比較 2 個對象的內存地址是否相等,String 類對該方法進行了重寫用戶比較字符串的值是否相等 |
| protected native Object clone() throws CloneNotSupportedException | 用于創建并返回當前對象的一份淺拷貝。一般情況下,對于任何對象 x,表達式 x.clone() != x 為true,x.clone().getClass() == x.getClass() 為 true。Object 本身沒有實現 Cloneable 接口,所以不重寫clone方法并且進行調用的話會發生CloneNotSupportedException 異常 |
| public String toString() | 返回類的名字@實例的哈希碼的16進制的字符串。建議Object所有的子類都重寫這個方法 |
| public final native void notify() | 不能重寫。喚醒一個在此對象監視器上等待的線程(監視器相當于就是鎖的概念)。如果有多個線程在等待只會任意喚醒一個 |
| public final native void notifyAll() | 不能重寫。跟notify一樣,唯一的區別就是會喚醒在此對象監視器上等待的所有線程,而不是一個線程 |
| public final native void wait(long timeout) throws InterruptedException | 不能重寫。暫停線程的執行注意:sleep方法沒有釋放鎖,而wait方法釋放了鎖 。timeout是等待時間,調用該方法后當前線程進入睡眠狀態,知道如下時間發生: 1. 其他線程調用該對象的 notify()/notifyAll() 方法; 2. 時間間隔到了; 3. 其他線程調用了 interrupt() 中斷該線程; |
| public final void wait(long timeout, int nanos) throws InterruptedException | 多了nanos參數,這個參數表示額外時間(以毫微秒為單位,范圍是 0-999999)。 所以超時的時間還需要加上 nanos 毫秒 |
| public final void wait() throws InterruptedException | 跟之前的 2 個 wait 方法一樣,只不過該方法一直等待,沒有超時時間這個概念 |
| protected void finalize() throws Throwable { } | 實例被垃圾回收器回收的時候觸發的操作 |
6.7 hashCode & equals
推薦閱讀:https://juejin.im/post/5a4379d4f265da432003874c
6.7.1 equals
| 自反性 | 對任意非空引用值 x,x.equals(x) 應該返回 true |
| 對稱性 | 對于任何非空引用值 x和 y,當 y.equals(x) 返回 true時,x.equals(y) 也應返回 true |
| 傳遞性 | 對于任何非空引用值x、y 和 z,如果 x.equals(y) 返回 true, 并且 y.equals(z) 返回 true,那么 x.equals(z) 也應返回 true |
| 一致性 | 對于任何非空引用值 x 和 y,多次調用 x.equals(y) 始終返回 true 或始終返回 false, 前提是對象上 equals比較中所用的信息沒有被修改 |
| 非空性 | 對于任何非空引用值 x,x.equals(null) 都應返回 false |
6.7.2 hashCode
hashCode 用于返回對象 hash 值,主要是為了加快查找的快捷性,因為 hashCode() 是 Object 類中的方法,所以所有 Java 類均有 hashCode(),在 HashTable 和 HashMap 這類的散列結構中,均是通過 hashCode() 來查找在散列表中位置,通過 hashCode 能夠較快的茶道小內存塊。
6.7.3 為什么重寫 equals() 必須重寫 hashCode()
6.8 序列化與反序列化
6.8.1 定義
- 序列化:指將對象轉換為字節序列的過程;
- 反序列化:指將字節序列轉換為目標對象的過程;
6.8.2 需要序列化的場景
當 Java 對象需要在網絡上傳輸或者持久化存儲到文件中時,我們就需要對象進行序列化;
6.8.3 如何實現序列化
要實現序列化,只需要讓類實現 Serializable 接口即可,此時就標注該類對象能夠被序列化;
針對類中某些數據不想序列化時,可以使用 transient 關鍵字來實現,例如:
// 通過關鍵字 transient 修飾,表明不參與序列化 transient private String telephone;總結
以上是生活随笔為你收集整理的【剑指 Java】第 1 弹:靠这份 Java 基础知识总结,我拿到了满意的 Offer的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 详细解读 PolarDB HTAP 的功
- 下一篇: java compiler类_利用 Ja