前言
因為部落客是 2021 屆畢業生,當時為了準備秋招,特意總結的 Java 基礎知識面試高頻題,最後也算找到了挺滿意的工作。是以回饋給大家,希望能對大家起到一定的幫助。
0. 入門常識
0.1 Java 特點
- 簡單易學
- 面向對象(封裝、繼承、多态)
- 平台獨立
- 安全可靠
- 支援多線程
- 解釋和編譯共存
- 安全性
- 健壯性(Java 語言的強類型機制、異常處理、垃圾的自動收集等)
- …
0.2 Java 和 C++
- 相同點:兩者均為 OOP 語言,均支援 OOP 的三大特性(封裝、繼承、多态);
- 不同點:
- Java 不存在指針的概念,是以記憶體更加安全;
- Java 類是單繼承(但是接口可以多繼承),C++ 的類是多繼承;
- Java 中有自動記憶體管理機制,但是 C++ 中需要開發者手動釋放記憶體;
- C/C++ 中,字元串和字元數組最後均有一個額外的
标志來表示結束,但 Java 中不存在這一概念;\0
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 基本資料類型
注意:
-
一般用 1boolean
來存儲,但是具體大小并未規定,JVM 在編譯期将bit
類型轉換為boolean
,此時 1 代表int
, 代表true
。此外,JVM 還指出false
數組,但底層是通過boolean
數組來實作;byte
- 使用
類型時,需要在後邊加上long
,否則将其作為整型解析,可能會導緻越界;L
- 浮點數如果沒有明确指定
還是float
,統一按double
處理;double
-
是用 單引号char
将内容括起來,相當于一個整型值(ASCII 值),能夠參加表達式運算;而‘’
是用 雙引号String
将内容括起來,代表的是一個位址值;“”
1.2 引用類型
資料類型 | 預設值 |
---|---|
數組 | null |
類 | null |
接口 | null |
1.3 封裝類
基本資料類型都有其對應的封裝類,兩者之間的指派通過 自動裝箱 和 自動拆箱 來完成;
- 自動裝箱:将基本資料類型裝箱為封裝類;
// 實際調用 Integer.valueOf(12)
Integer x = 12;
複制
- 自動拆箱:将封裝類拆箱為基本資料類型;
Integer x = 12;
// 實際調用 x.intValue()
int y = x;
複制
- 基本類型與對應封裝類的不同
- 基本類型隻能按值傳遞,封裝類按引用傳遞;
- 基本類型 會在 棧 中建立,效率較高,但可能存在記憶體洩露問題;封裝類對象會在堆中建立,其 引用在棧中建立;
1.4 緩存池
以
new Integer(123)
和
Integer.valueOf(123)
為例:
- 通過
的方式每次都會建立一個新的對象;new
- 通過
的方式則會優先判斷該值是否位于緩存池,如果在的話就直接傳回緩存池中的内容,多次調用指向同一個對象的引用;valueOf()
Integer x = new Integer(123);
Integer y = new Integer(123);
// false,通過 new 的方式,每次都會建立一個新對象,指向不同對象
System.out.println(x == y);
Integer m = Integer.valueOf(123);
Integer n = Integer.valueOf(123);
// true,通過 valueOf() 的方式,先到緩存池中查找,存在時則多次調用也是指向同一對象
System.out.println(m == n);
複制
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 不可變性的優點
- 用于緩存
值hash
由于
String
的
hash
值被頻繁使用,它的不可變性使得
hash
值也不可變,此時隻需要進行一次計算;
- 字元串常量池(String Pool)的需要
如果一個
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
的方式建立字元串對象,會有兩種不同的情況:
- String Pool 中不存在 “xxx”
此時會建立兩個字元串對象,“xxx” 屬于字元串字面量,是以在編譯期會在 String Pool 中建立一個字元串對象,用于指向該字元串的字面量 “xxx”;然後
new
會在堆中建立一個字元串對象;
- String Pool 中存在 “xxx”
此時隻需要建立一個字元串對象,由于 String Pool 中已經存在指向 “xxx” 的對象,是以直接在堆中建立一個字元串對象;
3. 基礎文法
3.1 注釋
- 單行注釋
// 這是單行注釋
String name = "村雨遙";
複制
- 多行注釋
/*
* 這是多行注釋
* name,公衆号
*/
String name = "村雨遙";
複制
- 文檔注釋
/**
* @author : 村雨遙
* @param : name,公衆号
*/
String name = "村雨遙";
複制
3.2 常見關鍵字
3.3 辨別符和關鍵字
- 辨別符:用于給程式、類、對象、變量、方法、接口、自定義資料類型等命名;
- 關鍵字:特殊的辨別符,被 Java 賦予了特殊含義,隻能有特定用途;
- 辨別符命名規則(可以參考《阿裡巴巴開發手冊》,關注公衆号【村雨遙】回複【資源下載下傳】下載下傳 PDF)
- 辨別符由英文字元大小寫(a - z, A - Z)、數字(0 - 9)、下劃線(
)和美元符号(_
)組成;$
- 不能以數字開頭,不能是關鍵字;
- 嚴格區分大小寫;
- 包名:多個單詞組成是所有單詞均小寫;
- 類名和接口:大寫駝峰命名法;
- 變量名和函數名:多個單詞組成時,第一個單詞全小寫,其他單詞采用大寫駝峰命名法;
- 常量名:字母全部大寫,單詞之間用下劃線(
)分割;_
- 辨別符由英文字元大小寫(a - z, A - Z)、數字(0 - 9)、下劃線(
3.4 通路控制符
作用域 | 目前類 | 同一 package 的類 | 子類 | 其他 package 的類 |
---|---|---|---|---|
public | 😀 | 😀 | 😀 | 😀 |
protected | 😀 | 😀 | 😀 | 😡 |
default | 😀 | 😀 | 😡 | 😡 |
private | 😀 | 😡 | 😡 | 😡 |
3.5 static、final、this、super
- static
static
主要有如下 4 中使用場景:
- 修飾成員變量和成員方法:被
修飾的成員屬于類,屬于靜态成員變量,存儲在 Java 記憶體中的 方法區,不屬于單個對象,被所有對象共享,而且最好通過static
調用;類名.靜态成員名/靜态方法名()
- 靜态代碼塊:定義在類中方法外,先于非靜态代碼塊之前執行(靜态代碼塊 -> 非靜态代碼塊 -> 構造方法) ,而且不管執行多少次建立新對象的操作,靜态代碼隻執行一次;
- 靜态内部類:
要修飾類時,隻有修飾内部類這一種用法。 非靜态内部類在編譯後會隐含儲存一個引用,用于指向建立它的外部類,但是靜态内部類不存在。即 内部類的建立不用依賴外圍類的建立,同時内部類也隻能使用任意外部類的static
成員變量和方法;static
- 靜态導包:用于導入靜态資源,
用于指定導入某一類中的靜态資源,然後我們就可以直接使用類中的靜态成員變量和方法;import static
- 注意:
-
方法不能同時是abstract
的,因為static
方法需要被重寫,但abstract
方法不可以;static
- 不能從
方法内部發出對非靜态方法的調用,因為靜态方法隻能通路靜态成員,而非靜态方法的調用需要先建立對象;static
-
不能用于修飾局部變量;static
- 内部類與靜态内部類的差別:靜态内部類相對外部類是獨立存在的,在靜态内部類中無法直接通路外部類中變量和方法。如果要進行通路,則必須
一個外部類對象,使用該對象來進行通路,但對于靜态變量和靜态方法,能夠直接調用。而普通的内部類作為外部類的一個成員而存在,能夠直接通路外部類屬性,調用外部類方法。new
-
- final
- 修飾類時,被修飾的類不能被繼承,而且類中所有成員方法均被隐式指定為
方法;final
- 修飾方法時,表明該方法無法被重寫;
- 修飾變量時,說明該 變量是一個常量。若變量為基本資料類型,則一旦初始化後不能再改變;若變量是引用類型,則初始化後不能指向其他對象;
- this
用于引用類的目前執行個體,比如我們最常用的構造方法中,注意不能用在
static
方法中;
public class User{
int age;
public User(int age){
this.age = age;
}
}
複制
其中
this.age
說明通路的是
User
類中的成員變量,而後面的
age
則代表傳入的形參;
- super
用于從子類通路父類中的變量和方法,注意不能用在
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
final
既是一個修飾符,也是一個關鍵字,修飾不同對象時,表示的意義也不一樣;
- 修飾類: 表示該類無法被繼承;
- 修飾變量:若變量是基本資料類型,則其數值一旦初始化後就不能再改變,若變量是引用類型,則在其初始化之後便不能再讓其指向另一個對象,但其指向的對象的内容是可變的;
- 修飾方法:表示方法無法被重寫,但是允許重載,
方法會隐式指定為private
方法;final
- finally
-
是一個關鍵字,在異常處理時提供finally
塊來執行任何清除操作,無論是否有異常被抛出或捕獲,finally
塊均會被執行,通常用于釋放資源;finally
-
正常情況下一定會被執行,但是在如下兩種情況下不會執行:finally
- 對應的
未執行,則該try
塊的try
塊并不會被執行;finally
- 若
塊中 JVM 關機,則try
塊也不會執行;finally
- 對應的
-
中如果有finally
語句,則會覆寫return
或try
中的catch
語句,導緻兩者無法return
,是以建議return
中不要存在finally
關鍵字;return
- finallize
finallize()
是
Object
類的
protected
方法,子類能夠覆寫該方法以實作資源清理工作;
GC 在回收前均會調用該方法,但是
finalize()
方法存在如下問題:
- Java 語言規範不保證
方法會被及時執行,也不保證他們一定被執行;finalize()
-
方法會帶來性能問題,因為 JVM 通常在單獨的低優先線程中完成finalize()
的執行;finalize
-
方法中,可将待回收對象指派給finalize()
可達的對象引用,進而達到對象再生的目的;GC Roots
-
方法最多由 GC 執行一次(但是可以手動調用對象的finalize()
方法);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 false
複制
4.7 instanceof
用于操作對象執行個體,檢查該對象是否是一個特定類型(類類型或接口類型);
( Object reference variable ) instanceof (class/interface type)
複制
4.8 equals() 和 ==
-
==
基本資料類型用
==
比較的是值,用于引用資料類型時判斷兩個對象的記憶體位址是否相等,即兩對象是否是同一個對象;
本質來講,由于 Java 中隻有值傳遞,是以不管是基本資料類型還是引用資料類型,比較的其實都是值,隻不過引用類型變量存的值是對象的位址;
-
equals()
作用也是判斷兩個對象是否相等,但是 不能用于基本資料類型變量的比較。存在于
Object()
類中,是以所有類都具有
equals()
方法存在兩種使用情況:
- 類未覆寫
方法:此時通過equals()
比較該類的兩個對象時,等價于equals()
比較這兩個對象,預設使用==
類中的Object
方法;equals()
- 類覆寫了
方法:一旦覆寫了該方法,則用來比較兩個對象的内容是否相等,如我們常用的equals()
就覆寫了String、BitSet、Data、File
方法;equals()
5. 方法
5.1 方法的類型
- 無參無傳回值;
- 無參有傳回值;
- 有參無傳回值;
- 有參有傳回值;
5.2 重載和重寫
- 重載(Overload)
重載就是同樣方法能夠根據輸入的不同,做出不同的處理。重載發生在 編譯期,而且在同一個類中,方法名必須相同,參數類型、參數個數、參數順序不同,傳回值和通路修飾符可以不同。 總的而言:重載就是同一類中多個同名方法根據不同傳參來執行不同的邏輯處理。
- 重寫(Override)
重寫是當子類繼承自父類的相同方法,輸入資料一樣,但最終響應不同于父類。重寫發生在 運作期,是子類對父類允許通路的方法的實作邏輯進行改寫。重寫方法的方法名、參數清單以及傳回值必須相同,抛出的異常範圍不超出父類,通路修飾符的範圍也不能小于父類。此外,若父類方法别
private/final/static
修飾,則子類無法重寫父類方法,但
static
修飾的方法能被再次聲明。構造方法是個特例,不能被重寫。總結起來就是:重寫即子類對父類方法的改造,外部樣子不能改變,但能夠改變内部邏輯。
- 重載 vs 重寫
不同點 | 重載 | 重寫 |
---|---|---|
參數清單 | 必須不同 | 必須相同 |
傳回類型 | 可不同 | 必須相同 |
通路修飾符 | 可不同 | 不能比父類更嚴格 |
發生範圍 | 同一類中 | 父子類 |
異常範圍 | 可修改 | 可以減少或删除,不能抛新異常或範圍更廣的異常 |
發生階段 | 編譯期 | 運作期 |
5.3 深/淺拷貝
- 淺拷貝
淺拷貝是 按位拷貝對象,會建立一個新對象,該對象具有原始對象屬性值的精确拷貝。 若屬性是基本類型,則拷貝的是基本類型的值;若屬性是引用類型(記憶體位址),則拷貝的是記憶體位址。是以,一旦其中任一對象改變了該引用類型屬性,均會影響到對方;
- 深拷貝
深拷貝會 拷貝所有屬性,同時拷貝屬性指向的動态配置設定的記憶體。當對象和它引用的對象一起拷貝是即發生深拷貝,相比于淺拷貝,深拷貝速度較慢同時花銷更大。
- 總結
淺拷貝後,改變其中任一份值都會引起另一份值的改變;而深拷貝後,改變其中任何一份值,均不會對另一份值造成影響;
5.4 值傳遞
推薦閱讀:https://juejin.im/post/5bce68226fb9a05ce46a0476
5.4.1 形參和實參
- 形參:方法被調用時需要傳遞進來的參數,如
中的func(String name)
就是一個形參,隻有在name
被調用時func
才被配置設定記憶體空間,當方法執行完後,name
将自動銷毀釋放空間;name
- 實參:方法調用時傳入的實際值,在方法調用前就已經被初始化且在方法調用時被傳入;
public static void func(String name){
System.out.println(name);
}
public static void main(String[] args) {
//實參
String name = "村雨遙";
func(name);
}
複制
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 中并 不存在引用傳遞,因為 無論是基本類型還是引用類型,在實參傳入形參時,均為值傳遞,即傳遞的都是一個副本,而非實參内容本身。
- 總結
如果是對基本資料類型的資料進行操作,由于實參原始内容和副本都是存儲實際值,并且處于不同棧區,是以對形參的操作,實參原始内容不受影響。
如果是對引用類型的資料進行操作,分兩種情況,
- 一種是形參和實參保持指向同一個對象位址,則形參的操作,會影響實參指向的對象的内容。
public static void PersonCrossTest(Person person){
System.out.println("傳入的person的name:" + person.getName());
person.setName("我是張小龍");
System.out.println("方法内重新指派後的name:" + person.getName());
}
複制
- 另一種是形參被改動指向新的對象位址(如重新指派引用),則形參的操作,不會影響實參指向的對象的内容。
public static void PersonCrossTest(Person person){
System.out.println("傳入的person的name:" + person.getName());
person=new Person();
person.setName("我是張小龍");
System.out.println("方法内重新指派後的name:" + person.getName());
}
複制
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 構造方法的特點
- 方法名與類名同名;
- 無傳回值,但不能用
關鍵字聲明;void
- 生成類對象時自動執行,無需顯式調用;
6.5 抽象類 & 接口
- 接口
- 接口中所有方法預設是
,而且不能有實作(Java 8 之前,Java 8 開始可以有預設實作);public
- 接口中所有變量均為
,不能有其他變量;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
- 重寫
方法的準則: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()
equals()
hashCode()
- 若兩個對象相等,則
一定也相同,因為hashCode()
是絕對可靠的;equals()
- 兩個對象相等,則兩個對象分别調用
方法也傳回equals()
;true
- 兩個對象有相同的
,他們不一定相等,因為hashCode()
不是絕對可靠的;hashCode()
- 如果重寫了
,但保留equals()
的實作不變,則可能出現兩者相等,但hashCode()
卻不一樣;hashCode
- 是以,一旦重寫了
方法,則必須重寫equals()
,hashCode()
的預設行為是對堆上的對象産生獨特值。如果沒有重寫hashCode()
,則該hashCode()
的兩個對象無論如何都不會相等(即使這兩個對象指向相同的資料)。class
6.8 序列化與反序列化
6.8.1 定義
- 序列化:指将對象轉換為位元組序列的過程;
- 反序列化:指将位元組序列轉換為目标對象的過程;
6.8.2 需要序列化的場景
當 Java 對象需要在網絡上傳輸或者持久化存儲到檔案中時,我們就需要對象進行序列化;
6.8.3 如何實作序列化
要實作序列化,隻需要讓類實作
Serializable
接口即可,此時就标注該類對象能夠被序列化;
針對類中某些資料不想序列化時,可以使用
transient
關鍵字來實作,例如:
// 通過關鍵字 transient 修飾,表明不參與序列化
transient private String telephone;
複制