【Java問題面試總結】

TJmer發表於2020-12-19

一、 Java基礎總結

大部分內容部分轉自 https://blog.csdn.net/ThinkWon/article/details/104390612,我只是針對大佬的博文進行自己相對常見問題的總結。大家想了解更多可以關注訂閱上述連結

目錄

1.JVM、JRE和JDK的關係

JVM:java虛擬機器,java跨平臺
JRE:java所需核心庫,主要包含java.lang包
JDK:java開發工具,內涵JRE,提供了java編譯工具,打包工具


2.JAVA資料型別

基本資料型別:整、浮、字元、布
引用型別:類、介面,陣列
在這裡插入圖片描述


3.final 有什麼用?

只是針對變數的引用
用於修飾類、屬性和方法;

  • 被final修飾的類不可以被繼承
  • 被final修飾的方法不可以被重寫
  • 被final修飾的變數不可以被改變,被final修飾不可變的是變數的引用,而不是引用指向的內容,引用指向的內容是可以改變的

4.this關鍵字的用法

指向物件本身的一個指標

  • 引用本類的建構函式
  • this相當於是指向當前物件本身
  • 形參與成員名字重名

5.super關鍵字的用法

指向的是離自己最近的一個父類

  • 指向當前物件的父類的引用
  • 區分子類和父類方法成員變數重名
  • 引用父類建構函式

6.this和super要注意的地方

  • super()和this()均需放在構造方法內第一行。
  • his()和super()都指的是物件,所以,均不可以在static環境中使用

7.static主要意義

  • 無物件呼叫方法 即使沒有建立物件,也能使用屬性和呼叫方法!
  • 物件共享用static修飾的變數和方法不屬於任何一個例項物件,但是可以被所有例項物件共享

8.物件導向、程式導向區別

  • 程式導向:強調效能(Linux、微控制器啥的)
  • 物件導向:設計出低耦合的系統,使系統更加靈活、更加易於維護

9.物件導向三大特性

  • 封裝 :隱藏細節,提供對外訪問方式,安全性和複用性高
  • 繼承:以已有類為基礎進行非私有擴充
  • 多型:引用變數所指向的具體型別通過該引用變數發出的方法在程式設計時不確定,只有在程式執行時才確定

10.抽象類和介面的對比

抽象類:模板設計
介面:行為抽象

相似處:

  • 介面和抽象類都不能例項化
  • 都位於繼承的頂端,用於被其他實現或繼承
  • 都包含抽象方法,其子類都必須覆寫這些抽象方法

不同處:

  • 宣告方式
  • 構造器
  • 實現
  • 繼承與實現
  • 欄位宣告
    在這裡插入圖片描述

11.內部類的分類有哪些

  • 成員內部類 :class作為成員變數
  • 區域性內部類 :在方法內部宣告
  • 靜態內部類 :static class
  • 匿名內部類:

12.匿名內部類特點

  • 必須繼承一個抽象類或者實現一個介面。
  • 不能定義任何靜態成員和靜態方法why
  • 所在的方法的形參需要被匿名內部類使用時,必須宣告為 final why?生命週期不一致,加了final,可以確保區域性內部類使用的變數與外層的區域性變數區分開
public class Outer {

    private void test(final int i) {
        new Service() {
            public void method() {
                for (int j = 0; j < i; j++) {
                    System.out.println("匿名內部類" );
                }
            }
        }.method();
    }
 }
 //匿名內部類必須繼承或實現一個已有的介面 
 interface Service{
    void method();
}

區域性變數直接儲存在棧中,當方法執行結束後,非final的區域性變數就被銷燬。而區域性內部類對區域性變數的引用依然存在,如果區域性內部類要呼叫區域性變數時,就會出錯。加了final,可以確保區域性內部類使用的變數與外層的區域性變數區分開,解決了這個問題。

public class Outer {
    private int age = 12;

    class Inner {
        private int age = 13;
        public void print() {
            int age = 14;
            System.out.println("區域性變數:" + age);
            System.out.println("內部類變數:" + this.age);
            System.out.println("外部類變數:" + Outer.this.age);
        }
    }

    public static void main(String[] args) {
        Outer.Inner in = new Outer().new Inner();
        in.print();
    }

}


13.過載(Overload)和重寫(Override)的區別

  • 過載: 同一個類中,方法名相同,引數列表(型別,個數,順序可不同)
  • 重寫:父子類中。方法名、引數列表必須相同。其他任何東西都小於父類(返回值,異常,訪問修飾符)

14.== 和 equals 的區別是什麼

  • == 和equal一般來說都是比較物件的記憶體地址
  • == 針對基本資料型別的比較是比較
  • ==針對引用資料型別的比較是比較記憶體地址
  • 針對String例子中重寫了equal方法

15.String中重寫的equal方法比較步驟

  • 先比較記憶體地址
  • 再比較型別是否為String
  • 比較String內容

16.hashCode 與 equals (重要)

  • equal方法被重寫時,hashCode方法也必須被重寫
  • 如果兩個物件相等equal為true,則hashcode一定也是相同的
  • 兩個物件有相同的hashcode值,它們也不一定是相等的

17.值傳遞和引用傳遞

  • Java程式設計語言總是採用按值呼叫,方法得到的是所有引數值的一個拷貝

在這裡插入圖片描述

  • 按引用傳遞時,雖然傳遞的是引用的拷貝,但是該拷貝的引用仍指向原來的物件
    在這裡插入圖片描述

18.BIO,NIO,AIO 有什麼區別?

  • BIO (Blocking I/O):Block IO 同步阻塞式 IO,就是我們平常使用的傳統 IO,它的特點是模式簡單使用方便,併發處理能力低。
  • NIO (New I/O):Non IO 同步非阻塞 IO,是傳統 IO 的升級,客戶端和伺服器端通過 Channel(通道)通訊,實現了多路複用。
  • AIO (Asynchronous I/O):Asynchronous IO 是 NIO 的升級,也叫 NIO2,實現了非同步非堵塞 IO ,非同步 IO 的操作基於事件和回撥機制。

19.反射

在程式執行的時候動態獲取類物件的資訊以及動態呼叫物件的方法
舉例

  • ①我們在使用JDBC連線資料庫時使用Class.forName()通過反射載入資料庫的驅動程式;
  • ②Spring框架也用到很多反射機制,最經典的就是xml的配置模式。Spring 通過 XML 配置模式裝載 Bean 的過程:
    • 1 將程式內所有 XML 或 Properties 配置檔案載入入記憶體中;
    • 2 Java類裡面解析xml或properties裡面的內容,得到對應實體類的位元組碼字串以及相關的屬性資訊;
    • 3 使用反射機制,根據這個字串獲得某個類的Class例項;
    • 4動態配置例項的屬性

20.Java獲取反射的三種方法

  • 1.通過new物件實現反射機制 getClass
  • 2.通過路徑實現反射機制 Class.forName
  • 3.通過類名實現反射機制 類.class
public class Student {
    private int id;
    String name;
    protected boolean sex;
    public float score;
}

public class Get {
    //獲取反射機制三種方式
    public static void main(String[] args) throws ClassNotFoundException {
        //方式一(通過建立物件)
        Student stu = new Student();
        Class classobj1 = stu.getClass();
        System.out.println(classobj1.getName());
        //方式二(所在通過路徑-相對路徑)
        Class classobj2 = Class.forName("fanshe.Student");
        System.out.println(classobj2.getName());
        //方式三(通過類名)
        Class classobj3 = Student.class;
        System.out.println(classobj3.getName());
    }
}

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

  • 可變性
    String不可變:原因使用final修飾的字元陣列儲存字串(private final char value[])
    StringBufferStringBulider繼承AbstractStringBuilder類,都普通使用char[] value儲存字串
  • 執行緒安全性
    String不可變->參量->執行緒安全
    StringBuffer有同步鎖->執行緒安全
    StringBuilder無鎖->執行緒不安全
  • 效能
    String每次改變的時候都會生成一個新的String物件,並將原來的指標指向新的String物件
    StringBuffer/StringBulider 只是對StringBuffer/StringBuilder物件本身進行操作,並不會生成新的物件。相同情況下,StringBulider比StringBuffer效能高。

對於三者使用的總結

  • 如果要操作少量的資料用 = String
  • 單執行緒操作字串緩衝區下操作大量資料 = StringBuilder
  • 多執行緒操作字串緩衝區下操作大量資料 = StringBuffer

22.Java常見異常有哪些

java.lang.IllegalAccessError違法訪問錯誤。當一個應用試圖訪問、修改某個類的域(Field)或者呼叫其方法,但是又違反域或方法的可見性宣告,則丟擲該異常。

java.lang.InstantiationError例項化錯誤。當一個應用試圖通過Java的new操作符構造一個抽象類或者介面時丟擲該異常.

java.lang.OutOfMemoryError記憶體不足錯誤。當可用記憶體不足以讓Java虛擬機器分配給一個物件時丟擲該錯誤。

java.lang.StackOverflowError堆疊溢位錯誤。當一個應用遞迴呼叫的層次太深而導致堆疊溢位或者陷入死迴圈時丟擲該錯誤。

java.lang.ClassCastException類造型異常。假設有類A和B(A不是B的父類或子類),O是A的例項,那麼當強制將O構造為類B的例項時丟擲該異常。該異常經常被稱為強制型別轉換異常。

java.lang.ClassNotFoundException找不到類異常。當應用試圖根據字串形式的類名構造類,而在遍歷CLASSPAH之後找不到對應名稱的class檔案時,丟擲該異常。

java.lang.ArithmeticException算術條件異常。譬如:整數除零等。

java.lang.ArrayIndexOutOfBoundsException陣列索引越界異常。當對陣列的索引值為負數或大於等於陣列大小時丟擲。

(執行時)java.lang.IndexOutOfBoundsException索引越界異常。當訪問某個序列的索引值小於0或大於等於序列大小時,丟擲該異常。

java.lang.InstantiationException例項化異常。當試圖通過newInstance()方法建立某個類的例項,而該類是一個抽象類或介面時,丟擲該異常。

java.lang.NoSuchFieldException屬性不存在異常。當訪問某個類的不存在的屬性時丟擲該異常。

java.lang.NoSuchMethodException方法不存在異常。當訪問某個類的不存在的方法時丟擲該異常。

(執行時)java.lang.NullPointerException空指標異常。當應用試圖在要求使用物件的地方使用了null時,丟擲該異常。譬如:呼叫null物件的例項方法、訪問null物件的屬性、計算null物件的長度、使用throw語句丟擲null等等。

java.lang.NumberFormatException數字格式異常。當試圖將一個String轉換為指定的數字型別,而該字串確不滿足數字型別要求的格式時,丟擲該異常。

java.lang.StringIndexOutOfBoundsException字串索引越界異常。當使用索引值訪問某個字串中的字元,而該索引值小於0或大於等於序列大小時,丟擲該異常。


23.常見的 RuntimeException 有哪些? ClassCastException(類轉換異常)

  • IndexOutOfBoundsException(陣列越界)
  • NullPointerException(空指標)
  • ArrayStoreException(資料儲存異常,運算元組時型別不一致)
  • 還有IO操作的BufferOverflowException異常

24.Java異常處理最佳實踐

  • 在finally中清理資源:close
  • 優先明確的異常
  • 對異常進行文件說明
  • 優先捕獲最具體的異常
  • 包裝異常時不要拋棄原始的異常

二、Java集合總結

1.List,Set,Map三者的區別?

Java 容器分為 Collection 和 Map 兩大類在這裡插入圖片描述

  • List:有序,可重複,可null。ArrayList、LinkedList 和 Vector
  • Set:一個無序,不可重複,只允許存入一個null元素。HashSet、LinkedHashSet 以及 TreeSet)
  • Map:鍵值對集合,可重複,不為null。HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap

2.Java集合的快速失敗機制 “fail-fast”?

  • 集合的一種錯誤檢測機制
  • 多個執行緒對集合進行結構上的改變的操作時,有可能會產生 fail-fast 機制

例如:假設存在兩個執行緒(執行緒1、執行緒2),執行緒1通過Iterator在遍歷集合A中的元素,在某個時候執行緒2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的內容),那麼這個時候程式就會丟擲 ConcurrentModificationException 異常,從而產生fail-fast機制。

原理如下:
迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個 modCount 變數。集合在被遍歷期間如果內容發生變化,就會改變modCount的值。每當迭代器使用hashNext()/next()遍歷下一個元素之前,都會檢測modCount變數是否為expectedmodCount值,是的話就返回遍歷;否則丟擲異常,終止遍歷。


3.遍歷一個 List 有哪些不同的方式?每種方法的實現原理是什麼?Java 中 List 遍歷的最佳實踐是什麼?

  • for迴圈,用i作計數器,通過get()方法獲取集合元素的值。
  • foreach遍歷:內部也是採用了 Iterator 的方式實現,不需要顯式宣告 Iterator 或計數器
  • Iterator迭代器遍歷
  • 最佳方式是:支援Random Access用for,不支援則用lterator或foreach

4.迭代器Iterator

Iterator 介面提供遍歷任何 Collection 的介面
使用如下:

List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
  String obj = it. next();
  System. out. println(obj);
}

特點如下:

  • 可以邊遍歷邊修改
  • 只能單向遍歷,安全性高

5.ArrayList、Vector、LinkedList的區別

ArrayList是動態陣列結構的實現,LinkedList是雙向連結串列的資料結構,Vector是執行緒安全容器

  • 隨機訪問效率:ArrayList > LinkedList
  • 增加和刪除效率:ArrayList > LinkedList
  • 記憶體空間佔用: ArrayList>LinkedList
  • 執行緒安全:Vector > ArrayList\LinkedList
  • 擴容方面:Vector每次擴容增加1倍,ArrayList每次擴容則增加50%

6.HashSet實現原理

  • 底層使用HashMap實現,將其value作為HashMap的key儲存,因此HashSet的value是不可重複的。而HashMap的value則存入PRESENT的虛值
  • 新增元素操作需要進過兩層驗證:hashcode和equal
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;

public HashSet() {
    map = new HashMap<>();
}

public boolean add(E e) {
    // 呼叫HashMap的put方法,PRESENT是一個至始至終都相同的虛值
	return map.put(e, PRESENT)==null;
}

7.HashMap原理

基本實現:

基於Hash演算法:在往HashMap中put()儲存資料的時候,通過key值計算hashcode,從而獲得當前物件的陣列下標;

  • 若key的hash值相同,若key相同則覆蓋原先的值,若key不同則將該元素放入key-value連結串列中
  • 若key的hash值不同,則直接存入table陣列之中

在JDK1.7時候:

  • HashMap的資料結構為:拉鍊法:陣列+連結串列
    在這裡插入圖片描述
  • 陣列存放規則:無衝突時,存放陣列;衝突時,存放連結串列

JDK1.8之後

HashMap的資料結構為:拉鍊法:陣列+連結串列+紅黑樹
在這裡插入圖片描述
無衝突時,存放陣列;衝突 & 連結串列長度 < 8:存放單連結串列;衝突 & 連結串列長度 > 8:樹化並存放紅黑樹

HashMap的put方法的具體流程

在這裡插入圖片描述①.判斷鍵值對陣列table[i]是否為空或為null,否則執行resize()進行擴容;

②.根據鍵值key計算hash值得到插入的陣列索引i,如果table[i]==null,直接新建節點新增,轉向⑥,如果table[i]不為空,轉向③;

③.判斷table[i]的首個元素是否和key一樣,如果相同直接覆蓋value,否則轉向④,這裡的相同指的是hashCode以及equals;

④.判斷table[i] 是否為treeNode,即table[i] 是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對,否則轉向⑤;

⑤.遍歷table[i],判斷連結串列長度是否大於8,大於8的話把連結串列轉換為紅黑樹,在紅黑樹中執行插入操作,否則進行連結串列的插入操作;遍歷過程中若發現key已經存在直接覆蓋value即可;

⑥.插入成功後,判斷實際存在的鍵值對數量size是否超多了最大容量threshold,如果超過,進行擴容。

putVal原始碼:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//實現Map.put和相關方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 步驟①:tab為空則建立 
    // table未初始化或者長度為0,進行擴容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 步驟②:計算index,並對null做處理  
    // (n - 1) & hash 確定元素存放在哪個桶中,桶為空,新生成結點放入桶中(此時,這個結點是放在陣列中)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 桶中已經存在元素
    else {
        Node<K,V> e; K k;
        // 步驟③:節點key存在,直接覆蓋value 
        // 比較桶中第一個元素(陣列中的結點)的hash值相等,key相等
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
                // 將第一個元素賦值給e,用e來記錄
                e = p;
        // 步驟④:判斷該鏈為紅黑樹 
        // hash值不相等,即key不相等;為紅黑樹結點
        // 如果當前元素型別為TreeNode,表示為紅黑樹,putTreeVal返回待存放的node, e可能為null
        else if (p instanceof TreeNode)
            // 放入樹中
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // 步驟⑤:該鏈為連結串列 
        // 為連結串列結點
        else {
            // 在連結串列最末插入結點
            for (int binCount = 0; ; ++binCount) {
                // 到達連結串列的尾部
                
                //判斷該連結串列尾部指標是不是空的
                if ((e = p.next) == null) {
                    // 在尾部插入新結點
                    p.next = newNode(hash, key, value, null);
                    //判斷連結串列的長度是否達到轉化紅黑樹的臨界值,臨界值為8
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        //連結串列結構轉樹形結構
                        treeifyBin(tab, hash);
                    // 跳出迴圈
                    break;
                }
                // 判斷連結串列中結點的key值與插入的元素的key值是否相等
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // 相等,跳出迴圈
                    break;
                // 用於遍歷桶中的連結串列,與前面的e = p.next組合,可以遍歷連結串列
                p = e;
            }
        }
        //判斷當前的key已經存在的情況下,再來一個相同的hash值、key值時,返回新來的value這個值
        if (e != null) { 
            // 記錄e的value
            V oldValue = e.value;
            // onlyIfAbsent為false或者舊值為null
            if (!onlyIfAbsent || oldValue == null)
                //用新值替換舊值
                e.value = value;
            // 訪問後回撥
            afterNodeAccess(e);
            // 返回舊值
            return oldValue;
        }
    }
    // 結構性修改
    ++modCount;
    // 步驟⑥:超過最大容量就擴容 
    // 實際大小大於閾值則擴容
    if (++size > threshold)
        resize();
    // 插入後回撥
    afterNodeInsertion(evict);
    return null;
}

HashMap的擴容操作是怎麼實現的?(難點!!)

HashMap是怎麼解決雜湊衝突的

雜湊衝突:當兩個不同的輸入值,根據同一雜湊函式計算出相同的雜湊值的現象
如何解決:

  • 使用2次擾動函式(hash函式)來降低雜湊衝突的概率,使得資料分佈更平均
  • 引入紅黑樹進一步降低遍歷的時間複雜度,使得遍歷更快
  • 在JDK 1.7中,更為簡潔,相比在1.7中的4次位運算,5次異或運算(9次擾動),在1.8中,只進行了1次位運算和1次異或運算(2次擾動);

8. HashMap 與HashTable 、CurrentHashMap區別、TreeMap

HashMap與HashTable(基本被淘汰)

  • 執行緒安全:HashMap 是非執行緒安全的,HashTable 是執行緒安全的,由於HashTable內部方法基本都進過synchronized修飾

  • 效率:HashMap 要比 HashTable 效率高一點

  • 對Null key 和Null value的支援:HashMap中允許其key唯一為null,允許多個value為null的鍵;HashTable不允許其鍵值為null

HashMap與CurrentHashMap

  • 對Null key 和Null value的支援:HashMap的鍵值對允許有null,但是ConCurrentHashMap都不允許。

  • 底層資料結構:ConcurrentHashMap對整個桶陣列進行了分割分段(Segment),然後在每一個分段上都用lock鎖進行保護,相對於HashTable的synchronized鎖的粒度更精細了一些,併發效能更好,而HashMap沒有鎖機制,不是執行緒安全的。(JDK1.8之後ConcurrentHashMap啟用了一種全新的方式實現,利用CAS演算法。)

  • 執行緒安全:HashMap 是非執行緒安全的,CurrentHashMap是執行緒安全的,由於CurrentHashMap在JDK1.8之前使用分段鎖,而JDK1.8之後使用Node + CAS + Synchronized來保證併發安全進行實現

HashMap 和TreeMap

  • 插入、刪除選HashMap,由於TreeMap的底層原理是一顆紅黑樹的實現

  • 搜尋的話使用TreeMap


9.CurrentHashMap原理(難點!!)


10.TreeMap 和 TreeSet 在排序時如何比較元素?

TreeSet 要求存放的物件所屬的類必須實現 Comparable 介面,該介面提供了比較元素的 compareTo()方法,當插入元素時會回撥該方法比較元素的大小。TreeMap 要求存放的鍵值對對映的鍵必須實現 Comparable 介面從而根據鍵對元素進行排序。


11.Collections 工具類中的 sort()方法如何比較元素?

第一種要求傳入的待排序容器中存放的物件比較實現 Comparable 介面以實現元素的比較;


三、Java JVM總結

四、Java 多執行緒總結

相關文章