Java後端高頻知識點學習筆記1—Java基礎
Java後端高頻知識點學習筆記1—Java基礎
參考地址:牛_客_網
//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: 所有輸出流的基類,前者是位元組輸出流,後者是字元輸出流