吐血整理!2萬字Java基礎面試題(帶答案)請收好!

熬夜整理了這麼多年來的Java基礎面試題,歡迎學習收藏,手機上可以點擊這裏,效果更佳https://mp.weixin.qq.com/s/ncbEQqQdJo0UaogQSgA0bQ

1.1 Hashmap 與 concurrentHashMap (重點)

  1. hashMap 1.7、8 put過程

d669d29c.png

  1. concurrentHashMap 1.8 put過程

Screen Shot 2019-12-10 at 8.56.23 PM.png
Screen Shot 2019-12-10 at 9.17.20 PM.png@w=400

  1. 怎麼解決衝突的(鏈表或者紅黑樹)

  2. sizeCtl (concurrentHashMap 1.8 中的數據結構)等關鍵成員變量的作用:

Node:存放實際的key和value值。
sizeCtl:負數:表示進行初始化或者擴容,-1表示正在初始化,-N,表示有N-1個線程正在進行擴容
正數:0 表示還沒有被初始化,>0的數,初始化或者是下一次進行擴容的閾值。
TreeNode:用在紅黑樹,表示樹的節點, TreeBin是實際放在table數組中的,代表了這個紅黑樹的根。

  1. concurrentHashmap 1.8爲什麼放棄了分段鎖 (鎖的粒度更小,減小併發衝突概率)

  2. HashMap的時間複雜度?

HashMap容器O(1)的查找時間複雜度只是其理想的狀態,而這種理想狀態需要由java設計者去保證。

jdk1.7中的hashMap在最壞情況下,退化成鏈表後,get/put時間複雜度均爲O(n);jdk1.8中,採用紅黑樹,複雜度可以到O(logN);如果hash函數設計的較好,元素均勻分佈,可以達到理想的O(1)複雜度。

  1. Java8中的HashMap有什麼變化?

1). 數據結構不同:jdk7 數組+單鏈表; jdk8 數組+(單鏈表+紅黑樹)

JDK7 在hashcode特別差的情況下,比方說所有key的hashcode都相同,這個鏈表可能會很長,那麼put/get操作都可能需要遍歷這個鏈表。也就是說時間複雜度在最差情況下會退化到O(n)。

JDK8 如果同一個格子裏的key不超過8個,使用鏈表結構存儲。如果超過了8個,那麼會調用treeifyBin函數,將鏈表轉換爲紅黑樹。那麼即使hashcode完全相同,由於紅黑樹的特點,查找某個特定元素,也只需要O(log n)的開銷。也就是說put/get的操作的時間複雜度最差只有O(log n)。

2). 鏈表中元素位置不同:jdk7頭插法;jdk8 鏈表尾插。

頭插: 最近put的可能等下就被get,頭插遍歷到鏈表頭就匹配到了,併發resize可能產生循環鏈
尾插:保證了元素的順序,併發resize過程中可能發生數據丟失的情況。

3). 擴容的處理不同:jdk7中使用hash和newCapacity計算元素在新數組中的位置;jdk8中利用新增的高位是否爲1,來確定新元素的位置,因此元素要麼在原位置,要麼在原位置+擴容的大小值。

jkd7中,擴容時,直接判斷每個元素在新數組中的位置,然後依次複製到新數組;
jdk8中,擴容時,首先建立兩個鏈表high和low,然後根據新增的高位是否爲0,將元素放到對應的鏈表後面。最後將對應的鏈表放在原位置或者原位置+擴容大小值的位置.

  1. 紅黑樹需要比較大小才能進行插入,是依據什麼進行比較的?

從下圖可以看到,是根據hash大小來確定左右子樹的位置的。

        final TreeNode<K,V> putTreeVal(int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                if (p == null) {
                    first = root = new TreeNode<K,V>(h, k, v, null, null);
                    break;
                }
                else if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                    return p;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) {
                    if (!searched) {
                        TreeNode<K,V> q, ch;
                        searched = true;
                        if (((ch = p.left) != null &&
                             (q = ch.findTreeNode(h, k, kc)) != null) ||
                            ((ch = p.right) != null &&
                             (q = ch.findTreeNode(h, k, kc)) != null))
                            return q;
                    }
                    dir = tieBreakOrder(k, pk);
                }

                TreeNode<K,V> xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    TreeNode<K,V> x, f = first;
                    first = x = new TreeNode<K,V>(h, k, v, f, xp);
                    if (f != null)
                        f.prev = x;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    if (!xp.red)
                        x.red = true;
                    else {
                        lockRoot();
                        try {
                            root = balanceInsertion(root, x);
                        } finally {
                            unlockRoot();
                        }
                    }
                    break;
                }
            }
            assert checkInvariants(root);
            return null;
        }
  1. 其他Hash衝突解決方式?

開放定址法(線性探測法,平方探測法,雙散列)和再散列(選擇新的散列函數,在另外一個大約兩倍大的表,而且使用一個相關的新散列函數,掃描整個原始散列表,計算每個(未刪除的)元素的新散列值並將其插入到新表中。)

  1. HashMap爲什麼不是線程安全的?怎麼讓HashMap變得線程安全?(加鎖)

1.7 hashmap 併發resize成環;1.8併發resize丟失數據。

  1. jdk1.8對ConcurrentHashMap做了哪些優化?

1、取消了segment數組,引入了Node結構,直接用Node數組來保存數據,鎖的粒度更小,減少併發衝突的概率。
2、存儲數據時採用了鏈表+紅黑樹的形式,純鏈表的形式時間複雜度爲O(n),紅黑樹則爲O(logn),性能提升很大。什麼時候鏈表轉紅黑樹?當key值相等的元素形成的鏈表中元素個數超過8個的時候。

  1. 怎麼高效率的實現數據遷移?

jdk1.8中,resize數據要麼在原位置,要麼在原位置加上resize大小的位置。
concurrentHashMap在put或者remove操作時,發現正在進行擴容,會首先幫助擴容。

  1. resize過程

1.7 hashmap:新建new table,根據hash值計算在新table中的位置,依次移動到新table
1.8 hashmap:新建table,從舊table中複製元素,利用high和low來保存不同位置的元素。

  1. 爲什麼都是2的N次冪的大小。

1) 從上面的分析JDK8 resize的過程可以可能到,數組長度保持2的次冪,當resize的時候,爲了通過h&(length-1)計算新的元素位置,可以看到當擴容後只有一位差異,也就是多出了最左位的1,這樣計算 h&(length-1)的時候,只要h對應的最左邊的那一個差異位爲0,就能保證得到的新的數組索引和老數組索引一致,否則index+OldCap。

1024555-20161115215812138-679881037.png

2) 數組長度保持2的次冪,length-1的低位都爲1,會使得獲得的數組索引index更加均勻。hash函數採用各種位運算也是爲了使得低位更加散列,如果低位全部爲1,那麼對於h低位部分來說,任何一位的變化都會對結果產生影響,可以儘可能的使元素分佈比較均勻。

1024555-20161116001404732-625340289.png

  1. HashMap,HashTable比較
  • HashMap允許將 null 作爲一個 entry 的 key 或者 value,而 Hashtable 不允許。
  • HashTable 繼承自 Dictionary 類,而 HashMap 是 Java1.2 引進的 Map interface 的一個實現。
  • HashTable 的方法是 Synchronized 的,而 HashMap 不是,在多個線程訪問 Hashtable 時,不需要自己爲它的方法實現同步,而 HashMap 就必須爲之提供外同步。
  1. 極高併發下HashTable和ConcurrentHashMap哪個性能更好,爲什麼,如何實現的。

ConcurrentHashMap。後者鎖粒度更低,前者直接對put、get方法加鎖。

  1. HashMap和HashSet的實現原理

HashSet的實現很簡單,內部有一個HashMap的成員變量,所有的Set相關的操作都轉換爲了對HashMap的操作。

1.2 集合相關問題

  1. LinkedHashMap、ArrayList、LinkedList、Vector的底層實現。

LinkedHashMap:

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
{
    ...
}

可以看到,LinkedHashMap是HashMap的子類,但和HashMap的無序性不一樣,LinkedHashMap通過維護一個運行於所有條目的雙向鏈表,保證了元素迭代的順序。該迭代順序可以是插入順序或者是訪問順序,這個可以在初始化的時候確定,默認採用插入順序來維持取出鍵值對的次序。

在成員變量上,與HashMap不同的是,引入了before和after兩個變量來記錄前後的元素。

test.png

在構造函數中,需要指定accessOrder,有兩種情況:

false,所有的Entry按照插入的順序排列
true,所有的Entry按照訪問的順序排列

ArrayList、LinkedList、Vector、stack的底層實現:

從圖中我們可以看出:

  1. List是一個接口,它繼承與Collection接口,代表有序的隊列。

  2. AbstractList是一個抽象類,它繼承與AbstractCollection。AbstractList實現了List接口中除了size()、get(int location)之外的方法。

  3. AbstractSequentialList是一個抽象類,它繼承與AbstrctList。AbstractSequentialList實現了“鏈表中,根據index索引值操作鏈表的全部方法”。

  4. ArrayList、LinkedList、Vector和Stack是List的四個實現類,其中Vector是基於JDK1.0,雖然實現了同步(大部分方法),但是效率低,已經不用了,Stack繼承於Vector。

  5. LinkedList是個雙向鏈表,它同樣可以被當作棧、隊列或雙端隊列來使用。

  1. TreeMap以及查詢複雜度

TreeMap繼承於AbstractMap,實現了Map, Cloneable, NavigableMap, Serializable接口。

Screen Shot 2019-12-08 at 3.56.55 PM.png@w=400

TreeMap 是一個有序的key-value集合,它是通過紅黑樹實現的。該映射根據其鍵的自然順序進行排序,或者根據創建映射時提供的Comparator進行排序,具體取決於使用的構造方法。
TreeMap的基本操作 containsKey、get、put 和 remove 的時間複雜度是 log(n) 。

對於SortedMap來說,該類是TreeMap體系中的父接口,也是區別於HashMap體系最關鍵的一個接口。SortedMap接口中定義的第一個方法Comparator<? super K> comparator();該方法決定了TreeMap體系的走向,有了比較器,就可以對插入的元素進行排序了。

TreeMap的查找、插入、更新元素等操作,主要是對紅黑樹的節點進行相應的更新,和數據結構中類似。

1.3 Java 泛型的理解

Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。

泛型的本質是參數化類型,也就是說所操作的數據類型被指定爲一個參數。

泛型的好處:

  1. 使程序的通用性更好,支持不同的類型;
  2. 編譯器無法進行類型檢查,可以向集合中添加任意類型的對象。取值時類型轉換失敗導致程序運行失敗。泛型的好處在於提高了程序的可讀性和安全性,這也是程序設計的宗旨之一。

1.4 跳錶(ConcurrentSkipListMap)的查詢過程是怎麼樣的,查詢和插入的時間複雜度?

ConcurrentSkipListMap是一個併發安全, 基於skiplist實現有序存儲的Map。可以看成是TreeMap的併發版本。

下面的圖示使用紫色的箭頭畫出了在一個SkipList中查找key值50的過程。簡述如下:

  1. 從head出發,因爲head指向最頂層(top level)鏈表的開始節點,相當於從頂層開始查找;

  2. 移動到當前節點的右指針(right)指向的節點,直到右節點的key值大於要查找的key值時停止;

  3. 如果還有更低層次的鏈表,則移動到當前節點的下一層節點(down),如果已經處於最底層,則退出;

  4. 重複第2步 和 第3步,直到查找到key值所在的節點,或者不存在而退出查找;

查詢複雜度:O(logN)

  • 爲什麼Redis選擇使用跳錶而不是紅黑樹來實現有序集合?

Redis 中的有序集合(zset) 支持的操作:

插入一個元素
刪除一個元素
查找一個元素
有序輸出所有元素
按照範圍區間查找元素(比如查找值在 [100, 356] 之間的數據)

其中,前四個操作紅黑樹也可以完成,且時間複雜度跟跳錶是一樣的。但是,按照區間來查找數據這個操作,紅黑樹的效率沒有跳錶高。按照區間查找數據時,跳錶可以做到 O(logn) 的時間複雜度定位區間的起點,然後在原始鏈表中順序往後遍歷就可以了,非常高效。

1.5 java 字節流 字符流

Java中的流是對字節序列的抽象,我們可以想象有一個水管,只不過現在流動在水管中的不再是水,而是字節序列。和水流一樣,Java中的流也具有一個“流動的方向”,通常可以從中讀入一個字節序列的對象被稱爲輸入流;能夠向其寫入一個字節序列的對象被稱爲輸出流。

Java中的字節流(Byte)處理的最基本單位爲單個字節,它通常用來處理二進制數據。Java中最基本的兩個字節流類是InputStream和OutputStream,它們分別代表了組基本的輸入字節流和輸出字節流。InputStream類與OutputStream類均爲抽象類,我們在實際使用中通常使用Java類庫中提供的它們的一系列子類。

public abstract int read() throws IOException;

Java中的字符流(Char)處理的最基本的單元是Unicode碼元(大小2字節),它通常用來處理文本數據。所謂Unicode碼元,也就是一個Unicode代碼單元,範圍是0x0000~0xFFFF。在以上範圍內的每個數字都與一個字符相對應,Java中的String類型默認就把字符以Unicode規則編碼而後存儲在內存中。然而與存儲在內存中不同,存儲在磁盤上的數據通常有着各種各樣的編碼方式。使用不同的編碼方式,相同的字符會有不同的二進制表示。實際上字符流是這樣工作的:

  • 輸出字符流:把要寫入文件的字符序列(實際上是Unicode碼元序列)轉爲指定編碼方式下的字節序列,然後再寫入到文件中;
  • 輸入字符流:把要讀取的字節序列按指定編碼方式解碼爲相應字符序列(實際上是Unicode碼元序列從)從而可以存在內存中。

字符流與字節流的區別

  • 字節流操作的基本單元爲字節;字符流操作的基本單元爲Unicode碼元。
  • 字節流默認不使用緩衝區;字符流使用緩衝區。
  • 字節流通常用於處理二進制數據,實際上它可以處理任意類型的數據,但它不支持直接寫入或讀取Unicode碼元;字符流通常處理文本數據,它支持寫入及讀取Unicode碼元。

1.8 包裝類型和基本類型比較問題

例如,Integer類型的變量能否==int類型變量,能否作比較

可以。 但是不同包裝類型直接進行比較,不會發生自動拆箱,比較的是兩個對象是否爲同一個。

自動裝箱和拆箱實現了基本數據類型與對象數據類型之間的隱式轉換。

自動裝箱就是Java自動將原始類型值轉換成對應的對象,比如將int的變量轉換成Integer對象,這個過程叫做裝箱,反之將Integer對象轉換成int類型值,這個過程叫做拆箱。因爲這裏的裝箱和拆箱是自動進行的非人爲轉換,所以就稱作爲自動裝箱和拆箱。原始類型byte,short,char,int,long,float,double和boolean對應的封裝類爲Byte,Short,Character,Integer,Long,Float,Double,Boolean。

何時發生自動裝箱和拆箱,

  1. 賦值:
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion
  1. 方法調用:當我們在方法調用時,我們可以傳入原始數據值或者對象,同樣編譯器會幫我們進行轉換。
public static Integer show(Integer iParam){
   System.out.println("autoboxing example - method invocation i: " + iParam);
   return iParam;
}

//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer

自動裝箱的弊端,

自動裝箱有一個問題,那就是在一個循環中進行自動裝箱操作的時候,如下面的例子就會創建多餘的對象,影響程序的性能。

Integer sum = 0;
 for(int i=1000; i<5000; i++){
   sum+=i;
}

自動裝箱與比較:

下面程序的輸出結果是什麼?

public class Main {
    public static void main(String[] args) {
         
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;
         
        System.out.println(c==d);
        System.out.println(e==f);
        System.out.println(c==(a+b));
        System.out.println(c.equals(a+b));
        System.out.println(g==(a+b));
        System.out.println(g.equals(a+b));
        System.out.println(g.equals(a+h));
    }
}

在解釋具體的結果時,首先必須明白如下兩點:

  • 當"=="運算符的兩個操作數都是 包裝器類型的引用,則是比較指向的是否是同一個對象,而如果其中有一個操作數是表達式(即包含算術運算)則比較的是數值(即會觸發自動拆箱的過程)。
  • 對於包裝器類型,equals方法並不會進行類型轉換。

下面是程序的具體輸出結果:

true
false
true
true
true
false
true

注意到對於Integer和Long,Java中,會對-128到127的對象進行緩存,當創建新的對象時,如果符合這個這個範圍,並且已有存在的相同值的對象,則返回這個對象,否則創建新的Integer對象。

對於上面的結果:

cd:指向相同的緩存對象,所以返回true;
e
f:不存在緩存,是不同的對象,所以返回false;
c(a+b):比較數值,因此爲true;
c.equals(a+b):比較的對象,由於存在緩存,所以兩個對象一樣,返回true;
g
(a+b):直接比較數值,因此爲true;
g.equals(a+b):比較對象,由於equals也不會進行類型轉換,a+b爲Integer,g爲Long,因此爲false;
g.equals(a+h):和上面不一樣,a+h時,a會進行類型轉換,轉成Long,接着比較兩個對象,由於Long存在緩存,所以兩個對象一致,返回true。

關於equals和==:

  • .equals(...) will only compare what it is written to compare, no more, no less.
  • If a class does not override the equals method, then it defaults to the equals(Object o) method of the closest parent class that has overridden this method.
  • If no parent classes have provided an override, then it defaults to the method from the ultimate parent class, Object, and so you're left with the Object#equals(Object o) method. Per the Object API this is the same as ==; that is, it returns true if and only if both variables refer to the same object, if their references are one and the same. Thus you will be testing for object equality and not functional equality.
  • Always remember to override hashCode if you override equals so as not to "break the contract". As per the API, the result returned from the hashCode() method for two objects must be the same if their equals methods show that they are equivalent. The converse is not necessarily true.

1.9 爲什麼重寫equals和hashcode

以下是Effective Java的建議:

You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object.hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.

主要是考慮到在map等需要hashcode的場合,保證程序運行正常。

由於Object是所有類的基類,默認的equals函數如下,直接比較兩個對象是否爲同一個。

    public boolean equals(Object obj) {
        return (this == obj);
    }

默認的hashcode方法是一個native函數,

public native int hashCode();

It just means that the method is implemented in the native aka C/C++ parts of the JVM. This means you can't find Java source code for that method. But there is still some code somewhere within the JVM that gets invoked whenever you call hashCode() on some Object.

從Object中關於hashCode方法的描述,對於不同的Object,hashCode會返回不同的值;但是實現可能與對象的地址有關,也有可能無關,具體看JVM實現。

1.10 stringBuilder和stringBuffer的區別

String 是 Java 語言非常基礎和重要的類,提供了構造和管理字符串的各種基本邏輯。它是典型的 Immutable 類,被聲明成爲 final class,所有屬性也都是 final 的。也由於它的不可變性,類似拼接、裁剪字符串等動作,都會產生新的 String 對象。由於字符串操作的普遍性,所以相關操作的效率往往對應用性能有明顯影響。

StringBuffer 是爲解決上面提到拼接產生太多中間對象的問題而提供的一個類,我們可以用 append 或者 add 方法,把字符串添加到已有序列的末尾或者指定位置。StringBuffer 本質是一個線程安全的可修改字符序列,它保證了線程安全,也隨之帶來了額外的性能開銷,所以除非有線程安全的需要,不然還是推薦使用它的後繼者,也就是 StringBuilder。

StringBuilder 是 Java 1.5 中新增的,在能力上和 StringBuffer 沒有本質區別,但是它去掉了線程安全的部分,有效減小了開銷,是絕大部分情況下進行字符串拼接的首選。

1.11 Java序列化的原理

序列化: 對象序列化的最主要的用處就是在傳遞和保存對象的時候,保證對象的完整性和可傳遞性。序列化是把對象轉換成有序字節流,以便在網絡上傳輸或者保存在本地文件中。序列化後的字節流保存了Java對象的狀態以及相關的描述信息。序列化機制的核心作用就是對象狀態的保存與重建。

反序列化: 客戶端從文件中或網絡上獲得序列化後的對象字節流後,根據字節流中所保存的對象狀態及描述信息,通過反序列化重建對象。

序列化算法一般會按步驟做如下事情:

  1. 將對象實例相關的類元數據輸出。
  2. 遞歸地輸出類的超類描述直到不再有超類。
  3. 類元數據完了以後,開始從最頂層的超類開始輸出對象實例的實際數據值。
  4. 從上至下遞歸輸出實例的數據

JDK中序列化一個對象:

  1. 創建某些OutputStream對象
  2. 將其封裝在一個ObjectOutputStream對象內
  3. 只需調用writeObject()即可將對象序列化

反序列化
將一個InputStream封裝在ObjectInputStream內,然後調用readObject()。最後獲得的是一個引用,它指向一個向上轉型的Object,所以必須向下轉型才能直接設置它們。

序列化的控制——通過實現Externalizable接口——代替實現Serializable接口——來對序列化過程進行控制。

  1. Externalizable接口繼承了Serializable接口,增加了兩個方法,writeExternal()和readExternal(),這兩個方法會在序列化和反序列化還原的過程中被自動調用。
  2. Externalizable對象,在還原的時候所有普通的默認構造器都會被調用(包括在字段定義時的初始化)(只有這樣才能使Externalizable對象產生正確的行爲),然後調用readExternal().
  3. 如果我們從一個Externalizable對象繼承,通常需要調用基類版本的writeExternal()和readExternal()來爲基類組件提供恰當的存儲和恢復功能。
  4. 爲了正常運行,我們不僅需要在writeExternal()方法中將來自對象的重要信息寫入,還必須在readExternal()中恢復數據

防止對象的敏感部分被序列化,兩種方式:

  1. 將類實現Externalizable,在writeExternal()內部只對所需部分進行顯示的序列化
  2. 實現Serializable,用transient(瞬時)關鍵字(只能和Serializable一起使用)逐個字段的關閉序列化,他的意思:不用麻煩你保存或恢復數據——我自己會處理。

Externalizable的替代方法

  1. 實現Serializable接口,並添加名爲writeObject()和readObject()的方法,這樣一旦對象被序列化或者被反序列化還原,就會自動的分別調用writeObject()和readObject()的方法(它們不是接口的一部分,接口的所有東西都是public的)。只要提供這兩個方法,就會使用它們而不是默認的序列化機制。
  2. 這兩個方法必須具有準確的方法特徵簽名,但是這兩個方法並不在這個類中的其他方法中調用,而是在ObjectOutputStream和ObjectInputStream對象的writeObject()和readObject()方法

1.11 Java8、9、10、11的一些新特性介紹

java8

  1. lambada表達式(Lambda Expressions)。Lambda允許把函數作爲一個方法的參數(函數作爲參數傳遞進方法中)。
  2. 方法引用(Method references)。方法引用提供了非常有用的語法,可以直接引用已有Java類或對象(實例)的方法或構造器。與lambda聯合使用,可以使語言的構造更緊湊簡潔,減少冗餘代碼。
  3. 默認方法(Default methods)。默認方法允許將新功能添加到庫的接口中,並確保兼容實現老版本接口的舊有代碼。
  4. 重複註解(Repeating Annotations)。重複註解提供了在同一聲明或類型中多次應用相同註解類型的能力。
  5. 類型註解(Type Annotation)。在任何地方都能使用註解,而不是在聲明的地方。
  6. 類型推斷增強。
  7. 方法參數反射(Method Parameter Reflection)。
  8. Stream API 。新添加的Stream API(java.util.stream) 把真正的函數式編程風格引入到Java中。Stream API集成到了Collections API裏。
  9. HashMap改進,在鍵值哈希衝突時能有更好表現。
  10. Date Time API。加強對日期和時間的處理。
  11. java.util 包下的改進,提供了幾個實用的工具類。
  • 並行數組排序。
  • 標準的Base64編解碼。
  • 支持無符號運算。
  1. java.util.concurrent 包下增加了新的類和方法。
  • java.util.concurrent.ConcurrentHashMap 類添加了新的方法以支持新的StreamApi和lambada表達式。
  • java.util.concurrent.atomic 包下新增了類以支持可伸縮可更新的變量。
  • java.util.concurrent.ForkJoinPool類新增了方法以支持 common pool。
  • 新增了java.util.concurrent.locks.StampedLock類,爲控制讀/寫訪問提供了一個基於性能的鎖,且有三種模式可供選擇。
  1. HotSpot
  • 刪除了 永久代(PermGen).
  • 方法調用的字節碼指令支持默認方法。

java9

  1. java模塊系統 (Java Platform Module System)。
  2. 新的版本號格式。$MAJOR.$MINOR.$SECURITY.$PATCH
  3. java shell,交互式命令行控制檯。
  4. 在private instance methods方法上可以使用@SafeVarargs註解。
  5. diamond語法與匿名內部類結合使用。
  6. 下劃線_不能單獨作爲變量名使用。
  7. 支持私有接口方法(您可以使用diamond語法與匿名內部類結合使用)。
  8. Javadoc
  • 簡化Doclet API。
  • 支持生成HTML5格式。
  • 加入了搜索框,使用這個搜索框可以查詢程序元素、標記的單詞和文檔中的短語。
  • 支持新的模塊系統。
  1. JVM
  • 增強了Garbage-First(G1) 並用它替代Parallel GC成爲默認的垃圾收集器。
  • 統一了JVM 日誌,爲所有組件引入了同一個日誌系統。
  • 刪除了JDK 8中棄用的GC組合。(DefNew + CMS,ParNew + SerialOld,Incremental CMS)。
  1. properties文件支持UTF-8編碼,之前只支持ISO-8859-1。
  2. 支持Unicode 8.0,在JDK8中是Unicode 6.2。

java10

  1. 局部變量類型推斷(Local-Variable Type Inference)
//之前的代碼格式
URL url = new URL("http://www.oracle.com/"); 
URLConnection conn = url.openConnection(); 
Reader reader = new BufferedReader(
    new InputStreamReader(conn.getInputStream()))
//java10中用var來聲明變量
var url = new URL("http://www.oracle.com/"); 
var conn = url.openConnection(); 
var reader = new BufferedReader(
    new InputStreamReader(conn.getInputStream()));

var是一個保留類型名稱,而不是關鍵字。所以之前使用var作爲變量、方法名、包名的都沒問題,但是如果作爲類或接口名,那麼這個類和接口就必須重命名了。

var的使用場景主要有以下四種:

  • 本地變量初始化。
  • 增強for循環中。
  • 傳統for循環中聲明的索引變量。
  • Try-with-resources 變量。
  1. Optional類添加了新的方法orElseThrow(無參數版)。相比於已經存在的get方法,這個方法更推薦使用。

java11

  1. 支持Unicode 10.0,在jdk10中是8.0。
  2. 標準化HTTP Client
  3. 編譯器線程的延遲分配。添加了新的命令-XX:+UseDynamicNumberOfCompilerThreads動態控制編譯器線程的數量。
  4. 新的垃圾收集器—ZGC。一種可伸縮的低延遲垃圾收集器(實驗性)。
  5. Epsilon。一款新的實驗性無操作垃圾收集器。Epsilon GC 只負責內存分配,不實現任何內存回收機制。這對於性能測試非常有用,可用於與其他GC對比成本和收益。
  6. Lambda參數的局部變量語法。java10中引入的var字段得到了增強,現在可以用在lambda表達式的聲明中。如果lambda表達式的其中一個形式參數使用了var,那所有的參數都必須使用var。

參考:https://www.jianshu.com/p/38985b61ea83

1.12 java中四種修飾符的限制範圍。

1.13 Object類中的方法。

  1. Object():默認構造方法
  2. clone():創建並返回此對象的一個副本,這是淺拷貝。
  3. equals():指示某個其他對象是否與此對象相等
  4. finalize():當垃圾回收器確定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。JVM準備對此對對象所佔用的內存空間進行垃圾回收前,將被調用。
  5. getClass():返回一個對象的運行時類對象。

首先解釋下"類對象"的概念:在Java中,類是是對具有一組相同特徵或行爲的實例的抽象並進行描述,對象則是此類所描述的特徵或行爲的具體實例。作爲概念層次的類,其本身也具有某些共同的特性,如都具有類名稱、由類加載器去加載,都具有包,具有父類,屬性和方法等。於是,Java中有專門定義了一個類,Class,去描述其他類所具有的這些特性,因此,從此角度去看,類本身也都是屬於Class類的對象。爲與經常意義上的對象相區分,在此稱之爲"類對象"。

  1. hashCode():返回該對象的哈希值
  2. notify():喚醒此對象監視器上等待的單個線程
  3. notifyAll():喚醒此對象監視器上等待的所有線程
  4. toString():返回該對象的字符串表示
  5. wait():導致當前的線程等待,直到其它線程調用此對象的notify()或notifyAll()
  6. wait(long timeout):導致當前的線程等待調用此對象的notify()或notifyAll()
  7. wait(long timeout, int nanos):導致當前的線程等待,直到其他線程調用此對象的notify()或notifyAll(),或其他某個線程中斷當前線程,或者已超過某個實際時間量。
  8. registerNatives():對本地方法進行註冊

1.14 淺拷貝 深拷貝

在 Java 中,除了基本數據類型(元類型)之外,還存在 類的實例對象 這個引用數據類型。而一般使用 『 = 』號做賦值操作的時候。對於基本數據類型,實際上是拷貝的它的值,但是對於對象而言,其實賦值的只是這個對象的引用,將原對象的引用傳遞過去,他們實際上還是指向的同一個對象。

而淺拷貝和深拷貝就是在這個基礎之上做的區分,如果在拷貝這個對象的時候,只對基本數據類型進行了拷貝,而對引用數據類型只是進行了引用的傳遞,而沒有真實的創建一個新的對象,則認爲是淺拷貝。反之,在對引用數據類型進行拷貝的時候,創建了一個新的對象,並且複製其內的成員變量,則認爲是深拷貝。

1、淺拷貝:對基本數據類型進行值傳遞,對引用數據類型進行引用傳遞般的拷貝,此爲淺拷貝。(默認的clone函數)

2、深拷貝:對基本數據類型進行值傳遞,對引用數據類型,創建一個新的對象,並複製其內容,此爲深拷貝。(序列化對象或者重寫clone函數)

1.15 接口和抽象類的區別,注意JDK8的接口可以有實現。

接口和抽象類是 Java 面向對象設計的兩個基礎機制。

接口是對行爲的抽象,它是抽象方法的集合,利用接口可以達到 API 定義和實現分離的目的。接口,不能實例化;不能包含任何非常量成員,任何 field 都是隱含着 public static final 的意義 Java 標準類庫中,定義了非常多的接口,比如 java.util.List。

Java 8 以後,接口也是可以有方法實現的。 從 Java 8 開始,interface 增加了對 default method 的支持。Java 9 以後,甚至可以定義 private default method。Default method 提供了一種二進制兼容的擴展已有接口的辦法。在 Java 8 中添加了一系列 default method,主要是增加 Lambda(forEach)、Stream 相關的功能。

抽象類是不能實例化的類,用 abstract 關鍵字修飾 class,其目的主要是代碼重用。除了不能實例化,形式上和一般的 Java 類並沒有太大區別,可以有一個或者多個抽象方法,也可以沒有抽象方法。抽象類大多用於抽取相關 Java 類的共用方法實現或者是共同成員變量,然後通過繼承的方式達到代碼複用的目的。 Java 標準庫中,比如 collection 框架,很多通用部分就被抽取成爲抽象類,例如 java.util.AbstractList。

Java 類實現 interface 使用 implements 關鍵詞,繼承 abstract class 則是使用 extends 關鍵詞,可以參考 Java 標準庫中的 ArrayList。

1.16 動態代理的兩種方式,以及區別。

1. JDK動態代理

1、因爲利用JDKProxy生成的代理類實現了接口,所以目標類中所有的方法在代理類中都有。
2、生成的代理類的所有的方法都攔截了目標類的所有的方法。而攔截器中invoke方法的內容正好就是代理類的各個方法的組成體。
3、利用JDKProxy方式必須有接口的存在。
4、invoke方法中的三個參數可以訪問目標類的被調用方法的API、被調用方法的參數、被調用方法的返回類型。

2. cglib動態代理

1、 CGlib是一個強大的,高性能,高質量的Code生成類庫。它可以在運行期擴展Java類與實現Java接口。
2、 用CGlib生成代理類是目標類的子類。
3、 用CGlib生成 代理類不需要接口
4、 用CGLib生成的代理類重寫了父類的各個方法。
5、 攔截器中的intercept方法內容正好就是代理類中的方法體

JDK動態代理和cglib動態代理有什麼區別?

  • JDK動態代理只能對實現了接口的類生成代理對象;
  • cglib可以對任意類生成代理對象,它的原理是對目標對象進行繼承代理,如果目標對象被final修飾,那麼該類無法被cglib代理。

Spring框架的一大特點就是AOP,SpringAOP的本質就是動態代理,那麼Spring到底使用的是JDK代理,還是cglib代理呢?

答:混合使用。如果被代理對象實現了接口,就優先使用JDK代理,如果沒有實現接口,就用用cglib代理。

1.16 傳值和傳引用的區別,Java是怎麼樣的,有沒有傳值引用。

在Java程序中,不區分傳值調用和傳引用,總是採用傳值調用

注意以下幾種情況:

  • 一個方法不能修改一個基本數據類型的參數(即數值型和布爾型);
  • 一個方法可以改變一個對象參數的狀態;
  • 一個方法不能讓對象參數引用一個新的對象。

值傳遞與引用傳遞的區別:一個方法可以修改傳遞引用所對應的變量值,而不能修改傳遞值調用所對應的變量值,這句話相當重要,這是按值調用與引用調用的根本區別。

就算是包裝類型也不行,修改之後生成新的變量,改變了形參的值,但是實參的值不會改變。

由於String類和包裝類都被設定成不可變的,沒有提供value對應的setter方法,而且很多都是final的,我們無法改變其內容,所以導致我們看起來好像是值傳遞。

在Java下實現swap函數可以通過反射實現,或者使用數組。

1.17 一個ArrayList在循環過程中刪除,會不會出問題,爲什麼。

會有問題,不過需要分情況討論。

所有可能的刪除方法如下,

public class ArrayListTest {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("aa");
        list.add("bb");
        list.add("bb");
        list.add("aa");
        list.add("cc");
        // 刪除元素 bb
        remove(list, "bb");
        for (String str : list) {
            System.out.println(str);
        }
    }
    public static void remove(ArrayList<String> list, String elem) {
        // 五種不同的循環及刪除方法
        // 方法一:普通for循環正序刪除,刪除過程中元素向左移動,不能刪除重複的元素
//        for (int i = 0; i < list.size(); i++) {
//            if (list.get(i).equals(elem)) {
//                list.remove(list.get(i));
//            }
//        }
        // 方法二:普通for循環倒序刪除,刪除過程中元素向左移動,可以刪除重複的元素
//        for (int i = list.size() - 1; i >= 0; i--) {
//            if (list.get(i).equals(elem)) {
//                list.remove(list.get(i));
//            }
//        }
        // 方法三:增強for循環刪除,使用ArrayList的remove()方法刪除,產生併發修改異常 ConcurrentModificationException
//        for (String str : list) {
//            if (str.equals(elem)) {
//                list.remove(str);
//            }
//        }
        // 方法四:迭代器,使用ArrayList的remove()方法刪除,產生併發修改異常 ConcurrentModificationException
//        Iterator iterator = list.iterator();
//        while (iterator.hasNext()) {
//            if(iterator.next().equals(elem)) {
//                list.remove(iterator.next());
//            }
//        }

        // 方法五:迭代器,使用迭代器的remove()方法刪除,可以刪除重複的元素,但不推薦
//        Iterator iterator = list.iterator();
//        while (iterator.hasNext()) {
//            if(iterator.next().equals(elem)) {
//                iterator.remove();
//            }
//        }
    }
}

針對上述結果總結如下:

  1. 普通for循環刪除,無論正向或者反向,不會拋出異常。但是由於刪除過程中,list整體左移,所以正向刪除無法刪除連續的重複元素。
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
  1. 使用增強的for循環或者迭代器,只要是調用list本身的remove函數,由於在remove中會修改list內部的modCount,導致expectedModCount!=modCount,當調用迭代器的next函數時,首先會檢查兩個計數是否相等,由於不相等,因此發生異常。
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
        
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
  1. 如果使用迭代器並調用迭代器的remove方法來刪除元素,由於迭代器的remove函數中對兩個計數進行了同步,所以不會出現異常。
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

1.18 Exception和Error區別

Exception 和 Error 都是繼承了 Throwable 類,在 Java 中只有 Throwable 類型的實例纔可以被拋出(throw)或者捕獲(catch),它是異常處理機制的基本組成類型。Exception 和 Error 體現了 Java 平臺設計者對不同異常情況的分類。

Exception 是程序正常運行中,可以預料的意外情況,可能並且應該被捕獲,進行相應處理。Exception 又分爲可檢查(checked)異常和不檢查(unchecked)異常,可檢查異常在源代碼裏必須顯式地進行捕獲處理,這是編譯期檢查的一部分。不檢查異常就是所謂的運行時異常,類似 NullPointerException、ArrayIndexOutOfBoundsException 之類,通常是可以編碼避免的邏輯錯誤,具體根據需要來判斷是否需要捕獲,並不會在編譯期強制要求。

Error 是指在正常情況下,不大可能出現的情況,絕大部分的 Error 都會導致程序(比如 JVM 自身)處於非正常的、不可恢復狀態。既然是非正常情況,所以不便於也不需要捕獲,常見的比如 OutOfMemoryError 之類,都是 Error 的子類。

1.19 new關鍵字和newinstance()方法

它們的區別在於創建對象的方式不一樣,前者newInstance()是使用類加載機制,後者new關鍵字是創建一個新類。那麼爲什麼會有兩種創建對象方式?這主要考慮到軟件的可伸縮、可擴展和可重用等軟件設計思想。

1、類的加載方式不同

在執行Class.forName("a.class.Name")時,JVM會在classapth中去找對應的類並加載,這時JVM會執行該類的靜態代碼段。在使用newInstance()方法的時候,必須保證這個類已經加載並且已經鏈接了,而這可以通過Class的靜態方法forName()來完成的。

使用關鍵字new創建一個類的時候,這個類可以沒有被加載,一般也不需要該類在classpath中設定,但可能需要通過classlaoder來加載。

2、所調用的構造方法不盡相同

new關鍵字能調用任何構造方法。
newInstance()只能調用無參構造方法。

3、執行效率不同

new關鍵字是強類型的,效率相對較高。
newInstance()是弱類型的,效率相對較低。

既然使用newInstance()構造對象的地方通過new關鍵字也可以創建對象,爲什麼又會使用newInstance()來創建對象呢?

假設定義了一個接口Door,開始的時候是用木門的,定義爲一個類WoodenDoor,在程序裏就要這樣寫 Door door = new WoodenDoor() 。假設後來生活條件提高,換爲自動門了,定義一個類AutoDoor,這時程序就要改寫爲 Door door = new AutoDoor() 。雖然只是改個標識符,如果這樣的語句特別多,改動還是挺大的。於是出現了工廠模式,所有Door的實例都由DoorFactory提供,這時換一種門的時候,只需要把工廠的生產模式改一下,還是要改一點代碼。

而如果使用newInstance(),則可以在不改變代碼的情況下,換爲另外一種Door。具體方法是把Door的具體實現類的類名放到配置文件中,通過newInstance()生成實例。這樣,改變另外一種Door的時候,只改配置文件就可以了。示例代碼如下:
String className = 從配置文件讀取Door的具體實現類的類名;
Door door = (Door) Class.forName(className).newInstance();

再配合依賴注入的方法,就提高了軟件的可伸縮性、可擴展性。

https://blog.csdn.net/luckykapok918/article/details/50186797

1.20 Map、List、Set 分別說下你知道的線程安全類和線程不安全的類

MAP:

不安全:hashmap、treeMap、LinkedHashMap
安全:concurrentHashMap、ConcurrentSkipListMap、或者說hashtable

List:

不安全:ArrayList、linkedlist
安全:Vector、SynchronizedList(將list轉爲線程安全,全部上鎖)、CopyOnWriteArrayList(讀讀不上鎖、寫上鎖ReentrantLock、寫完直接替換)

Set:

不安全:hashset、treeSet、LinkedHashSet
安全:ConcurrentSkipListSet、CopyOnWriteArraySet、synchronizedSet

1.21 Java防止SQL注入

  1. PreparedStatement:採用預編譯語句集,它內置了處理SQL注入的能力,只要使用它的setXXX方法傳值即可。PreparedStatement已經準備好了,執行階段只是把輸入串作爲數據處理,
    而不再對sql語句進行解析,準備,因此也就避免了sql注入問題。
  2. 使用正則表達式過濾傳入的參數
  3. 字符串過濾

1.22 反射原理及使用場景

反射 (Reflection) 是 Java 的特徵之一,它允許運行中的 Java 程序獲取自身的信息,並且可以操作類或對象的內部屬性。

簡而言之,通過反射,我們可以在運行時獲得程序或程序集中每一個類型的成員和成員的信息。程序中一般的對象的類型都是在編譯期就確定下來的,而 Java 反射機制可以動態地創建對象並調用其屬性,這樣的對象的類型在編譯期是未知的。所以我們可以通過反射機制直接創建對象,即使這個對象的類型在編譯期是未知的。

反射的核心是 JVM 在運行時才動態加載類或調用方法/訪問屬性,它不需要事先(寫代碼的時候或編譯期)知道運行對象是誰。

Java 反射主要提供以下功能:

  • 在運行時判斷任意一個對象所屬的類;
  • 在運行時構造任意一個類的對象;
  • 在運行時判斷任意一個類所具有的成員變量和方法(通過反射甚至可以調用private方法);
  • 在運行時調用任意一個對象的方法。

重點:是運行時而不是編譯時。

1 主要用途

反射最重要的用途就是開發各種通用框架。 很多框架(比如 Spring)都是配置化的(比如通過 XML 文件配置 Bean),爲了保證框架的通用性,它們可能需要根據配置文件加載不同的對象或類,調用不同的方法,這個時候就必須用到反射,運行時動態加載需要加載的對象。

2 基本使用

1. 獲得Class對象

方法有三種:

(1) 使用 Class 類的 forName 靜態方法:

public static Class<?> forName(String className)

比如在 JDBC 開發中常用此方法加載數據庫驅動,Class.forName(driver);

(2) 直接獲取某一個對象的 class,比如:

Class<?> klass = int.class;
Class<?> classInt = Integer.TYPE;

(3) 調用某個對象的 getClass() 方法,比如:

StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();

2. 判斷是否爲某個類的實例

一般地,我們用 instanceof 關鍵字來判斷是否爲某個類的實例。同時我們也可以藉助反射中 Class 對象的 isInstance() 方法來判斷是否爲某個類的實例,它是一個 native 方法:

public native boolean isInstance(Object obj);

3. 創建實例

通過反射來生成對象主要有兩種方式。

  • 使用Class對象的newInstance()方法來創建Class對象對應類的實例。
Class<?> c = String.class;
Object str = c.newInstance();
  • 先通過Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建實例。這種方法可以用指定的構造器構造類的實例。
//獲取String所對應的Class對象
Class<?> c = String.class;
//獲取String類帶一個String參數的構造器
Constructor constructor = c.getConstructor(String.class);
//根據構造器創建實例
Object obj = constructor.newInstance("23333");
System.out.println(obj);

4. 獲取方法

獲取某個Class對象的方法集合,主要有以下幾個方法:

  • getDeclaredMethods 方法返回類或接口聲明的所有方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法。
public Method[] getDeclaredMethods() throws SecurityException
  • getMethods 方法返回某個類的所有公用(public)方法,包括其繼承類的公用方法。
public Method[] getMethods() throws SecurityException
  • getMethod 方法返回一個特定的方法,其中第一個參數爲方法名稱,後面的參數爲方法的參數對應Class的對象。
public Method getMethod(String name, Class<?>... parameterTypes)

5. 獲取構造器信息

獲取類構造器的用法與上述獲取方法的用法類似。主要是通過Class類的getConstructor方法得到Constructor類的一個實例,而Constructor類有一個newInstance方法可以創建一個對象實例:

public T newInstance(Object ... initargs)

此方法可以根據傳入的參數來調用對應的Constructor創建對象實例。

6、獲取類的成員變量(字段)信息

主要是這幾個方法,在此不再贅述:

  • getFiled:訪問公有的成員變量
  • getDeclaredField:所有已聲明的成員變量,但不能得到其父類的成員變量

注:可以通過method.setAccessible(true)和field.setAccessible(true)來關閉安全檢查來提升反射速度。

7. 調用方法

當我們從類中獲取了一個方法後,我們就可以用 invoke() 方法來調用這個方法。invoke 方法的原型爲:

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException

下面是例子:

public class test1 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<?> klass = methodClass.class;
        //創建methodClass的實例
        Object obj = klass.newInstance();
        //獲取methodClass類的add方法
        Method method = klass.getMethod("add",int.class,int.class);
        //調用method對應的方法 => add(1,4)
        Object result = method.invoke(obj,1,4);
        System.out.println(result);
    }
}
class methodClass {
    public final int fuck = 3;
    public int add(int a,int b) {
        return a+b;
    }
    public int sub(int a,int b) {
        return a+b;
    }
}

8. 利用反射創建數組

數組在Java裏是比較特殊的一種類型,它可以賦值給一個Object Reference。下面我們看一看利用反射創建數組的例子:

public static void testArray() throws ClassNotFoundException {
        Class<?> cls = Class.forName("java.lang.String");
        Object array = Array.newInstance(cls,25);
        //往數組裏添加內容
        Array.set(array,0,"hello");
        Array.set(array,1,"Java");
        Array.set(array,2,"fuck");
        Array.set(array,3,"Scala");
        Array.set(array,4,"Clojure");
        //獲取某一項的內容
        System.out.println(Array.get(array,3));
    }

其中的Array類爲java.lang.reflect.Array類。我們通過Array.newInstance()創建數組對象,它的原型是:

public static Object newInstance(Class<?> componentType, int length)
        throws NegativeArraySizeException {
        return newArray(componentType, length);
    }

3 注意事項

由於反射會額外消耗一定的系統資源,因此如果不需要動態地創建一個對象,那麼就不需要用反射。

另外,反射調用方法時可以忽略權限檢查,因此可能會破壞封裝性而導致安全問題。

1.23 static Vs Final ? 如何讓類不能被繼承

Static :被static修飾的成員變量屬於類,不屬於這個類的某個對象。

final意味着”不可改變的”,一般應用於數據、方法和類。

  • final數據:當數據是基本類型時,意味着這是一個永不改變的編譯時常量。

  • final方法:一般我們使用final方法的目的就是防止子類對該類方法的覆蓋或修改。

  • final類:一般我們使用final類的目的就是說明我們不打算用任何類繼承該類,即不希望該類有子類。

如何讓類不被繼承:用final修飾這個類,或者將構造函數聲明爲私有。

1.24 內存泄露?內存溢出?

內存溢出:是指程序在申請內存時,沒有足夠的內存空間供其使用,出現OutOfMemoryError。

產生該錯誤的原因主要包括:

  • JVM內存過小。
  • 程序不嚴密,產生了過多的垃圾。

內存泄露:Memory Leak,是指程序在申請內存後,無法釋放已申請的內存空間,一次內存泄露危害可以忽略,但內存泄露堆積後果很嚴重,無論多少內存,遲早會被佔光。

在Java中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特點:
1)首先,這些對象是可達的,即在有向圖中,存在通路可以與其相連;
2)其次,這些對象是無用的,即程序以後不會再使用這些對象。

兩者的聯繫:

內存泄露會最終會導致內存溢出。

相同點:都會導致應用程序運行出現問題,性能下降或掛起。
不同點:

  1. 內存泄露是導致內存溢出的原因之一,內存泄露積累起來將導致內存溢出。
  2. 內存泄露可以通過完善代碼來避免,內存溢出可以通過調整配置來減少發生頻率,但無法徹底避免。

1.25 重寫Vs重載

  • 重寫是子類對父類的允許訪問的方法的實現過程進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!

  • 重載(overloading) 是在一個類裏面,方法名字相同,而參數不同。返回類型可以相同也可以不同。每個重載的方法(或者構造函數)都必須有一個獨一無二的參數類型列表。

在 Java 中重載是由靜態類型確定的,在類加載的時候即可確定,屬於靜態分派;而重寫是由動態類型確定,是在運行時確定的,屬於動態分派,動態分派是由虛方法表實現的,虛方法表中存在着各個方法的實際入口地址,如若父類中某個方法子類沒有重寫,則父類與子類的方法表中的方法地址相同,如若重寫了,則子類方法表的地址指向重寫後的地址。

1.26 Lambda表達式實現

這樣就完成的實現了Lambda表達式,使用invokedynamic指令,運行時調用LambdaMetafactory.metafactory動態的生成內部類,實現了接口,內部類裏的調用方法塊並不是動態生成的,只是在原class裏已經編譯生成了一個靜態的方法,內部類只需要調用該靜態方法。

1.27 ClassNotFoundException和NoClassDefFoundError的區別

  1. ClassNotFoundException是一個檢查異常。從類繼承層次上來看,ClassNotFoundException是從Exception繼承的,所以ClassNotFoundException是一個檢查異常。

當應用程序運行的過程中嘗試使用類加載器去加載Class文件的時候,如果沒有在classpath中查找到指定的類,就會拋出ClassNotFoundException。一般情況下,當我們使用Class.forName()或者ClassLoader.loadClass以及使用ClassLoader.findSystemClass()在運行時加載類的時候,如果類沒有被找到,那麼就會導致JVM拋出ClassNotFoundException。

  1. NoClassDefFoundError異常,看命名後綴是一個Error。從類繼承層次上看,NoClassDefFoundError是從Error繼承的。和ClassNotFoundException相比,明顯的一個區別是,NoClassDefFoundError並不需要應用程序去關心catch的問題。

當JVM在加載一個類的時候,如果這個類在編譯時是可用的,但是在運行時找不到這個類的定義的時候,JVM就會拋出一個NoClassDefFoundError錯誤。 比如當我們在new一個類的實例的時候,如果在運行是類找不到,則會拋出一個NoClassDefFoundError的錯誤。

歡迎關注【後端精進之路】,硬貨文章一手掌握~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章