JavaSE-Java基礎面試題

可道也發表於2021-09-01

過載與重寫的區別?

過載:本類中,方法名相同,引數列表不同,(引數型別、引數順序、引數個數),返回值型別可以不同,訪問修飾符可不同
重寫:子類中,方法名相同,引數不能改,返回值型別一致或其子類,訪問許可權大於等父類

== 與equals區別是什麼?

== :

在基本資料型別中 == 比較的是值;在引用資料型別中 == 比較的是地址

equals :

只能比較引用資料型別,預設比較地址,此時等同於 == ;但是String與Ingeter等方法重寫了equals方法,變成了值的比較。

final關鍵字的作用

變數:final修飾的變數必須要初始化,不能再修改
方法:final修飾的方法不能被重寫,可以被繼承
類:final修飾的類不能被繼承

static關鍵字

  1. 用來修飾成員變數,將其變為類的成員,從而實現所有物件對於該成員的共享;
  2. 用來修飾成員方法,將其變為類方法,可以直接使用“類名.方法名”的方式呼叫,常用於工具類;
  3. 靜態塊用法,將多個類成員放在一起初始化,使得程式更加規整,其中理解物件的初始化過程非常關鍵;在建立物件時,static修飾的成員會首先被初始化,而且我們還可以看到,如果有多個static修飾的成員,那麼會按照他們的先後位置進行初始化
  4. 靜態導包用法,將類的方法直接匯入到當前類中,從而直接使用“方法名”即可呼叫類方法,更加方便。

String、StringBuffer 和 StringBuilder 的區別是什麼?

String:

String類中有final修飾,所有String物件不可變。

StringBuffer與StringBuilder:

這倆是繼承AbstractBuilder類,AbstractBuilder類沒有final,所以是可變的;
StringBuffer 對方法加了同步鎖或者對呼叫的方法加了同步鎖,所以是執行緒安全的;
StringBuilder 並沒有對方法進行加同步鎖,所以是非執行緒安全的。

抽象類與介面的區別

  1. 介面中的變數預設用static和final修飾,不能有其他修飾的變數,必須給初始值
  2. 介面中的方法預設是public abstract,1.8之後可以有static 或者 default 修飾的方法體;
    而抽象類中,可以有普通方法和抽象方法,並可以有main方法
  3. 類可以繼承多個介面,但只能實現一個抽象類
  4. 抽象類可以有建構函式,介面沒有

單例設計模式特點及分類(你瞭解的設計模式有什麼(工廠設計模式 代理設計模式 裝飾設計模式)

特點:

只能例項化一次物件
只能有本類例項化(構造器私有化)
如果想要呼叫,對外一定提供一個獲取本類物件的方法

分類(執行緒不一樣):

懶漢:多執行緒 安全
惡漢:單執行緒 快捷

瞭解的:

各有優缺點:

  1. 代理設計模式:為某物件提供一種代理
    通過提供代理以控制對該物件的訪問。(找一個更熟悉的代理來替我們操作)

代理模式的應用場景:
如果已有的方法在使用的時候需要對原有的方法進行改進,此時有兩種辦法:
① 修改原有的方法來適應。這樣違反了“對擴充套件開放,對修改關閉”的原則。
② 採用一個代理類呼叫原有的方法,且對產生的結果進行控制。這種方法就是代理模式。
使用代理模式,可以將功能劃分的更加清晰,有助於後期維護!

  1. 裝飾設計模式:給一個物件增加一些功能,
    要求是動態的,並且裝飾物件和被裝飾物件要實現同一個介面

裝飾器模式的應用場景:
① 需要擴充套件一個類的功能。
② 動態的為一個物件增加功能,而且還能動態撤銷。(繼承不能做到這一點,繼承的功能是靜態的,不能動態增刪。

缺點:產生過多相似的物件,不易排錯

List、Set、Map 之間的區別

  1. List和Set繼承Collection介面;Map沒有繼承Collection介面,Map提供key到value的對映。一個Map中不能包含相同key,每個key只能對映一個value。
  2. List按物件進入的順序儲存物件,不做排序或編輯操作。List介面儲存一組不唯一,可重複(可以有多個元素引用相同的物件),有序的物件。
  3. Set注重獨一無二的性質,不允許重複的集合,對每個物件只接受一次,且無序。
  4. Map特點:元素按鍵值對儲存,無放入順序,不可重複。

List介面有三個實現類:LinkedList,ArrayList,Vector

  1. 同步性:LinkedList與ArrayList都是不同步,不保證執行緒安全,但效率高;而Vector執行緒同步,執行緒安全,但效率低
  2. 底層資料結構:ArrayList底層使用的是Object陣列,支援隨機訪問;LinkedList底層使用的是雙向連結串列資料結構,不支援隨機訪問,連結串列增刪快,查詢慢。
  3. 資料增長:ArrayList與Vector都有一個初始的容量大小,當儲存進它們裡面的元素的個數超過了容量時,就需要增加ArrayList與Vector的儲存空間,每次要增加儲存空間時,不是隻增加一個儲存單元,而是增加多個儲存單元,每次增加的儲存單元的個數在記憶體空間利用與程式效率之間要取得一定的平衡。即Vector增長原來的一倍,ArrayList增加原來的0.5倍。

Set介面有兩個實現類:HashSet,LinkedHashSet

HashSet:

為快速查詢設計的Set。存入HashSet的物件必須定義hashCode()

  1. 底層由HashMap實現
  2. HashSet的值存放在HashMap的key上
  3. HashMap的value統一為PRESENT
LinkedHashSet:

具有HashSet的查詢速度,且內部使用連結串列維護元素的順序(插入的次序)。於是在使用迭代器遍歷Set時,結果會按元素插入的次序顯示。

Map介面有三個實現類:HashMap,HashTable,LinkeHashMap

HashMap:

HashMap是Hashtable的輕量級實現(非執行緒安全的實現)非執行緒安全,高效,支援null鍵值(key)

HashTable:

HashTable執行緒安全,低效,不支援null

LinkeHashMap:

類似於HashMap,但是迭代遍歷它時,取得“鍵值對”的順序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一點。而在迭代訪問時發而更快,因為它使用連結串列維護內部次序。

Java中實現多執行緒的三種方式

通過繼承Thread類:

建立Thread的子類來繼承Thread類,並重寫run()方法,在測試類中呼叫start()方法開啟多執行緒

通過Runnable介面建立:

建立Runnable介面的實現類,重寫run()方法,並通過Thread類構造器建立關聯,通過關聯物件呼叫start()方法開啟多執行緒

通過Callable和Future建立:

建立Callable介面的實現類,並實現call()方法,該call()方法將作為執行緒執行體,並且有返回值;
使用FutureTask物件作為Thread物件的target建立並啟動新執行緒;呼叫FutureTask物件的get()方法來獲得子執行緒執行結束後的返回值。

兩種方式實現多執行緒,哪一種更好(如何開啟多執行緒?方式區別?)

如何開啟多執行緒:

無論是繼承,還是實現介面,都是呼叫Thread類中的start方法開啟

方式區別:

相同點:無論是繼承Thread類,還是實現Runnable介面,都是將多執行緒執行程式碼定義在run方法中,並都是通過start方法開啟多執行緒操作
不同點:實現Runnable介面需要增加一步與Thread建立關聯的操作,然後才能呼叫start方法
實際開發中,推薦使用Runnable介面方式,避免Java中單繼承的侷限性

執行緒有哪些基本狀態?

image
圖源《Java 併發程式設計藝術》4.1.4節

執行緒的 run()和 start()有什麼區別?

每個執行緒都是通過某個特定Thread物件所對應的方法run()來完成其操作的,方法run()稱為執行緒體。通過呼叫Thread類的start()方法來啟動一個執行緒。

start()方法來啟動一個執行緒,真正實現了多執行緒執行。這時無需等待run方法體程式碼執行完畢,可以直接繼續執行下面的程式碼; 這時此執行緒是處於就緒狀態, 並沒有執行。 然後通過此Thread類呼叫方法run()來完成其執行狀態, 這裡方法run()稱為執行緒體,它包含了要執行的這個執行緒的內容, Run方法執行結束, 此執行緒終止。然後CPU再排程其它執行緒。

run()方法是在本執行緒裡的,只是執行緒裡的一個函式,而不是多執行緒的。 如果直接呼叫run(),其實就相當於是呼叫了一個普通函式而已,直接待用run()方法必須等待run()方法執行完畢才能執行下面的程式碼,所以執行路徑還是隻有一條,根本就沒有執行緒的特徵,所以在多執行緒執行時要使用start()方法而不是run()方法。

sleep() 和 wait() 有什麼區別?

  1. sleep() 方法執行完成後,執行緒會自動甦醒,或者可以使用 wait(long timeout)超時後執行緒會自動甦醒;執行緒類(Thread)的靜態方法,通過Thread打點呼叫;不能改變物件的機鎖,執行緒雖然進入休眠,但是物件的機鎖沒有被釋放,其他執行緒依然無法訪問這個物件
  2. wait() 方法被呼叫後,執行緒不會自動甦醒,需要別的執行緒呼叫同一個物件上的 notify() 或者 notifyAll() 方法,我們將以上操作稱之為等待喚醒機制;當一個執行緒執行到wait()方法時,它就進入到一個和該物件相關的等待池,同時釋放物件的機鎖,使得其他執行緒能夠訪問
補充

notify喚醒執行緒池中先進入的
notifyAll喚醒執行緒池中所有的執行緒,然後這些被喚醒的執行緒再次“搶”佔CPU執行權;哪個執行緒“搶”到,哪個就執行;而沒搶到的再次回到執行緒池中;因此,有的執行緒可能剛被喚醒,就又等待了

什麼是執行緒死鎖?

兩個或兩個以上執行緒因競爭資源,而陷入彼此相互等待,無外力作用下永遠等待下去的現象

產生死鎖的必要條件

  1. 互斥條件:該資源任意一個時刻只由一個執行緒佔用。
  2. 請求與保持條件:程式已經保持了至少一個資源,但又提出了新的資源請求時,因請求資源而阻塞,但對已獲得的資源保持不放。
  3. 不剝奪條件:執行緒已獲得的資源在末使用完之前不能被其他執行緒強行剝奪,只有自己使用完畢後才釋放資源。
  4. 迴圈等待條件:若干程式之間形成一種頭尾相接的迴圈等待資源關係。

如何避免執行緒死鎖?

為了避免死鎖,我們只要破壞產生死鎖的四個條件中的其中一個就可以了。

  1. 破壞互斥條件:這個條件我們沒有辦法破壞,因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥訪問)。
  2. 破壞請求與保持條件:一次性申請所有的資源。
  3. 破壞不剝奪條件:佔用部分資源的執行緒進一步申請其他資源時,如果申請不到,可以主動釋放它佔有的資源。
  4. 破壞迴圈等待條件:靠按序申請資源來預防。按某一順序申請資源,釋放資源則反序釋放。破壞迴圈等待條件。

相關文章