Java後端高頻知識點學習筆記1---Java基礎
Java後端高頻知識點學習筆記1---Java基礎
參考地址:牛_客_網
https://www.nowcoder.com/discuss/819297
1、過載和重寫的區別
過載:同一類中多個同名方法根據不同的傳參來執行不同的處理邏輯;方法名必須相同,引數型別不同、個數不同、順序不同;返回值型別可以相同也可以不同(因為返回值型別不是方法簽名的一部分)
重寫:子類對父類的方法的實現過程進行重新編寫。方法名,引數列表和返回值型別都不能改變。丟擲的異常範圍小於等於父類,訪問修飾符範圍大於等於父類。
什麼是方法簽名?
答:方法簽名,區分不同方法的標示符;方法是由方法名、形參列表、返回值以及方法體構成;方法簽名是由方法名與形參列表構成
構造器是否可以被重寫,是否可以被過載?
答:構造器可以被過載(Overload),不能被重寫(Override)
靜態方法能否被重寫,能否被過載?
答:靜態方法不能被重寫,可以被過載
靜態方法可以被繼承。靜態方法是類在載入時就被載入到記憶體中的方法,在整個執行過程中保持不變,因而不能重寫;在Java中,如果父類中含有一個靜態方法,且在子類中也含有一個返回型別、方法名、引數列表均與之相同的靜態方法,那麼該子類實際上只是將父類中的該同名方法進行了隱藏,而非重寫,可以通過類名.方法名呼叫被隱藏地方法;換句話說,父類和子類中含有的其實是兩個沒有關係的方法,它們的行為也並不具有多型性
2、Java面向物件的三大特性
封裝:把一個物件的屬性私有化,不允許外部物件直接訪問這些私有屬性,同時提供一些可以被外界訪問私有屬性的方法
繼承:子類繼承父類的非私有屬性和方法;子類可以對父類的方法進行重寫,也可以進行擴充套件,擁有自己的屬性和方法;一個子類只能擁有一個父類,但是可以通過實現多個介面來達到多重繼承的目的
多型:同一個操作作用在不同物件時,可以產生不同的執行結果;在Java語言中,多型主要有兩種表現形式,方法的過載和重寫
多型分為編譯時多型-->過載;執行時多型-->重寫
- 過載:同一個類中有多個同名方法,根據不同的傳參可以執行不同的處理邏輯;在編譯時就可以確定到底呼叫哪個方法,它是一種編譯時多型
- 重寫:子類對父類的方法的實現過程進行重新編寫,方法名,引數列表和返回值型別都不能改變,因此同樣的方法在父類與子類中有著不同的表現形式。
Java語言中,父類的引用變數不僅可以指向父類的例項物件,也可以指向子類的例項物件。而程式呼叫的方法在執行時才動態繫結,就是引用變數所指向的具體例項物件的方法,也就是記憶體中正在執行的那個物件的方法,而不是引用變數的型別中的定義的方法。這就會出現相同型別的變數呼叫同一個方法時呈現出多種不同的行為特徵,這就是多型;在執行時才能確定呼叫哪個方法,被稱為執行時多型
使用多型的好處?
多型:同一個操作作用在不同物件時,可以產生不同的執行結果;使用多型,可以解決程式碼的緊耦合的問題,提高程式的可擴充套件性
- 應用程式不必為每一個子類編寫功能呼叫,只需要對抽象父類進行處理即可,大大提高程式的可複用性
- 子類的功能可以被父類的方法或引用變數所呼叫,這叫向上相容,可以提高可擴充性和可維護性
3、Java面向物件的5大設計原則
原則 | 描述 |
---|---|
單一職責 | 一個類只負責一個功能的實現 |
里氏替換 | 只要父類出現的地方,都可以用子類替換 |
依賴倒置 | 高層模組不應該依賴低層模組,二者都應該依賴其抽象;就是面向介面程式設計 |
介面隔離 | 介面的功能儘可能單一;介面更可能細化,不要建立臃腫龐大的介面 |
開閉 | 儘量通過擴充套件來面對需求的更改或者系統的變化,儘量不要對原有內容修改 |
4、String、StringBuilder和StringBuffer的區別是什麼?
(1)String是不可變的,StringBuilder和StringBuffer是可變的
String不可變,String類中使用final關鍵字修飾char字元陣列來儲存字串;private final char value[],從Java9開始,String類的實現改用byte位元組陣列儲存字串;private final byte[] value
而StringBuilder與StringBuffer都繼承自AbstractStringBuilder類,在AbstractStringBuilder中也是使用字元陣列儲存字串char[] value但是沒有用final關鍵字修飾,所以這兩種物件都是可變的
(2)String和StringBuffer是執行緒安全的,StringBuilder不是執行緒安全的
String中的物件的不可變的,所以是執行緒安全
StringBuffer對方法加了synchronized同步鎖,所以是執行緒安全的
StringBuilder沒有對方法加同步鎖,所以不是執行緒安全的
(3)執行效率:StringBuilder最高,StringBuffer次之,String最低
每次對String型別進行改變的時候,都會生成一個新的String物件,然後將指標指向新的String物件。StringBuffer每次都會對StringBuffer本身進行操作,而不是生成新的物件並改變物件引用。相同情況下使用StringBuilder相比使用StringBuffer僅能獲得10%~15%左右的效能提升,但要冒多執行緒不安全的風險
對於三者使用的總結:
當操作少量資料時,優先使用String。
當在單執行緒下操作大量資料,優先使用StringBuilder類
當在多執行緒下操作大量資料,優先使用StringBuffer類
5、String為什麼要設定成不可變的?
(1)實現字串常量池
字串常量池(String pool)是Java堆記憶體中一個特殊的儲存區域,當建立一個String物件時,假如此字串值已經存在於常量池中,則不會建立一個新的物件,而是引用已經存在的物件;假若字串物件允許改變,那麼將會導致各種邏輯錯誤,比如改變一個物件會影響到另一個獨立物件,嚴格來說,這種常量池的思想,是一種優化手段
(2)允許String物件快取HashCode
Java中String物件的雜湊碼被頻繁地使用, 比如在HashMap等容器中;字串不變性保證了hash碼的唯一性;因此可以放心地進行快取,這也是一種效能優化手段,意味著不必每次都去計算新的雜湊碼.
(3)安全性
String被許多的Java類(庫)用來當做引數,例如:網路連線地址URL,檔案路徑path,還有反射機制所需要的String引數等,假若String不是固定不變的,將會引起各種安全隱患;
資料庫的使用者名稱、密碼都是以字串的形式傳入來獲得資料庫的連線,或者在socket程式設計中,主機名和埠都是以字串的形式傳入;因為字串是不可變的,所以它的值是不可改變的,否則黑客們可以鑽到空子,改變字串指向的物件的值,造成安全漏洞。
在併發場景下,多個執行緒同時讀寫資源時,由於 String 是不可變的,不會引發執行緒的問題而保證了執行緒安全
總體來說,String不可變的原因包括設計考慮,效率優化與安全性這三大方面
6、自動裝箱與拆箱
裝箱:將基本型別用它們對應的引用型別包裝起來
拆箱:將包裝型別轉化為基本資料型別
如下程式碼:
Integer i = 100; // 實際上是Integer.valueOf(100),
int n = i; // 實際上是 i.intValue()
如上程式碼所示,在裝箱的時候自動呼叫的是Integer的valueOf(int)方法;而在拆箱的時候自動呼叫的是Integer的intValue()方法。
詳解:在呼叫Integer.valueOf()方法中,如果數值在[-128,127]之間,將直接從IntegerCache中獲取
因為IntegerCache中快取了[-128,127]之間的值,通過Integer.valueOf()方法建立數值在[-128,127]之間的Integer物件時,便返回指向IntegerCache中已經存在的物件的引用;否則建立一個新的Integer物件
7、抽象類和介面的異同?
不同點:
序號 | 不同點 |
---|---|
1 | 在JDK1.8之前介面只有方法的定義,不能有方法的實現;JDK1.8中介面可以有預設方法(default修飾)和靜態方法(static修飾)的實現;JDK1.9開始介面中可以定義和實現私有方法(普通私有方法和靜態私有方法);而抽象類可以有方法的定義與實現 |
2 | 接口裡只能定義靜態常量(static final),不能定義普通成員變數;抽象類中既可以定義靜態常量,也能定義普通成員變數 |
3 | 介面中不能包含靜態程式碼塊,抽象類中可以包含靜態程式碼塊 |
4 | 一個類只能繼承一個抽象類,但是一個類可以實現多個介面 |
5 | 介面強調的是特定功能的實現,抽象類強調的是所屬關係 |
6 | main 方法:介面不能有 main 方法;抽象類可以有 main 方法,並且可以執行它 |
7 | 介面中定義的成員變數,只能是靜態常量,預設修飾符 public static final,而且必須給其賦初值;介面中定義的成員方法,抽象方法,預設修飾符為public abstract;抽象類中成員變數預設default(預設,什麼也不寫,同一包中可見),可在子類中被重新定義,也可被重新賦值;抽象類中抽象方法被abstract修飾,不能被private、static、synchronzed和native等修飾,必須以分號結尾,不帶花括號 |
8 | 介面中不包含構造器;抽象類裡可以包含構造器,抽象類中的構造器並不是用於建立物件,而是讓其子類呼叫這些構造器來完成屬於抽象類的初始化操作 |
9 | 介面被用於常用的功能,便於日後維護和新增刪除;抽象類更傾向於充當公共類的角色,不適用於日後重新對立面的程式碼修改;功能需要累積時用抽象類,不需要累積時用介面 |
相同點:
序號 | 相同點 |
---|---|
1 | 介面和抽象類都不能被例項化;介面的實現類或抽象類的子類都只有實現了介面或抽象類中的方法後才能被例項化 |
2 | 介面和抽象類都可以包含抽象方法 |
8、Java中" == "與equals()方法的區別?
" == ":對於八大基本資料型別來說,直接比較值;如果是引用資料型別,則是比較記憶體地址;(因為Java只有值傳遞,所以對於" == "來說,不管是比較基本資料型別,還是引用資料型別的變數,本質都是比較值,只是引用型別變數存的值是物件的地址)
equals():equals()是Object類提供的方法之一;每一個Java類都繼承自Object類,所以每一個物件都具有equals方法。Object中的equals方法是直接使用" == "運算子比較的兩個物件,所以在沒有重寫equals方法的情況下,equals與" == "運算子一樣,比較的是地址;可以通過重寫equals方法來比較兩個物件的內容是否相等
9、為什麼重寫equals()方法時必須重寫hashCode()方法?
equals()和hashCode()方法要遵循如下的原則:
1、如果兩個物件equals()方法相等,它們的hashCode返回值一定要相同
2、如果兩個物件的hashCode返回值相同,但它們的equals()方法不一定相等
3、兩個物件的hashcode()返回值不相等,則可以判定兩個物件的內容一定不相等
如果只重寫equals()方法而不重寫hashCode()方法,將會造成equals()方法判斷出的結果是true,但hashCode()返回值不同的情況,也可能會出現hashCode()方法返回值相等,但equals()方法相同的情況,因此equals()方法與hashCode()方法都要進行修改,使兩者遵循上述原則;即:equals方法被覆蓋,則hashCode方法也必須被覆蓋
問題:為什麼需要hashCode()方法?
答:使用hashCode()方法提前校驗,避免每一次比較都呼叫equals方法,提高效率
面試官:你有沒有重寫過equals()和hashcode()?
答:在使用HashMap的“key”的部分存放自定義的物件時,重寫過hashCode和equals方法,從而保證key是唯一的
10、Java中的八大基本資料型別
Java有8中基本資料型別,其中包括
6種數字型別:byte(1)、short(2)、int(4)、long(8)、float(4)、double(8)
1種字元型別:char(2)
1種布林型:boolean
對於boolean型,官方文件未明確定義,它依賴於JVM廠商的具體實現
11、final關鍵字
final用來修飾類、方法和變數
1、final修飾的類不能被繼承,final類中的所有成員方法都會被隱式的指定為final方法(但是final修飾的類中成員變數是可變的,如果想要final類的成員變數不可變,必須給成員變數增加final修飾)
2、final修飾的方法不能被重寫
3、final修飾的變數是常量;如果是基本資料型別的變數,則其數值一旦在初始化之後便不能更改;如果是引用型別的變數,則在對其初始化之後便不能讓其指向另一個物件
12、static關鍵字
static關鍵字主要有以下4種用法:
1、修飾成員變數和成員方法
被static修飾的成員屬於類,被類中所有物件共享,可通過類名.成員變數或物件.成員變數的方式呼叫
static變數也稱作靜態變數,靜態變數和非靜態變數的區別是:靜態變數被所有的物件所共享,在記憶體中只有一個副本,它當且僅當在類初次載入時會被初始化;而非靜態變數是物件所擁有的,在建立物件的時候被初始化,存在多個副本,各個物件擁有的副本互不影響;static成員變數的初始化順序按照定義的順序進行初始化
static方法也稱作靜態方法,靜態方法不依賴於任何物件就可以進行訪問,靜態方法只能訪問靜態成員,但不能訪問類的非靜態成員,因為非靜態成員必須依賴具體的物件才能夠被呼叫(成員:成員方法與成員變數)
2.靜態程式碼塊
靜態程式碼塊定義在類中方法外,類中可以有多個static塊
靜態程式碼塊在非靜態程式碼塊之前按照static塊的順序來執行每個static塊(靜態程式碼塊-->非靜態程式碼塊-->構造方法)
一個類不管建立多少物件,靜態程式碼塊只執行一次
3.靜態內部類
static修飾類的話只能修飾內部類;靜態內部類與非靜態內部類之間存在一個最大的區別: 非靜態內部類在編譯完成之後會隱含地儲存著一個引用,該引用是指向建立它的外圍類,但是靜態內部類卻沒有。沒有這個引用就意味著:<1>它的建立是不需要依賴外圍類的建立;<2>它不能使用任何外圍類的非靜態成員(成員:成員方法與成員變數)
4.靜態導包
用來匯入類中的靜態資源,1.5之後的新特性;可以指定匯入某個類中的指定靜態資源,並且不需要使用類名呼叫類中的靜態成員,可以直接使用類中靜態成員變數和成員方法。
靜態程式碼塊的載入次序:
父類靜態程式碼塊-->子類靜態程式碼塊-->父類非靜態程式碼塊-->父類建構函式-->子類非靜態程式碼塊-->子類建構函式
13、深拷貝和淺拷貝
淺拷貝:對基本資料型別進行值傳遞;對引用資料型別只是進行引用的傳遞,並沒有在記憶體中的建立一個新的物件,此為淺拷貝
深拷貝:對基本資料型別進行值傳遞;對引用資料型別,建立一個新的物件,並複製其內容,此為深拷貝
14、Java異常體系
Java中,所有的異常都有⼀個共同的祖先java.lang包中的Throwable類:Throwable類有兩個重要的⼦類:Exception(異常) 和 Error(錯誤),⼆者都是Java異常體系的重要⼦類,各⾃都包含⼤量⼦類
Error(錯誤):程式⽆法處理的錯誤,表示運⾏應⽤程式中較嚴重問題;⼤多數錯誤與程式碼編寫者執⾏的操作⽆關,⽽表示程式碼運⾏時JVM出現的問題;例如,Java 虛擬機器運⾏錯誤(Virtual MachineError),當 JVM 不再有繼續執⾏操作所需的記憶體資源時,將出現記憶體溢位(OutOfMemoryError);這些異常發⽣時,JVM⼀般會選擇終⽌執行緒
Exception(異常):程式本身可以處理的異常;Exception 類有⼀個重要的⼦類RuntimeException;RuntimeException異常由JVM丟擲;還有NullPointerException(要訪問的變數沒有引⽤任何物件時,丟擲該異常);ArithmeticException(算術運算異常,⼀個整數除以0時,丟擲該異常)以及 ArrayIndexOutOfBoundsException(陣列下標越界異常)
Exception(異常)又分為兩類:執行時異常和編譯時異常
1、執行時異常(不受檢異常):RuntimeException類及其子類表示JVM在執行期間可能出現的錯誤;比如說試圖使用空值物件的引用(NulIPointerException)、陣列下標越界(ArraylndexOutBoundException);此類異常屬於不可查異常,一般是由程式邏輯錯誤引起的,在程式中可以選擇捕獲處理,也可以不處理
2、編譯時異常(受檢異常):Exception中除RuntimeException及其子類之外的異常;如果程式中出現此類異常,比如說IOException,必須對該異常進行處理,否則編譯不通過;在程式中,通常不會自定義該類異常,而是直接使用系統提供的異常類
15、反射
1、什麼是Java的反射
反射是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取資訊以及動態呼叫物件的方法的功能稱為 Java 語言的反射機制
2、反射的作用
執行時:判斷物件所屬的類;構造一個物件所屬的類;判斷一個物件的方法和屬性;呼叫物件的方法和屬性
16、Java泛型
Java的泛型即"引數化型別",允許程式在建立集合時指定集合元素的型別,表示該集合只能儲存該型別的物件
為什麼要使用泛型?
答:如果不使用泛型,當把一個物件存入集合後,集合就會忘記這個物件的資料型別,再次取出該物件時,該物件的編譯型別就變成了Object型別,還需要進行強制轉換;當使用泛型後,集合中只能存入指定型別的物件,否則將報錯,並且將物件從集合取出後無需對元素進行強制轉換,就是原本的型別(指定的型別);
由此可見在集合中儲存物件並在使用前進行型別轉換是多麼的不方便;泛型防止了那種情況的發生,提供了編譯時的型別安全,確保只能把正確型別的物件放入集合中,避免了在執行時出現ClassCastException(型別轉換異常)
擴充套件知識:
父子物件之間的轉換分為了向上轉型和向下轉型;它們解釋如下:
向上轉型:通俗地講,就是將子類物件向上轉為父類物件,或者說是父類引用指向子類物件,此處父類物件可以是介面
格式:父類 變數名 = new 子類();
向上轉型的好處?
向上轉型後,父類引用可以呼叫子類重寫過的父類方法,當需要新添功能時,可以新增一個(子)類即可,而不用更改原父類程式碼
向上轉型後的物件不是新建立的父類物件,而是子類物件的"簡化"狀態,它不關心子類新增的功能,只關心子類繼承和重寫的功能;當一個類有很多子類時,並且這些子類都重寫了父類中的某個方法,使用上轉型物件呼叫這個方法時就可能具有多種形態,因為不同的子類在重寫父類的方法時可能產生不同的行為;也就是說,不同物件的上轉型物件呼叫同一方法可能產生不同的行為
向下轉型:與向上轉型相反,即是把父類物件轉為子類物件;一個已經向上轉型的子類物件,將父類引用轉為子類引用,可以使用強制轉換的格式
格式:子類 變數名 = (父類型別)父類變數;
為什麼要向下轉型?
向下轉型一般是為了重新獲得因為向上轉型而丟失的子類特性;因此,通常在向下轉型前已經有向上轉型,而向下轉型通常也會結合instanceof一起使用;藉由向下轉型,可以在靈活應用多型的基礎上,同時兼顧子類的獨有特徵(instanceof:用來測試一個物件是否為一個類的例項)
用法是:boolean result = obj instanceof Class
17、什麼是泛型擦除
在程式碼中定義List<Object>
和List<String>
等型別,在編譯後都會變成List,JVM看到的只是List,而由泛型附加的型別資訊對JVM是看不到的
在如下例子中,定義了兩個ArrayList陣列;一個是ArrayList
說明泛型型別String和Integer都被擦除掉了,只剩下原始型別
Java程式碼:
import java.util.*;
public class Test {
public static void main(String[] args) {
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
System.out.println("l1.getClass():"+l1.getClass().getName());
System.out.println("l1.getClass():"+l2.getClass().getName());
}
}
執行結果:
true
l1.getClass():java.util.ArrayList
l1.getClass():java.util.ArrayList
18、Java Object類中的方法
方法 | 含義 |
---|---|
getClass() | 返回一個物件的執行時類 |
int hashCode() | 返回該物件的雜湊碼值 |
boolean equals(Object obj) | 判斷其他物件是否與此物件“相等” |
String toString() | 返回該物件的字串表示 |
void notify() | 喚醒在此物件監視器上等待的單個執行緒 |
void notifyAll() | 喚醒在此物件監視器上等待的所有執行緒 |
void wait() | 導致當前的執行緒等待,直到其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 方法 |
protected Object clone() | 建立並返回此物件的一個副本 |
protected void finalize() | 當垃圾回收器確定不存在對該物件的更多引用時,由物件的垃圾回收器呼叫此方法 |
19、Java String類中的方法
20、Java建立物件的5種方式?
①使用new關鍵字
②使用Class類的newInstance方法
③使用Constructor類的newInstance方法
④使用clone方法
⑤使用反序列化
21、Java訪問修飾符的範圍
22、Hash衝突的解決方式?
1、開放定址法
當發生衝突時,使用某種探查技術在散列表中形成一個探查序列,沿此序列逐個單元地查詢,直到找到一個開放的地址(地址單元為空)或探查序列查詢完為止;若探查到開放的地址,則可將待插入的新結點存人該地址單元;若探查序列查詢完都沒有找到開放的地址,則失敗
按照形成探查序列的方法不同,可將開放定址法區分為線性探查法、二次探查法、隨機探查法
①線性探查法
②二次探查法
③隨機探查法
2.拉鍊法
將所有雜湊地址為i的元素構成一個稱為同義詞鏈的單鏈表,並將單鏈表的頭指標存到雜湊表的第i個單元中,因而查詢、插入和刪除主要在同義詞鏈中進行
拉鍊法(鏈地址法)適用於經常進行插入和刪除的情況
3.再Hash法
當計算出的hash值產生衝突時,再計算另一個Hash函式的雜湊值,直到衝突不再發生為止
4.建立公共溢位區
將雜湊表分為基本表和溢位表兩部分,凡是和基本表發生衝突的元素,一律填入溢位表
23、Java中的流分為幾種?
按照流的流向劃分,分為輸⼊流和輸出流
按照操作單元劃分,分為位元組流和字元流
按照流的⻆⾊劃分,分為節點流和處理流
Java IO流共涉及40多個類,這些類看上去很雜亂,但實際上很有規則,⽽且彼此之間存在⾮常緊密的聯絡;Java IO流的 40 多個類都是從如下4個抽象類基類中派⽣出來的;
InputStream/Reader: 所有的輸⼊流的基類,前者是位元組輸⼊流,後者是字元輸⼊流
OutputStream/Writer: 所有輸出流的基類,前者是位元組輸出流,後者是字元輸出流