java面試題及答案2021,java最新面試題及答案2021
java面試題及答案2021,java2021最新面試題及答案
發現網上很多Java面試題都沒有答案,所以花了很長時間蒐集整理出來了這套Java面試題大全,希望對大家有幫助哈~
本套Java面試題大全,全的不能再全,哈哈~
一、Java 基礎
1. JDK 和 JRE 有什麼區別?
- JDK:Java Development Kit 的簡稱,java 開發工具包,提供了 java 的開發環境和執行環境。
- JRE:Java Runtime Environment 的簡稱,java 執行環境,為 java 的執行提供了所需環境。
具體來說 JDK 其實包含了 JRE,同時還包含了編譯 java 原始碼的編譯器 javac,還包含了很多 java 程式除錯和分析的工具。簡單來說:如果你需要執行 java 程式,只需安裝 JRE 就可以了,如果你需要編寫 java 程式,需要安裝 JDK。
2. == 和 equals 的區別是什麼?
== 解讀
對於基本型別和引用型別 == 的作用效果是不同的,如下所示:
- 基本型別:比較的是值是否相同;
- 引用型別:比較的是引用是否相同;
程式碼示例:
- String x = "string";
- String y = "string";
- String z = new String("string"
); - System.out.println(x==y); // true
- System.out.println(x==z); // false
- System.out.println(x.equals(y)); // true
- System.out.println(x.equals(z)); // true
程式碼解讀:因為 x 和 y 指向的是同一個引用,所以 == 也是 true,而 new String()方法則重寫開闢了記憶體空間,所以 == 結果為 false,而 equals 比較的一直是值,所以結果都為 true。
equals 解讀
equals 本質上就是 ==,只不過 String 和 Integer 等重寫了 equals 方法,把它變成了值比較。看下面的程式碼就明白了。
首先來看預設情況下 equals 比較一個有相同值的物件,程式碼如下:
- class Cat {
- public Cat(String name) {
- this.name = name;
- }
-
- private String name;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
- }
-
- Cat c1 = new Cat("王磊");
- Cat c2 = new Cat("王磊");
- System.out.println(c1.equals(c2)); // false
輸出結果出乎我們的意料,竟然是 false?這是怎麼回事,看了 equals 原始碼就知道了,原始碼如下:
- public boolean equals(Object obj) {
- return (this == obj);
- }
原來 equals 本質上就是 ==。
那問題來了,兩個相同值的 String 物件,為什麼返回的是 true?程式碼如下:
- String s1 = new String("老王");
- String s2 = new String("老王");
- System.out.println(s1.equals(s2)); // true
同樣的,當我們進入 String 的 equals 方法,找到了答案,程式碼如下:
- public boolean equals(Object anObject) {
- if (this == anObject) {
- return true;
- }
- if (anObject instanceof String) {
- String anotherString = (String)anObject;
- int n = value.length;
- if (n == anotherString.value.length) {
- char v1[] = value;
- char v2[] = anotherString.value;
- int i = 0;
- while (n-- != 0) {
- if (v1[i] != v2[i])
- return false;
- i++;
- }
- return true;
- }
- }
- return false;
- }
原來是 String 重寫了 Object 的 equals 方法,把引用比較改成了值比較。
總結:== 對於基本型別來說是值比較,對於引用型別來說是比較的是引用;而 equals 預設情況下是引用比較,只是很多類重新了 equals 方法,比如 String、Integer 等把它變成了值比較,所以一般情況下 equals 比較的是值是否相等。
3. 兩個物件的 hashCode()相同,則 equals()也一定為 true,對嗎?
不對,兩個物件的 hashCode()相同,equals()不一定 true。
程式碼示例:
- String str1 = "通話";
- String str2 = "重地";
- System.out.println(String.format("str1:%d | str2:%d", str1.hashCode(),str2.hashCode()));
- System.out.println(str1.equals(str2));
執行的結果:
str1:1179395 | str2:1179395
false
程式碼解讀:很顯然“通話”和“重地”的 hashCode() 相同,然而 equals() 則為 false,因為在散列表中,hashCode()相等即兩個鍵值對的雜湊值相等,然而雜湊值相等,並不一定能得出鍵值對相等。
4. final 在 java 中有什麼作用?
- final 修飾的類叫最終類,該類不能被繼承。
- final 修飾的方法不能被重寫。
- final 修飾的變數叫常量,常量必須初始化,初始化之後值就不能被修改。
5. java 中的 Math.round(-1.5) 等於多少?
等於 -1,因為在數軸上取值時,中間值(0.5)向右取整,所以正 0.5 是往上取整,負 0.5 是直接捨棄。
6. String 屬於基礎的資料型別嗎?
String 不屬於基礎型別,基礎型別有 8 種:byte、boolean、char、short、int、float、long、double,而 String 屬於物件。
7. java 中操作字串都有哪些類?它們之間有什麼區別?
操作字串的類有:String、StringBuffer、StringBuilder。
String 和 StringBuffer、StringBuilder 的區別在於 String 宣告的是不可變的物件,每次操作都會生成新的 String 物件,然後將指標指向新的 String 物件,而 StringBuffer、StringBuilder 可以在原有物件的基礎上進行操作,所以在經常改變字串內容的情況下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的區別在於,StringBuffer 是執行緒安全的,而 StringBuilder 是非執行緒安全的,但 StringBuilder 的效能卻高於 StringBuffer,所以在單執行緒環境下推薦使用 StringBuilder,多執行緒環境下推薦使用 StringBuffer。
8. String str="i"與 String str=new String("i")一樣嗎?
不一樣,因為記憶體的分配方式不一樣。String str="i"的方式,java 虛擬機器會將其分配到常量池中;而 String str=new String("i") 則會被分到堆記憶體中。
9. 如何將字串反轉?
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
示例程式碼:
- // StringBuffer reverse
- StringBuffer stringBuffer = new StringBuffer();
- stringBuffer.append("abcdefg");
- System.out.println(stringBuffer.reverse()); // gfedcba
- // StringBuilder reverse
- StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append("abcdefg");
- System.out.println(stringBuilder.reverse()); // gfedcba
10. String 類的常用方法都有那些?
- indexOf():返回指定字元的索引。
- charAt():返回指定索引處的字元。
- replace():字串替換。
- trim():去除字串兩端空白。
- split():分割字串,返回一個分割後的字串陣列。
- getBytes():返回字串的 byte 型別陣列。
- length():返回字串長度。
- toLowerCase():將字串轉成小寫字母。
- toUpperCase():將字串轉成大寫字元。
- substring():擷取字串。
- equals():字串比較。
11. 抽象類必須要有抽象方法嗎?
不需要,抽象類不一定非要有抽象方法。
示例程式碼:
- abstract class Cat {
- public static void sayHi() {
- System.out.println("hi~");
- }
- }
上面程式碼,抽象類並沒有抽象方法但完全可以正常執行。
12. 普通類和抽象類有哪些區別?
- 普通類不能包含抽象方法,抽象類可以包含抽象方法。
- 抽象類不能直接例項化,普通類可以直接例項化。
13. 抽象類能使用 final 修飾嗎?
不能,定義抽象類就是讓其他類繼承的,如果定義為 final 該類就不能被繼承,這樣彼此就會產生矛盾,所以 final 不能修飾抽象類,如下圖所示,編輯器也會提示錯誤資訊:
14. 介面和抽象類有什麼區別?
- 實現:抽象類的子類使用 extends 來繼承;介面必須使用 implements 來實現介面。
- 建構函式:抽象類可以有建構函式;介面不能有。
- main 方法:抽象類可以有 main 方法,並且我們能執行它;介面不能有 main 方法。
- 實現數量:類可以實現很多個介面;但是隻能繼承一個抽象類。
- 訪問修飾符:介面中的方法預設使用 public 修飾;抽象類中的方法可以是任意訪問修飾符。
15. java 中 IO 流分為幾種?
按功能來分:輸入流(input)、輸出流(output)。
按型別來分:位元組流和字元流。
位元組流和字元流的區別是:位元組流按 8 位傳輸以位元組為單位輸入輸出資料,字元流按 16 位傳輸以字元為單位輸入輸出資料。
16. BIO、NIO、AIO 有什麼區別?
- BIO:Block IO 同步阻塞式 IO,就是我們平常使用的傳統 IO,它的特點是模式簡單使用方便,併發處理能力低。
- NIO:New IO 同步非阻塞 IO,是傳統 IO 的升級,客戶端和伺服器端通過 Channel(通道)通訊,實現了多路複用。
- AIO:Asynchronous IO 是 NIO 的升級,也叫 NIO2,實現了非同步非堵塞 IO ,非同步 IO 的操作基於事件和回撥機制。
17. Files的常用方法都有哪些?
- Files.exists():檢測檔案路徑是否存在。
- Files.createFile():建立檔案。
- Files.createDirectory():建立資料夾。
- Files.delete():刪除一個檔案或目錄。
- Files.copy():複製檔案。
- Files.move():移動檔案。
- Files.size():檢視檔案個數。
- Files.read():讀取檔案。
- Files.write():寫入檔案。
二、容器
18. java 容器都有哪些?
常用容器的圖錄:
19. Collection 和 Collections 有什麼區別?
- java.util.Collection 是一個集合介面(集合類的一個頂級介面)。它提供了對集合物件進行基本操作的通用介面方法。Collection介面在Java 類庫中有很多具體的實現。Collection介面的意義是為各種具體的集合提供了最大化的統一操作方式,其直接繼承介面有List與Set。
- Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜尋以及執行緒安全等各種操作。
20. List、Set、Map 之間的區別是什麼?
21. HashMap 和 Hashtable 有什麼區別?
- hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
- hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。
- hashMap允許空鍵值,而hashTable不允許。
22. 如何決定使用 HashMap 還是 TreeMap?
對於在Map中插入、刪除和定位元素這類操作,HashMap是最好的選擇。然而,假如你需要對一個有序的key集合進行遍歷,TreeMap是更好的選擇。基於你的collection的大小,也許向HashMap中新增元素會更快,將map換為TreeMap進行有序key的遍歷。
23. 說一下 HashMap 的實現原理?
HashMap概述: HashMap是基於雜湊表的Map介面的非同步實現。此實現提供所有可選的對映操作,並允許使用null值和null鍵。此類不保證對映的順序,特別是它不保證該順序恆久不變。
HashMap的資料結構: 在java程式語言中,最基本的結構就是兩種,一個是陣列,另外一個是模擬指標(引用),所有的資料結構都可以用這兩個基本結構來構造的,HashMap也不例外。HashMap實際上是一個“連結串列雜湊”的資料結構,即陣列和連結串列的結合體。
當我們往Hashmap中put元素時,首先根據key的hashcode重新計算hash值,根絕hash值得到這個元素在陣列中的位置(下標),如果該陣列在該位置上已經存放了其他元素,那麼在這個位置上的元素將以連結串列的形式存放,新加入的放在鏈頭,最先加入的放入鏈尾.如果陣列中該位置沒有元素,就直接將該元素放到陣列的該位置上。
需要注意Jdk 1.8中對HashMap的實現做了優化,當連結串列中的節點資料超過八個之後,該連結串列會轉為紅黑樹來提高查詢效率,從原來的O(n)到O(logn)
24. 說一下 HashSet 的實現原理?
- HashSet底層由HashMap實現
- HashSet的值存放於HashMap的key上
- HashMap的value統一為PRESENT
25. ArrayList 和 LinkedList 的區別是什麼?
最明顯的區別是 ArrrayList底層的資料結構是陣列,支援隨機訪問,而 LinkedList 的底層資料結構是雙向迴圈連結串列,不支援隨機訪問。使用下標訪問一個元素,ArrayList 的時間複雜度是 O(1),而 LinkedList 是 O(n)。
26. 如何實現陣列和 List 之間的轉換?
- List轉換成為陣列:呼叫ArrayList的toArray方法。
- 陣列轉換成為List:呼叫Arrays的asList方法。
27. ArrayList 和 Vector 的區別是什麼?
- Vector是同步的,而ArrayList不是。然而,如果你尋求在迭代的時候對列表進行改變,你應該使用CopyOnWriteArrayList。
- ArrayList比Vector快,它因為有同步,不會過載。
- ArrayList更加通用,因為我們可以使用Collections工具類輕易地獲取同步列表和只讀列表。
28. Array 和 ArrayList 有何區別?
- Array可以容納基本型別和物件,而ArrayList只能容納物件。
- Array是指定大小的,而ArrayList大小是固定的。
- Array沒有提供ArrayList那麼多功能,比如addAll、removeAll和iterator等。
29. 在 Queue 中 poll()和 remove()有什麼區別?
poll() 和 remove() 都是從佇列中取出一個元素,但是 poll() 在獲取元素失敗的時候會返回空,但是 remove() 失敗的時候會丟擲異常。
30. 哪些集合類是執行緒安全的?
- vector:就比arraylist多了個同步化機制(執行緒安全),因為效率較低,現在已經不太建議使用。在web應用中,特別是前臺頁面,往往效率(頁面響應速度)是優先考慮的。
- statck:堆疊類,先進後出。
- hashtable:就比hashmap多了個執行緒安全。
- enumeration:列舉,相當於迭代器。
31. 迭代器 Iterator 是什麼?
迭代器是一種設計模式,它是一個物件,它可以遍歷並選擇序列中的物件,而開發人員不需要了解該序列的底層結構。迭代器通常被稱為“輕量級”物件,因為建立它的代價小。
32. Iterator 怎麼使用?有什麼特點?
Java中的Iterator功能比較簡單,並且只能單向移動:
(1) 使用方法iterator()要求容器返回一個Iterator。第一次呼叫Iterator的next()方法時,它返回序列的第一個元素。注意:iterator()方法是java.lang.Iterable介面,被Collection繼承。
(2) 使用next()獲得序列中的下一個元素。
(3) 使用hasNext()檢查序列中是否還有元素。
(4) 使用remove()將迭代器新返回的元素刪除。
Iterator是Java迭代器最簡單的實現,為List設計的ListIterator具有更多的功能,它可以從兩個方向遍歷List,也可以從List中插入和刪除元素。
33. Iterator 和 ListIterator 有什麼區別?
- Iterator可用來遍歷Set和List集合,但是ListIterator只能用來遍歷List。
- Iterator對集合只能是前向遍歷,ListIterator既可以前向也可以後向。
- ListIterator實現了Iterator介面,幷包含其他的功能,比如:增加元素,替換元素,獲取前一個和後一個元素的索引,等等。
三、多執行緒
35. 並行和併發有什麼區別?
- 並行是指兩個或者多個事件在同一時刻發生;而併發是指兩個或多個事件在同一時間間隔發生。
- 並行是在不同實體上的多個事件,併發是在同一實體上的多個事件。
- 在一臺處理器上“同時”處理多個任務,在多臺處理器上同時處理多個任務。如hadoop分散式叢集。
所以併發程式設計的目標是充分的利用處理器的每一個核,以達到最高的處理效能。
36. 執行緒和程序的區別?
簡而言之,程序是程式執行和資源分配的基本單位,一個程式至少有一個程序,一個程序至少有一個執行緒。程序在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體資源,減少切換次數,從而效率更高。執行緒是程序的一個實體,是cpu排程和分派的基本單位,是比程式更小的能獨立執行的基本單位。同一程序中的多個執行緒之間可以併發執行。
37. 守護執行緒是什麼?
守護執行緒(即daemon thread),是個服務執行緒,準確地來說就是服務其他的執行緒。
38. 建立執行緒有哪幾種方式?
①. 繼承Thread類建立執行緒類
- 定義Thread類的子類,並重寫該類的run方法,該run方法的方法體就代表了執行緒要完成的任務。因此把run()方法稱為執行體。
- 建立Thread子類的例項,即建立了執行緒物件。
- 呼叫執行緒物件的start()方法來啟動該執行緒。
②. 通過Runnable介面建立執行緒類
- 定義runnable介面的實現類,並重寫該介面的run()方法,該run()方法的方法體同樣是該執行緒的執行緒執行體。
- 建立 Runnable實現類的例項,並依此例項作為Thread的target來建立Thread物件,該Thread物件才是真正的執行緒物件。
- 呼叫執行緒物件的start()方法來啟動該執行緒。
③. 通過Callable和Future建立執行緒
- 建立Callable介面的實現類,並實現call()方法,該call()方法將作為執行緒執行體,並且有返回值。
- 建立Callable實現類的例項,使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的返回值。
- 使用FutureTask物件作為Thread物件的target建立並啟動新執行緒。
- 呼叫FutureTask物件的get()方法來獲得子執行緒執行結束後的返回值。
39. 說一下 runnable 和 callable 有什麼區別?
有點深的問題了,也看出一個Java程式設計師學習知識的廣度。
- Runnable介面中的run()方法的返回值是void,它做的事情只是純粹地去執行run()方法中的程式碼而已;
- Callable介面中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取非同步執行的結果。
40. 執行緒有哪些狀態?
執行緒通常都有五種狀態,建立、就緒、執行、阻塞和死亡。
- 建立狀態。在生成執行緒物件,並沒有呼叫該物件的start方法,這是執行緒處於建立狀態。
- 就緒狀態。當呼叫了執行緒物件的start方法之後,該執行緒就進入了就緒狀態,但是此時執行緒排程程式還沒有把該執行緒設定為當前執行緒,此時處於就緒狀態。線上程執行之後,從等待或者睡眠中回來之後,也會處於就緒狀態。
- 執行狀態。執行緒排程程式將處於就緒狀態的執行緒設定為當前執行緒,此時執行緒就進入了執行狀態,開始執行run函式當中的程式碼。
- 阻塞狀態。執行緒正在執行的時候,被暫停,通常是為了等待某個時間的發生(比如說某項資源就緒)之後再繼續執行。sleep,suspend,wait等方法都可以導致執行緒阻塞。
- 死亡狀態。如果一個執行緒的run方法執行結束或者呼叫stop方法後,該執行緒就會死亡。對於已經死亡的執行緒,無法再使用start方法令其進入就緒
41. sleep() 和 wait() 有什麼區別?
sleep():方法是執行緒類(Thread)的靜態方法,讓呼叫執行緒進入睡眠狀態,讓出執行機會給其他執行緒,等到休眠時間結束後,執行緒進入就緒狀態和其他執行緒一起競爭cpu的執行時間。因為sleep() 是static靜態的方法,他不能改變物件的機鎖,當一個synchronized塊中呼叫了sleep() 方法,執行緒雖然進入休眠,但是物件的機鎖沒有被釋放,其他執行緒依然無法訪問這個物件。
wait():wait()是Object類的方法,當一個執行緒執行到wait方法時,它就進入到一個和該物件相關的等待池,同時釋放物件的機鎖,使得其他執行緒能夠訪問,可以通過notify,notifyAll方法來喚醒等待的執行緒。
42. notify()和 notifyAll()有什麼區別?
- 如果執行緒呼叫了物件的 wait()方法,那麼執行緒便會處於該物件的等待池中,等待池中的執行緒不會去競爭該物件的鎖。
- 當有執行緒呼叫了物件的 notifyAll()方法(喚醒所有 wait 執行緒)或 notify()方法(只隨機喚醒一個 wait 執行緒),被喚醒的的執行緒便會進入該物件的鎖池中,鎖池中的執行緒會去競爭該物件鎖。也就是說,呼叫了notify後只要一個執行緒會由等待池進入鎖池,而notifyAll會將該物件等待池內的所有執行緒移動到鎖池中,等待鎖競爭。
- 優先順序高的執行緒競爭到物件鎖的概率大,假若某執行緒沒有競爭到該物件鎖,它還會留在鎖池中,唯有執行緒再次呼叫 wait()方法,它才會重新回到等待池中。而競爭到物件鎖的執行緒則繼續往下執行,直到執行完了 synchronized 程式碼塊,它會釋放掉該物件鎖,這時鎖池中的執行緒會繼續競爭該物件鎖。
43. 執行緒的 run()和 start()有什麼區別?
每個執行緒都是通過某個特定Thread物件所對應的方法run()來完成其操作的,方法run()稱為執行緒體。通過呼叫Thread類的start()方法來啟動一個執行緒。
start()方法來啟動一個執行緒,真正實現了多執行緒執行。這時無需等待run方法體程式碼執行完畢,可以直接繼續執行下面的程式碼; 這時此執行緒是處於就緒狀態, 並沒有執行。 然後通過此Thread類呼叫方法run()來完成其執行狀態, 這裡方法run()稱為執行緒體,它包含了要執行的這個執行緒的內容, Run方法執行結束, 此執行緒終止。然後CPU再排程其它執行緒。
run()方法是在本執行緒裡的,只是執行緒裡的一個函式,而不是多執行緒的。如果直接呼叫run(),其實就相當於是呼叫了一個普通函式而已,直接待用run()方法必須等待run()方法執行完畢才能執行下面的程式碼,所以執行路徑還是隻有一條,根本就沒有執行緒的特徵,所以在多執行緒執行時要使用start()方法而不是run()方法。
44. 建立執行緒池有哪幾種方式?
①. newFixedThreadPool(int nThreads)
建立一個固定長度的執行緒池,每當提交一個任務就建立一個執行緒,直到達到執行緒池的最大數量,這時執行緒規模將不再變化,當執行緒發生未預期的錯誤而結束時,執行緒池會補充一個新的執行緒。
②. newCachedThreadPool()
建立一個可快取的執行緒池,如果執行緒池的規模超過了處理需求,將自動回收空閒執行緒,而當需求增加時,則可以自動新增新執行緒,執行緒池的規模不存在任何限制。
③. newSingleThreadExecutor()
這是一個單執行緒的Executor,它建立單個工作執行緒來執行任務,如果這個執行緒異常結束,會建立一個新的來替代它;它的特點是能確保依照任務在佇列中的順序來序列執行。
④. newScheduledThreadPool(int corePoolSize)
建立了一個固定長度的執行緒池,而且以延遲或定時的方式來執行任務,類似於Timer。
45. 執行緒池都有哪些狀態?
執行緒池有5種狀態:Running、ShutDown、Stop、Tidying、Terminated。
執行緒池各個狀態切換框架圖:
46. 執行緒池中 submit()和 execute()方法有什麼區別?
- 接收的引數不一樣
- submit有返回值,而execute沒有
- submit方便Exception處理
47. 在 java 程式中怎麼保證多執行緒的執行安全?
執行緒安全在三個方面體現:
- 原子性:提供互斥訪問,同一時刻只能有一個執行緒對資料進行操作,(atomic,synchronized);
- 可見性:一個執行緒對主記憶體的修改可以及時地被其他執行緒看到,(synchronized,volatile);
- 有序性:一個執行緒觀察其他執行緒中的指令執行順序,由於指令重排序,該觀察結果一般雜亂無序,(happens-before原則)。
48. 多執行緒鎖的升級原理是什麼?
在Java中,鎖共有4種狀態,級別從低到高依次為:無狀態鎖,偏向鎖,輕量級鎖和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。鎖可以升級但不能降級。
鎖升級的圖示過程:
49. 什麼是死鎖?
死鎖是指兩個或兩個以上的程序在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序。是作業系統層面的一個錯誤,是程序死鎖的簡稱,最早在 1965 年由 Dijkstra 在研究銀行家演算法時提出的,它是計算機作業系統乃至整個併發程式設計領域最難處理的問題之一。
50. 怎麼防止死鎖?
死鎖的四個必要條件:
- 互斥條件:程序對所分配到的資源不允許其他程序進行訪問,若其他程序訪問該資源,只能等待,直至佔有該資源的程序使用完成後釋放該資源
- 請求和保持條件:程序獲得一定的資源之後,又對其他資源發出請求,但是該資源可能被其他程序佔有,此事請求阻塞,但又對自己獲得的資源保持不放
- 不可剝奪條件:是指程序已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用完後自己釋放
- 環路等待條件:是指程序發生死鎖後,若干程序之間形成一種頭尾相接的迴圈等待資源關係
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之 一不滿足,就不會發生死鎖。
理解了死鎖的原因,尤其是產生死鎖的四個必要條件,就可以最大可能地避免、預防和 解除死鎖。
所以,在系統設計、程序排程等方面注意如何不讓這四個必要條件成立,如何確 定資源的合理分配演算法,避免程序永久佔據系統資源。
此外,也要防止程序在處於等待狀態的情況下佔用資源。因此,對資源的分配要給予合理的規劃。
51. ThreadLocal 是什麼?有哪些使用場景?
執行緒區域性變數是侷限於執行緒內部的變數,屬於執行緒自身所有,不在多個執行緒間共享。Java提供ThreadLocal類來支援執行緒區域性變數,是一種實現執行緒安全的方式。但是在管理環境下(如 web 伺服器)使用執行緒區域性變數的時候要特別小心,在這種情況下,工作執行緒的生命週期比任何應用變數的生命週期都要長。任何執行緒區域性變數一旦在工作完成後沒有釋放,Java 應用就存在記憶體洩露的風險。
52.說一下 synchronized 底層實現原理?
synchronized可以保證方法或者程式碼塊在執行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變數的記憶體可見性。
Java中每一個物件都可以作為鎖,這是synchronized實現同步的基礎:
- 普通同步方法,鎖是當前例項物件
- 靜態同步方法,鎖是當前類的class物件
- 同步方法塊,鎖是括號裡面的物件
53. synchronized 和 volatile 的區別是什麼?
- volatile本質是在告訴jvm當前變數在暫存器(工作記憶體)中的值是不確定的,需要從主存中讀取; synchronized則是鎖定當前變數,只有當前執行緒可以訪問該變數,其他執行緒被阻塞住。
- volatile僅能使用在變數級別;synchronized則可以使用在變數、方法、和類級別的。
- volatile僅能實現變數的修改可見性,不能保證原子性;而synchronized則可以保證變數的修改可見性和原子性。
- volatile不會造成執行緒的阻塞;synchronized可能會造成執行緒的阻塞。
- volatile標記的變數不會被編譯器優化;synchronized標記的變數可以被編譯器優化。
54. synchronized 和 Lock 有什麼區別?
- 首先synchronized是java內建關鍵字,在jvm層面,Lock是個java類;
- synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖;
- synchronized會自動釋放鎖(a執行緒執行完同步程式碼會釋放鎖 ;b 執行緒執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成執行緒死鎖;
- 用synchronized關鍵字的兩個執行緒1和執行緒2,如果當前執行緒1獲得鎖,執行緒2執行緒等待。如果執行緒1阻塞,執行緒2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,執行緒可以不用一直等待就結束了;
- synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可);
-
Lock鎖適合大量同步的程式碼的同步問題,synchronized鎖適合程式碼少量的同步問題。
55. synchronized 和 ReentrantLock 區別是什麼?
synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變數,ReentrantLock比synchronized的擴充套件性體現在幾點上:
- ReentrantLock可以對獲取鎖的等待時間進行設定,這樣就避免了死鎖
- ReentrantLock可以獲取各種鎖的資訊
- ReentrantLock可以靈活地實現多路通知
另外,二者的鎖機制其實也是不一樣的:ReentrantLock底層呼叫的是Unsafe的park方法加鎖,synchronized操作的應該是物件頭中mark word。
56. 說一下 atomic 的原理?
Atomic包中的類基本的特性就是在多執行緒環境下,當有多個執行緒同時對單個(包括基本型別及引用型別)變數進行操作時,具有排他性,即當多個執行緒同時對該變數的值進行更新時,僅有一個執行緒能成功,而未成功的執行緒可以向自旋鎖一樣,繼續嘗試,一直等到執行成功。
Atomic系列的類中的核心方法都會呼叫unsafe類中的幾個本地方法。我們需要先知道一個東西就是Unsafe類,全名為:sun.misc.Unsafe,這個類包含了大量的對C程式碼的操作,包括很多直接記憶體分配以及原子操作的呼叫,而它之所以標記為非安全的,是告訴你這個裡面大量的方法呼叫都會存在安全隱患,需要小心使用,否則會導致嚴重的後果,例如在通過unsafe分配記憶體的時候,如果自己指定某些區域可能會導致一些類似C++一樣的指標越界到其他程序的問題。
四、反射
57. 什麼是反射?
反射主要是指程式可以訪問、檢測和修改它本身狀態或行為的一種能力
Java反射:
在Java執行時環境中,對於任意一個類,能否知道這個類有哪些屬性和方法?對於任意一個物件,能否呼叫它的任意一個方法
Java反射機制主要提供了以下功能:
- 在執行時判斷任意一個物件所屬的類。
- 在執行時構造任意一個類的物件。
- 在執行時判斷任意一個類所具有的成員變數和方法。
- 在執行時呼叫任意一個物件的方法。
58. 什麼是 java 序列化?什麼情況下需要序列化?
簡單說就是為了儲存在記憶體中的各種物件的狀態(也就是例項變數,不是方法),並且可以把儲存的物件狀態再讀出來。雖然你可以用你自己的各種各樣的方法來儲存object states,但是Java給你提供一種應該比你自己好的儲存物件狀態的機制,那就是序列化。
什麼情況下需要序列化:
a)當你想把的記憶體中的物件狀態儲存到一個檔案中或者資料庫中時候;
b)當你想用套接字在網路上傳送物件的時候;
c)當你想通過RMI傳輸物件的時候;
59. 動態代理是什麼?有哪些應用?
動態代理:
當想要給實現了某個介面的類中的方法,加一些額外的處理。比如說加日誌,加事務等。可以給這個類建立一個代理,故名思議就是建立一個新的類,這個類不僅包含原來類方法的功能,而且還在原來的基礎上添加了額外處理的新類。這個代理類並不是定義好的,是動態生成的。具有解耦意義,靈活,擴充套件性強。
動態代理的應用:
- Spring的AOP
- 加事務
- 加許可權
- 加日誌
60. 怎麼實現動態代理?
首先必須定義一個介面,還要有一個InvocationHandler(將實現介面的類的物件傳遞給它)處理類。再有一個工具類Proxy(習慣性將其稱為代理類,因為呼叫他的newInstance()可以產生代理物件,其實他只是一個產生代理物件的工具類)。利用到InvocationHandler,拼接代理類原始碼,將其編譯生成代理類的二進位制碼,利用載入器載入,並將其例項化產生代理物件,最後返回。
五、物件拷貝
61. 為什麼要使用克隆?
想對一個物件進行處理,又想保留原有的資料進行接下來的操作,就需要克隆了,Java語言中克隆針對的是類的例項。
62. 如何實現物件克隆?
有兩種方式:
1). 實現Cloneable介面並重寫Object類中的clone()方法;
2). 實現Serializable介面,通過物件的序列化和反序列化實現克隆,可以實現真正的深度克隆,程式碼如下:
-
- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
-
- public class MyUtil {
-
- private MyUtil() {
- throw new AssertionError();
- }
-
- @SuppressWarnings("unchecked")
- public static <T extends Serializable> T clone(T obj) throws Exception {
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(bout);
- oos.writeObject(obj);
-
- ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
- ObjectInputStream ois = new ObjectInputStream(bin);
- return (T) ois.readObject();
-
- // 說明:呼叫ByteArrayInputStream或ByteArrayOutputStream物件的close方法沒有任何意義
- // 這兩個基於記憶體的流只要垃圾回收器清理物件就能夠釋放資源,這一點不同於對外部資源(如檔案流)的釋放
- }
- }
下面是測試程式碼:
-
- import java.io.Serializable;
-
- /**
- * 人類
- * @author nnngu
- *
- */
- class Person implements Serializable {
- private static final long serialVersionUID = -9102017020286042305L;
-
- private String name; // 姓名
- private int age; // 年齡
- private Car car; // 座駕
-
- public Person(String name, int age, Car car) {
- this.name = name;
- this.age = age;
- this.car = car;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public int getAge() {
- return age;
- }
-
- public void setAge(int age) {
- this.age = age;
- }
-
- public Car getCar() {
- return car;
- }
-
- public void setCar(Car car) {
- this.car = car;
- }
-
- @Override
- public String toString() {
- return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
- }
-
- }
-
- /**
- * 小汽車類
- * @author nnngu
- *
- */
- class Car implements Serializable {
- private static final long serialVersionUID = -5713945027627603702L;
-
- private String brand; // 品牌
- private int maxSpeed; // 最高時速
-
- public Car(String brand, int maxSpeed) {
- this.brand = brand;
- this.maxSpeed = maxSpeed;
- }
-
- public String getBrand() {
- return brand;
- }
-
- public void setBrand(String brand) {
- this.brand = brand;
- }
-
- public int getMaxSpeed() {
- return maxSpeed;
- }
-
- public void setMaxSpeed(int maxSpeed) {
- this.maxSpeed = maxSpeed;
- }
-
- @Override
- public String toString() {
- return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
- }
-
- }
- class CloneTest {
-
- public static void main(String[] args) {
- try {
- Person p1 = new Person("郭靖", 33, new Car("Benz", 300));
- Person p2 = MyUtil.clone(p1); // 深度克隆
- p2.getCar().setBrand("BYD");
- // 修改克隆的Person物件p2關聯的汽車物件的品牌屬性
- // 原來的Person物件p1關聯的汽車不會受到任何影響
- // 因為在克隆Person物件時其關聯的汽車物件也被克隆了
- System.out.println(p1);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
注意:基於序列化和反序列化實現的克隆不僅僅是深度克隆,更重要的是通過泛型限定,可以檢查出要克隆的物件是否支援序列化,這項檢查是編譯器完成的,不是在執行時丟擲異常,這種是方案明顯優於使用Object類的clone方法克隆物件。讓問題在編譯的時候暴露出來總是好過把問題留到執行時。
63. 深拷貝和淺拷貝區別是什麼?
- 淺拷貝只是複製了物件的引用地址,兩個物件指向同一個記憶體地址,所以修改其中任意的值,另一個值都會隨之變化,這就是淺拷貝(例:assign())
- 深拷貝是將物件及值複製過來,兩個物件修改其中任意的值另一個值不會改變,這就是深拷貝(例:JSON.parse()和JSON.stringify(),但是此方法無法複製函式型別)
六、Java Web
64. jsp 和 servlet 有什麼區別?
- jsp經編譯後就變成了Servlet.(JSP的本質就是Servlet,JVM只能識別java的類,不能識別JSP的程式碼,Web容器將JSP的程式碼編譯成JVM能夠識別的java類)
- jsp更擅長表現於頁面顯示,servlet更擅長於邏輯控制。
- Servlet中沒有內建物件,Jsp中的內建物件都是必須通過HttpServletRequest物件,HttpServletResponse物件以及HttpServlet物件得到。
- Jsp是Servlet的一種簡化,使用Jsp只需要完成程式設計師需要輸出到客戶端的內容,Jsp中的Java指令碼如何鑲嵌到一個類中,由Jsp容器完成。而Servlet則是個完整的Java類,這個類的Service方法用於生成對客戶端的響應。
65. jsp 有哪些內建物件?作用分別是什麼?
JSP有9個內建物件:
- request:封裝客戶端的請求,其中包含來自GET或POST請求的引數;
- response:封裝伺服器對客戶端的響應;
- pageContext:通過該物件可以獲取其他物件;
- session:封裝使用者會話的物件;
- application:封裝伺服器執行環境的物件;
- out:輸出伺服器響應的輸出流物件;
- config:Web應用的配置物件;
- page:JSP頁面本身(相當於Java程式中的this);
- exception:封裝頁面丟擲異常的物件。
66. 說一下 jsp 的 4 種作用域?
JSP中的四種作用域包括page、request、session和application,具體來說:
- page代表與一個頁面相關的物件和屬性。
- request代表與Web客戶機發出的一個請求相關的物件和屬性。一個請求可能跨越多個頁面,涉及多個Web元件;需要在頁面顯示的臨時資料可以置於此作用域。
- session代表與某個使用者與伺服器建立的一次會話相關的物件和屬性。跟某個使用者相關的資料應該放在使用者自己的session中。
- application代表與整個Web應用程式相關的物件和屬性,它實質上是跨越整個Web應用程式,包括多個頁面、請求和會話的一個全域性作用域。
67. session 和 cookie 有什麼區別?
- 由於HTTP協議是無狀態的協議,所以服務端需要記錄使用者的狀態時,就需要用某種機制來識具體的使用者,這個機制就是Session.典型的場景比如購物車,當你點選下單按鈕時,由於HTTP協議無狀態,所以並不知道是哪個使用者操作的,所以服務端要為特定的使用者建立了特定的Session,用用於標識這個使用者,並且跟蹤使用者,這樣才知道購物車裡面有幾本書。這個Session是儲存在服務端的,有一個唯一標識。在服務端儲存Session的方法很多,記憶體、資料庫、檔案都有。叢集的時候也要考慮Session的轉移,在大型的網站,一般會有專門的Session伺服器叢集,用來儲存使用者會話,這個時候 Session 資訊都是放在記憶體的,使用一些快取服務比如Memcached之類的來放 Session。
- 思考一下服務端如何識別特定的客戶?這個時候Cookie就登場了。每次HTTP請求的時候,客戶端都會發送相應的Cookie資訊到服務端。實際上大多數的應用都是用 Cookie 來實現Session跟蹤的,第一次建立Session的時候,服務端會在HTTP協議中告訴客戶端,需要在 Cookie 裡面記錄一個Session ID,以後每次請求把這個會話ID傳送到伺服器,我就知道你是誰了。有人問,如果客戶端的瀏覽器禁用了 Cookie 怎麼辦?一般這種情況下,會使用一種叫做URL重寫的技術來進行會話跟蹤,即每次HTTP互動,URL後面都會被附加上一個諸如 sid=xxxxx 這樣的引數,服務端據此來識別使用者。
- Cookie其實還可以用在一些方便使用者的場景下,設想你某次登陸過一個網站,下次登入的時候不想再次輸入賬號了,怎麼辦?這個資訊可以寫到Cookie裡面,訪問網站的時候,網站頁面的指令碼可以讀取這個資訊,就自動幫你把使用者名稱給填了,能夠方便一下使用者。這也是Cookie名稱的由來,給使用者的一點甜頭。所以,總結一下:Session是在服務端儲存的一個數據結構,用來跟蹤使用者的狀態,這個資料可以儲存在叢集、資料庫、檔案中;Cookie是客戶端儲存使用者資訊的一種機制,用來記錄使用者的一些資訊,也是實現Session的一種方式。
68. 說一下 session 的工作原理?
其實session是一個存在伺服器上的類似於一個散列表格的檔案。裡面存有我們需要的資訊,在我們需要用的時候可以從裡面取出來。類似於一個大號的map吧,裡面的鍵儲存的是使用者的sessionid,使用者向伺服器傳送請求的時候會帶上這個sessionid。這時就可以從中取出對應的值了。
69. 如果客戶端禁止 cookie 能實現 session 還能用嗎?
Cookie與 Session,一般認為是兩個獨立的東西,Session採用的是在伺服器端保持狀態的方案,而Cookie採用的是在客戶端保持狀態的方案。但為什麼禁用Cookie就不能得到Session呢?因為Session是用Session ID來確定當前對話所對應的伺服器Session,而Session ID是通過Cookie來傳遞的,禁用Cookie相當於失去了Session ID,也就得不到Session了。
假定使用者關閉Cookie的情況下使用Session,其實現途徑有以下幾種:
- 設定php.ini配置檔案中的“session.use_trans_sid = 1”,或者編譯時開啟打開了“--enable-trans-sid”選項,讓PHP自動跨頁傳遞Session ID。
- 手動通過URL傳值、隱藏表單傳遞Session ID。
- 用檔案、資料庫等形式儲存Session ID,在跨頁過程中手動呼叫。
70. spring mvc 和 struts 的區別是什麼?
- 攔截機制的不同
Struts2是類級別的攔截,每次請求就會建立一個Action,和Spring整合時Struts2的ActionBean注入作用域是原型模式prototype,然後通過setter,getter吧request資料注入到屬性。Struts2中,一個Action對應一個request,response上下文,在接收引數時,可以通過屬性接收,這說明屬性引數是讓多個方法共享的。Struts2中Action的一個方法可以對應一個url,而其類屬性卻被所有方法共享,這也就無法用註解或其他方式標識其所屬方法了,只能設計為多例。
SpringMVC是方法級別的攔截,一個方法對應一個Request上下文,所以方法直接基本上是獨立的,獨享request,response資料。而每個方法同時又何一個url對應,引數的傳遞是直接注入到方法中的,是方法所獨有的。處理結果通過ModeMap返回給框架。在Spring整合時,SpringMVC的Controller Bean預設單例模式Singleton,所以預設對所有的請求,只會建立一個Controller,有應為沒有共享的屬性,所以是執行緒安全的,如果要改變預設的作用域,需要新增@Scope註解修改。
Struts2有自己的攔截Interceptor機制,SpringMVC這是用的是獨立的Aop方式,這樣導致Struts2的配置檔案量還是比SpringMVC大。
- 底層框架的不同
Struts2採用Filter(StrutsPrepareAndExecuteFilter)實現,SpringMVC(DispatcherServlet)則採用Servlet實現。Filter在容器啟動之後即初始化;服務停止以後墜毀,晚於Servlet。Servlet在是在呼叫時初始化,先於Filter呼叫,服務停止後銷燬。
- 效能方面
Struts2是類級別的攔截,每次請求對應例項一個新的Action,需要載入所有的屬性值注入,SpringMVC實現了零配置,由於SpringMVC基於方法的攔截,有載入一次單例模式bean注入。所以,SpringMVC開發效率和效能高於Struts2。
- 配置方面
spring MVC和Spring是無縫的。從這個專案的管理和安全上也比Struts2高。
71. 如何避免 sql 注入?
- PreparedStatement(簡單又有效的方法)
- 使用正則表示式過濾傳入的引數
- 字串過濾
- JSP中呼叫該函式檢查是否包函非法字元
- JSP頁面判斷程式碼
72. 什麼是 XSS 攻擊,如何避免?
XSS攻擊又稱CSS,全稱Cross Site Script (跨站指令碼攻擊),其原理是攻擊者向有XSS漏洞的網站中輸入惡意的 HTML 程式碼,當用戶瀏覽該網站時,這段 HTML 程式碼會自動執行,從而達到攻擊的目的。XSS 攻擊類似於 SQL 注入攻擊,SQL注入攻擊中以SQL語句作為使用者輸入,從而達到查詢/修改/刪除資料的目的,而在xss攻擊中,通過插入惡意指令碼,實現對使用者遊覽器的控制,獲取使用者的一些資訊。 XSS是 Web 程式中常見的漏洞,XSS 屬於被動式且用於客戶端的攻擊方式。
XSS防範的總體思路是:對輸入(和URL引數)進行過濾,對輸出進行編碼。
73. 什麼是 CSRF 攻擊,如何避免?
CSRF(Cross-site request forgery)也被稱為 one-click attack或者 session riding,中文全稱是叫跨站請求偽造。一般來說,攻擊者通過偽造使用者的瀏覽器的請求,向訪問一個使用者自己曾經認證訪問過的網站傳送出去,使目標網站接收並誤以為是使用者的真實操作而去執行命令。常用於盜取賬號、轉賬、傳送虛假訊息等。攻擊者利用網站對請求的驗證漏洞而實現這樣的攻擊行為,網站能夠確認請求來源於使用者的瀏覽器,卻不能驗證請求是否源於使用者的真實意願下的操作行為。
如何避免:
1. 驗證 HTTP Referer 欄位
HTTP頭中的Referer欄位記錄了該 HTTP 請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求來自於同一個網站,而如果黑客要對其實施 CSRF
攻擊,他一般只能在他自己的網站構造請求。因此,可以通過驗證Referer值來防禦CSRF 攻擊。
2. 使用驗證碼
關鍵操作頁面加上驗證碼,後臺收到請求後通過判斷驗證碼可以防禦CSRF。但這種方法對使用者不太友好。
3. 在請求地址中新增token並驗證
CSRF 攻擊之所以能夠成功,是因為黑客可以完全偽造使用者的請求,該請求中所有的使用者驗證資訊都是存在於cookie中,因此黑客可以在不知道這些驗證資訊的情況下直接利用使用者自己的cookie 來通過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能偽造的資訊,並且該資訊不存在於 cookie 之中。可以在 HTTP 請求中以引數的形式加入一個隨機產生的 token,並在伺服器端建立一個攔截器來驗證這個 token,如果請求中沒有token或者 token 內容不正確,則認為可能是 CSRF 攻擊而拒絕該請求。這種方法要比檢查 Referer 要安全一些,token 可以在使用者登陸後產生並放於session之中,然後在每次請求時把token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在於如何把 token 以引數的形式加入請求。
對於 GET 請求,token 將附在請求地址之後,這樣 URL 就變成 http://url?csrftoken=tokenvalue。
而對於 POST 請求來說,要在 form 的最後加上 <input type="hidden" name="csrftoken" value="tokenvalue"/>,這樣就把token以引數的形式加入請求了。
4. 在HTTP 頭中自定義屬性並驗證
這種方法也是使用 token 並進行驗證,和上一種方法不同的是,這裡並不是把 token 以引數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性裡。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的位址列,也不用擔心 token 會透過 Referer 洩露到其他網站中去。
七、異常
74. throw 和 throws 的區別?
throws是用來宣告一個方法可能丟擲的所有異常資訊,throws是將異常宣告但是不處理,而是將異常往上傳,誰呼叫我就交給誰處理。而throw則是指丟擲的一個具體的異常型別。
75. final、finally、finalize 有什麼區別?
- final可以修飾類、變數、方法,修飾類表示該類不能被繼承、修飾方法表示該方法不能被重寫、修飾變量表示該變數是一個常量不能被重新賦值。
- finally一般作用在try-catch程式碼塊中,在處理異常的時候,通常我們將一定要執行的程式碼方法finally程式碼塊中,表示不管是否出現異常,該程式碼塊都會執行,一般用來存放一些關閉資源的程式碼。
- finalize是一個方法,屬於Object類的一個方法,而Object類是所有類的父類,該方法一般由垃圾回收器來呼叫,當我們呼叫System的gc()方法的時候,由垃圾回收器呼叫finalize(),回收垃圾。
76. try-catch-finally 中哪個部分可以省略?
答:catch 可以省略
原因:
更為嚴格的說法其實是:try只適合處理執行時異常,try+catch適合處理執行時異常+普通異常。也就是說,如果你只用try去處理普通異常卻不加以catch處理,編譯是通不過的,因為編譯器硬性規定,普通異常如果選擇捕獲,則必須用catch顯示宣告以便進一步處理。而執行時異常在編譯時沒有如此規定,所以catch可以省略,你加上catch編譯器也覺得無可厚非。
理論上,編譯器看任何程式碼都不順眼,都覺得可能有潛在的問題,所以你即使對所有程式碼加上try,程式碼在執行期時也只不過是在正常執行的基礎上加一層皮。但是你一旦對一段程式碼加上try,就等於顯示地承諾編譯器,對這段程式碼可能丟擲的異常進行捕獲而非向上丟擲處理。如果是普通異常,編譯器要求必須用catch捕獲以便進一步處理;如果執行時異常,捕獲然後丟棄並且+finally掃尾處理,或者加上catch捕獲以便進一步處理。
至於加上finally,則是在不管有沒捕獲異常,都要進行的“掃尾”處理。
77. try-catch-finally 中,如果 catch 中 return 了,finally 還會執行嗎?
答:會執行,在 return 前執行。
程式碼示例1:
-
- /*
- * java面試題--如果catch裡面有return語句,finally裡面的程式碼還會執行嗎?
- */
- public class FinallyDemo2 {
- public static void main(String[] args) {
- System.out.println(getInt());
- }
-
- public static int getInt() {
- int a = 10;
- try {
- System.out.println(a / 0);
- a = 20;
- } catch (ArithmeticException e) {
- a = 30;
- return a;
- /*
- * return a 在程式執行到這一步的時候,這裡不是return a 而是 return 30;這個返回路徑就形成了
- * 但是呢,它發現後面還有finally,所以繼續執行finally的內容,a=40
- * 再次回到以前的路徑,繼續走return 30,形成返回路徑之後,這裡的a就不是a變量了,而是常量30
- */
- } finally {
- a = 40;
- }
-
- // return a;
- }
- }
執行結果:30
程式碼示例2:
-
- package com.java_02;
-
- /*
- * java面試題--如果catch裡面有return語句,finally裡面的程式碼還會執行嗎?
- */
- public class FinallyDemo2 {
- public static void main(String[] args) {
- System.out.println(getInt());
- }
-
- public static int getInt() {
- int a = 10;
- try {
- System.out.println(a / 0);
- a = 20;
- } catch (ArithmeticException e) {
- a = 30;
- return a;
- /*
- * return a 在程式執行到這一步的時候,這裡不是return a 而是 return 30;這個返回路徑就形成了
- * 但是呢,它發現後面還有finally,所以繼續執行finally的內容,a=40
- * 再次回到以前的路徑,繼續走return 30,形成返回路徑之後,這裡的a就不是a變量了,而是常量30
- */
- } finally {
- a = 40;
- return a; //如果這樣,就又重新形成了一條返回路徑,由於只能通過1個return返回,所以這裡直接返回40
- }
-
- // return a;
- }
- }
執行結果:40
78. 常見的異常類有哪些?
- NullPointerException:當應用程式試圖訪問空物件時,則丟擲該異常。
- SQLException:提供關於資料庫訪問錯誤或其他錯誤資訊的異常。
- IndexOutOfBoundsException:指示某排序索引(例如對陣列、字串或向量的排序)超出範圍時丟擲。
- NumberFormatException:當應用程式試圖將字串轉換成一種數值型別,但該字串不能轉換為適當格式時,丟擲該異常。
- FileNotFoundException:當試圖開啟指定路徑名錶示的檔案失敗時,丟擲此異常。
- IOException:當發生某種I/O異常時,丟擲此異常。此類是失敗或中斷的I/O操作生成的異常的通用類。
- ClassCastException:當試圖將物件強制轉換為不是例項的子類時,丟擲該異常。
- ArrayStoreException:試圖將錯誤型別的物件儲存到一個物件陣列時丟擲的異常。
- IllegalArgumentException:丟擲的異常表明向方法傳遞了一個不合法或不正確的引數。
- ArithmeticException:當出現異常的運算條件時,丟擲此異常。例如,一個整數“除以零”時,丟擲此類的一個例項。
- NegativeArraySizeException:如果應用程式試圖建立大小為負的陣列,則丟擲該異常。
- NoSuchMethodException:無法找到某一特定方法時,丟擲該異常。
- SecurityException:由安全管理器丟擲的異常,指示存在安全侵犯。
- UnsupportedOperationException:當不支援請求的操作時,丟擲該異常。
- RuntimeExceptionRuntimeException:是那些可能在Java虛擬機器正常執行期間丟擲的異常的超類。
八、網路
79. http 響應碼 301 和 302 代表的是什麼?有什麼區別?
答:301,302都是HTTP狀態的編碼,都代表著某個URL發生了轉移。
區別:
- 301redirect:301代表永久性轉移(PermanentlyMoved)。
- 302redirect:302代表暫時性轉移(TemporarilyMoved)。
80. forward 和 redirect 的區別?
Forward和Redirect代表了兩種請求轉發方式:直接轉發和間接轉發。
直接轉發方式(Forward),客戶端和瀏覽器只發出一次請求,Servlet、HTML、JSP或其它資訊資源,由第二個資訊資源響應該請求,在請求物件request中,儲存的物件對於每個資訊資源是共享的。
間接轉發方式(Redirect)實際是兩次HTTP請求,伺服器端在響應第一次請求的時候,讓瀏覽器再向另外一個URL發出請求,從而達到轉發的目的。
舉個通俗的例子:
直接轉發就相當於:“A找B借錢,B說沒有,B去找C借,借到借不到都會把訊息傳遞給A”;
間接轉發就相當於:"A找B借錢,B說沒有,讓A去找C借"。
81. 簡述 tcp 和 udp的區別?
- TCP面向連線(如打電話要先撥號建立連線);UDP是無連線的,即傳送資料之前不需要建立連線。
- TCP提供可靠的服務。也就是說,通過TCP連線傳送的資料,無差錯,不丟失,不重複,且按序到達;UDP盡最大努力交付,即不保證可靠交付。
- Tcp通過校驗和,重傳控制,序號標識,滑動視窗、確認應答實現可靠傳輸。如丟包時的重發控制,還可以對次序亂掉的分包進行順序控制。
- UDP具有較好的實時性,工作效率比TCP高,適用於對高速傳輸和實時性有較高的通訊或廣播通訊。
- 每一條TCP連線只能是點到點的;UDP支援一對一,一對多,多對一和多對多的互動通訊。
- TCP對系統資源要求較多,UDP對系統資源要求較少。
82. tcp 為什麼要三次握手,兩次不行嗎?為什麼?
為了實現可靠資料傳輸, TCP 協議的通訊雙方, 都必須維護一個序列號, 以標識傳送出去的資料包中, 哪些是已經被對方收到的。 三次握手的過程即是通訊雙方相互告知序列號起始值, 並確認對方已經收到了序列號起始值的必經步驟。
如果只是兩次握手, 至多隻有連線發起方的起始序列號能被確認, 另一方選擇的序列號則得不到確認。
83. 說一下 tcp 粘包是怎麼產生的?
①. 傳送方產生粘包
採用TCP協議傳輸資料的客戶端與伺服器經常是保持一個長連線的狀態(一次連線發一次資料不存在粘包),雙方在連線不斷開的情況下,可以一直傳輸資料;但當傳送的資料包過於的小時,那麼TCP協議預設的會啟用Nagle演算法,將這些較小的資料包進行合併傳送(緩衝區資料傳送是一個堆壓的過程);這個合併過程就是在傳送緩衝區中進行的,也就是說資料傳送出來它已經是粘包的狀態了。
②. 接收方產生粘包
接收方採用TCP協議接收資料時的過程是這樣的:資料到底接收方,從網路模型的下方傳遞至傳輸層,傳輸層的TCP協議處理是將其放置接收緩衝區,然後由應用層來主動獲取(C語言用recv、read等函式);這時會出現一個問題,就是我們在程式中呼叫的讀取資料函式不能及時的把緩衝區中的資料拿出來,而下一個資料又到來並有一部分放入的緩衝區末尾,等我們讀取資料時就是一個粘包。(放資料的速度 > 應用層拿資料速度)
84. OSI 的七層模型都有哪些?
- 應用層:網路服務與終端使用者的一個介面。
- 表示層:資料的表示、安全、壓縮。
- 會話層:建立、管理、終止會話。
- 傳輸層:定義傳輸資料的協議埠號,以及流控和差錯校驗。
- 網路層:進行邏輯地址定址,實現不同網路之間的路徑選擇。
- 資料鏈路層:建立邏輯連線、進行硬體地址定址、差錯校驗等功能。
- 物理層:建立、維護、斷開物理連線。
85. get 和 post 請求有哪些區別?
- GET在瀏覽器回退時是無害的,而POST會再次提交請求。
- GET產生的URL地址可以被Bookmark,而POST不可以。
- GET請求會被瀏覽器主動cache,而POST不會,除非手動設定。
- GET請求只能進行url編碼,而POST支援多種編碼方式。
- GET請求引數會被完整保留在瀏覽器歷史記錄裡,而POST中的引數不會被保留。
- GET請求在URL中傳送的引數是有長度限制的,而POST麼有。
- 對引數的資料型別,GET只接受ASCII字元,而POST沒有限制。
- GET比POST更不安全,因為引數直接暴露在URL上,所以不能用來傳遞敏感資訊。
- GET引數通過URL傳遞,POST放在Request body中。
86. 如何實現跨域?
方式一:圖片ping或script標籤跨域
圖片ping常用於跟蹤使用者點選頁面或動態廣告曝光次數。
script標籤可以得到從其他來源資料,這也是JSONP依賴的根據。
方式二:JSONP跨域
JSONP(JSON with Padding)是資料格式JSON的一種“使用模式”,可以讓網頁從別的網域要資料。根據 XmlHttpRequest 物件受到同源策略的影響,而利用 <script>元素的這個開放策略,網頁可以得到從其他來源動態產生的JSON資料,而這種使用模式就是所謂的 JSONP。用JSONP抓到的資料並不是JSON,而是任意的JavaScript,用 JavaScript直譯器執行而不是用JSON解析器解析。所有,通過Chrome檢視所有JSONP傳送的Get請求都是js型別,而非XHR。
缺點:
- 只能使用Get請求
- 不能註冊success、error等事件監聽函式,不能很容易的確定JSONP請求是否失敗
- JSONP是從其他域中載入程式碼執行,容易受到跨站請求偽造的攻擊,其安全性無法確保
方式三:CORS
Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術的規範,提供了 Web 服務從不同域傳來沙盒指令碼的方法,以避開瀏覽器的同源策略,確保安全的跨域資料傳輸。現代瀏覽器使用CORS在API容器如XMLHttpRequest來減少HTTP請求的風險來源。與 JSONP 不同,CORS 除了 GET 要求方法以外也支援其他的 HTTP 要求。伺服器一般需要增加如下響應頭的一種或幾種:
- Access-Control-Allow-Origin: *
- Access-Control-Allow-Methods: POST, GET, OPTIONS
- Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
- Access-Control-Max-Age: 86400
跨域請求預設不會攜帶Cookie資訊,如果需要攜帶,請配置下述引數:
- "Access-Control-Allow-Credentials": true
- // Ajax設定
- "withCredentials": true
方式四:window.name+iframe
window.name通過在iframe(一般動態建立i)中載入跨域HTML檔案來起作用。然後,HTML檔案將傳遞給請求者的字串內容賦值給window.name。然後,請求者可以檢索window.name值作為響應。
- iframe標籤的跨域能力;
- window.name屬性值在文件重新整理後依舊存在的能力(且最大允許2M左右)。
每個iframe都有包裹它的window,而這個window是top window的子視窗。contentWindow屬性返回<iframe>元素的Window物件。你可以使用這個Window物件來訪問iframe的文件及其內部DOM。
- <!--
- 下述用埠
- 10000表示:domainA
- 10001表示:domainB
- -->
-
- <!-- localhost:10000 -->
- <script>
- var iframe = document.createElement('iframe');
- iframe.style.display = 'none'; // 隱藏
-
- var state = 0; // 防止頁面無限重新整理
- iframe.onload = function() {
- if(state === 1) {
- console.log(JSON.parse(iframe.contentWindow.name));
- // 清除建立的iframe
- iframe.contentWindow.document.write('');
- iframe.contentWindow.close();
- document.body.removeChild(iframe);
- } else if(state === 0) {
- state = 1;
- // 載入完成,指向當前域,防止錯誤(proxy.html為空白頁面)
- // Blocked a frame with origin "http://localhost:10000" from accessing a cross-origin frame.
- iframe.contentWindow.location = 'http://localhost:10000/proxy.html';
- }
- };
-
- iframe.src = 'http://localhost:10001';
- document.body.appendChild(iframe);
- </script>
-
- <!-- localhost:10001 -->
- <!DOCTYPE html>
- ...
- <script>
- window.name = JSON.stringify({a: 1, b: 2});
- </script>
- </html>
方式五:window.postMessage()
HTML5新特性,可以用來向其他所有的 window 物件傳送訊息。需要注意的是我們必須要保證所有的指令碼執行完才傳送 MessageEvent,如果在函式執行的過程中呼叫了它,就會讓後面的函式超時無法執行。
下述程式碼實現了跨域儲存localStorage
- <!--
- 下述用埠
- 10000表示:domainA
- 10001表示:domainB
- -->
-
- <!-- localhost:10000 -->
- <iframe src="http://localhost:10001/msg.html" name="myPostMessage" style="display:none;">
- </iframe>
-
- <script>
- function main() {
- LSsetItem('test', 'Test: ' + new Date());
- LSgetItem('test', function(value) {
- console.log('value: ' + value);
- });
- LSremoveItem('test');
- }
-
- var callbacks = {};
- window.addEventListener('message', function(event) {
- if (event.source === frames['myPostMessage']) {
- console.log(event)
- var data = /^#localStorage#(\d+)(null)?#([\S\s]*)/.exec(event.data);
- if (data) {
- if (callbacks[data[1]]) {
- callbacks[data[1]](data[2] === 'null' ? null : data[3]);
- }
- delete callbacks[data[1]];
- }
- }
- }, false);
-
- var domain = '*';
- // 增加
- function LSsetItem(key, value) {
- var obj = {
- setItem: key,
- value: value
- };
- frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
- }
- // 獲取
- function LSgetItem(key, callback) {
- var identifier = new Date().getTime();
- var obj = {
- identifier: identifier,
- getItem: key
- };
- callbacks[identifier] = callback;
- frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
- }
- // 刪除
- function LSremoveItem(key) {
- var obj = {
- removeItem: key
- };
- frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
- }
- </script>
-
- <!-- localhost:10001 -->
- <script>
- window.addEventListener('message', function(event) {
- console.log('Receiver debugging', event);
- if (event.origin == 'http://localhost:10000') {
- var data = JSON.parse(event.data);
- if ('setItem' in data) {
- localStorage.setItem(data.setItem, data.value);
- } else if ('getItem' in data) {
- var gotItem = localStorage.getItem(data.getItem);
- event.source.postMessage(
- '#localStorage#' + data.identifier +
- (gotItem === null ? 'null#' : '#' + gotItem),
- event.origin
- );
- } else if ('removeItem' in data) {
- localStorage.removeItem(data.removeItem);
- }
- }
- }, false);
- </script>
注意Safari一下,會報錯:
Blockedaframewithorigin“http://localhost:10001”fromaccessingaframewithorigin“http://localhost:10000“.Protocols,domains,andportsmustmatch.
避免該錯誤,可以在Safari瀏覽器中勾選開發選單==>停用跨域限制。或者只能使用伺服器端轉存的方式實現,因為Safari瀏覽器預設只支援CORS跨域請求。
方式六:修改document.domain跨子域
前提條件:這兩個域名必須屬於同一個基礎域名!而且所用的協議,埠都要一致,否則無法利用document.domain進行跨域,所以只能跨子域
在根域範圍內,允許把domain屬性的值設定為它的上一級域。例如,在”aaa.xxx.com”域內,可以把domain設定為 “xxx.com” 但不能設定為 “xxx.org” 或者”com”。
現在存在兩個域名aaa.xxx.com和bbb.xxx.com。在aaa下嵌入bbb的頁面,由於其document.name不一致,無法在aaa下操作bbb的js。可以在aaa和bbb下通過js將document.name='xxx.com';設定一致,來達到互相訪問的作用。
方式七:WebSocket
WebSocket protocol 是HTML5一種新的協議。它實現了瀏覽器與伺服器全雙工通訊,同時允許跨域通訊,是server push技術的一種很棒的實現。相關文章,請檢視:WebSocket、WebSocket-SockJS
需要注意:WebSocket物件不支援DOM 2級事件偵聽器,必須使用DOM 0級語法分別定義各個事件。
方式八:代理
同源策略是針對瀏覽器端進行的限制,可以通過伺服器端來解決該問題
DomainA客戶端(瀏覽器) ==> DomainA伺服器 ==> DomainB伺服器 ==> DomainA客戶端(瀏覽器)
來源:blog.csdn.net/ligang2585116/article/details/73072868
87.說一下 JSONP 實現原理?
jsonp 即 json+padding,動態建立script標籤,利用script標籤的src屬性可以獲取任何域下的js指令碼,通過這個特性(也可以說漏洞),伺服器端不在返貨json格式,而是返回一段呼叫某個函式的js程式碼,在src中進行了呼叫,這樣實現了跨域。
九、設計模式
88. 說一下你熟悉的設計模式?
89. 簡單工廠和抽象工廠有什麼區別?
簡單工廠模式:
這個模式本身很簡單而且使用在業務較簡單的情況下。一般用於小專案或者具體產品很少擴充套件的情況(這樣工廠類才不用經常更改)。
它由三種角色組成:
- 工廠類角色:這是本模式的核心,含有一定的商業邏輯和判斷邏輯,根據邏輯不同,產生具體的工廠產品。如例子中的Driver類。
- 抽象產品角色:它一般是具體產品繼承的父類或者實現的介面。由介面或者抽象類來實現。如例中的Car介面。
- 具體產品角色:工廠類所建立的物件就是此角色的例項。在java中由一個具體類實現,如例子中的Benz、Bmw類。
來用類圖來清晰的表示下的它們之間的關係:
抽象工廠模式:
先來認識下什麼是產品族: 位於不同產品等級結構中,功能相關聯的產品組成的家族。
圖中的BmwCar和BenzCar就是兩個產品樹(產品層次結構);而如圖所示的BenzSportsCar和BmwSportsCar就是一個產品族。他們都可以放到跑車家族中,因此功能有所關聯。同理BmwBussinessCar和BenzBusinessCar也是一個產品族。
可以這麼說,它和工廠方法模式的區別就在於需要建立物件的複雜程度上。而且抽象工廠模式是三個裡面最為抽象、最具一般性的。抽象工廠模式的用意為:給客戶端提供一個介面,可以建立多個產品族中的產品物件。
而且使用抽象工廠模式還要滿足一下條件:
- 系統中有多個產品族,而系統一次只可能消費其中一族產品
- 同屬於同一個產品族的產品以其使用。
來看看抽象工廠模式的各個角色(和工廠方法的如出一轍):
- 抽象工廠角色: 這是工廠方法模式的核心,它與應用程式無關。是具體工廠角色必須實現的介面或者必須繼承的父類。在java中它由抽象類或者介面來實現。
- 具體工廠角色:它含有和具體業務邏輯有關的程式碼。由應用程式呼叫以建立對應的具體產品的物件。在java中它由具體的類來實現。
- 抽象產品角色:它是具體產品繼承的父類或者是實現的介面。在java中一般有抽象類或者介面來實現。
- 具體產品角色:具體工廠角色所建立的物件就是此角色的例項。在java中由具體的類來實現。
十、Spring / Spring MVC
90. 為什麼要使用 spring?
1.簡介
- 目的:解決企業應用開發的複雜性
- 功能:使用基本的JavaBean代替EJB,並提供了更多的企業應用功能
- 範圍:任何Java應用
簡單來說,Spring是一個輕量級的控制反轉(IoC)和麵向切面(AOP)的容器框架。
2.輕量
從大小與開銷兩方面而言Spring都是輕量的。完整的Spring框架可以在一個大小隻有1MB多的JAR檔案裡釋出。並且Spring所需的處理開銷也是微不足道的。此外,Spring是非侵入式的:典型地,Spring應用中的物件不依賴於Spring的特定類。
3.控制反轉
Spring通過一種稱作控制反轉(IoC)的技術促進了鬆耦合。當應用了IoC,一個物件依賴的其它物件會通過被動的方式傳遞進來,而不是這個物件自己建立或者查詢依賴物件。你可以認為IoC與JNDI相反——不是物件從容器中查詢依賴,而是容器在物件初始化時不等物件請求就主動將依賴傳遞給它。
4.面向切面
Spring提供了面向切面程式設計的豐富支援,允許通過分離應用的業務邏輯與系統級服務(例如審計(auditing)和事務(transaction)管理)進行內聚性的開發。應用物件只實現它們應該做的——完成業務邏輯——僅此而已。它們並不負責(甚至是意識)其它的系統級關注點,例如日誌或事務支援。
5.容器
Spring包含並管理應用物件的配置和生命週期,在這個意義上它是一種容器,你可以配置你的每個bean如何被建立——基於一個可配置原型(prototype),你的bean可以建立一個單獨的例項或者每次需要時都生成一個新的例項——以及它們是如何相互關聯的。然而,Spring不應該被混同於傳統的重量級的EJB容器,它們經常是龐大與笨重的,難以使用。
6.框架
Spring可以將簡單的元件配置、組合成為複雜的應用。在Spring中,應用物件被宣告式地組合,典型地是在一個XML檔案裡。Spring也提供了很多基礎功能(事務管理、持久化框架整合等等),將應用邏輯的開發留給了你。
所有Spring的這些特徵使你能夠編寫更乾淨、更可管理、並且更易於測試的程式碼。它們也為Spring中的各種模組提供了基礎支援。
91. 解釋一下什麼是 aop?
AOP(Aspect-Oriented Programming,面向方面程式設計),可以說是OOP(Object-Oriented Programing,面向物件程式設計)的補充和完善。OOP引入封裝、繼承和多型性等概念來建立一種物件層次結構,用以模擬公共行為的一個集合。當我們需要為分散的物件引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關係,但並不適合定義從左到右的關係。例如日誌功能。日誌程式碼往往水平地散佈在所有物件層次中,而與它所散佈到的物件的核心功能毫無關係。對於其他型別的程式碼,如安全性、異常處理和透明的持續性也是如此。這種散佈在各處的無關的程式碼被稱為橫切(cross-cutting)程式碼,在OOP設計中,它導致了大量程式碼的重複,而不利於各個模組的重用。
而AOP技術則恰恰相反,它利用一種稱為“橫切”的技術,剖解開封裝的物件內部,並將那些影響了多個類的公共行為封裝到一個可重用模組,並將其名為“Aspect”,即方面。所謂“方面”,簡單地說,就是將那些與業務無關,卻為業務模組所共同呼叫的邏輯或責任封裝起來,便於減少系統的重複程式碼,降低模組間的耦合度,並有利於未來的可操作性和可維護性。AOP代表的是一個橫向的關係,如果說“物件”是一個空心的圓柱體,其中封裝的是物件的屬性和行為;那麼面向方面程式設計的方法,就彷彿一把利刃,將這些空心圓柱體剖開,以獲得其內部的訊息。而剖開的切面,也就是所謂的“方面”了。然後它又以巧奪天功的妙手將這些剖開的切面復原,不留痕跡。
使用“橫切”技術,AOP把軟體系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處都基本相似。比如許可權認證、日誌、事務處理。Aop 的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。正如Avanade公司的高階方案構架師Adam Magee所說,AOP的核心思想就是“將應用程式中的商業邏輯同對其提供支援的通用服務進行分離。”
92. 解釋一下什麼是 ioc?
IOC是Inversion of Control的縮寫,多數書籍翻譯成“控制反轉”。
1996年,Michael Mattson在一篇有關探討面向物件框架的文章中,首先提出了IOC 這個概念。對於面向物件設計及程式設計的基本思想,前面我們已經講了很多了,不再贅述,簡單來說就是把複雜系統分解成相互合作的物件,這些物件類通過封裝以後,內部實現對外部是透明的,從而降低了解決問題的複雜度,而且可以靈活地被重用和擴充套件。
IOC理論提出的觀點大體是這樣的:藉助於“第三方”實現具有依賴關係的物件之間的解耦。如下圖:
大家看到了吧,由於引進了中間位置的“第三方”,也就是IOC容器,使得A、B、C、D這4個物件沒有了耦合關係,齒輪之間的傳動全部依靠“第三方”了,全部物件的控制權全部上繳給“第三方”IOC容器,所以,IOC容器成了整個系統的關鍵核心,它起到了一種類似“粘合劑”的作用,把系統中的所有物件粘合在一起發揮作用,如果沒有這個“粘合劑”,物件與物件之間會彼此失去聯絡,這就是有人把IOC容器比喻成“粘合劑”的由來。
我們再來做個試驗:把上圖中間的IOC容器拿掉,然後再來看看這套系統:
我們現在看到的畫面,就是我們要實現整個系統所需要完成的全部內容。這時候,A、B、C、D這4個物件之間已經沒有了耦合關係,彼此毫無聯絡,這樣的話,當你在實現A的時候,根本無須再去考慮B、C和D了,物件之間的依賴關係已經降低到了最低程度。所以,如果真能實現IOC容器,對於系統開發而言,這將是一件多麼美好的事情,參與開發的每一成員只要實現自己的類就可以了,跟別人沒有任何關係!
我們再來看看,控制反轉(IOC)到底為什麼要起這麼個名字?我們來對比一下:
軟體系統在沒有引入IOC容器之前,如圖1所示,物件A依賴於物件B,那麼物件A在初始化或者執行到某一點的時候,自己必須主動去建立物件B或者使用已經建立的物件B。無論是建立還是使用物件B,控制權都在自己手上。
軟體系統在引入IOC容器之後,這種情形就完全改變了,如圖3所示,由於IOC容器的加入,物件A與物件B之間失去了直接聯絡,所以,當物件A執行到需要物件B的時候,IOC容器會主動建立一個物件B注入到物件A需要的地方。
通過前後的對比,我們不難看出來:物件A獲得依賴物件B的過程,由主動行為變為了被動行為,控制權顛倒過來了,這就是“控制反轉”這個名稱的由來。
93. spring 有哪些主要模組?
Spring框架至今已集成了20多個模組。這些模組主要被分如下圖所示的核心容器、資料訪問/整合,、Web、AOP(面向切面程式設計)、工具、訊息和測試模組。
更多資訊:howtodoinjava.com/java-spring-framework-tutorials/
94. spring 常用的注入方式有哪些?
Spring通過DI(依賴注入)實現IOC(控制反轉),常用的注入方式主要有三種:
- 構造方法注入
- setter注入
- 基於註解的注入
95. spring 中的 bean 是執行緒安全的嗎?
Spring容器中的Bean是否執行緒安全,容器本身並沒有提供Bean的執行緒安全策略,因此可以說spring容器中的Bean本身不具備執行緒安全的特性,但是具體還是要結合具體scope的Bean去研究。
96. spring 支援幾種 bean 的作用域?
當通過spring容器建立一個Bean例項時,不僅可以完成Bean例項的例項化,還可以為Bean指定特定的作用域。Spring支援如下5種作用域:
- singleton:單例模式,在整個Spring IoC容器中,使用singleton定義的Bean將只有一個例項
- prototype:原型模式,每次通過容器的getBean方法獲取prototype定義的Bean時,都將產生一個新的Bean例項
- request:對於每次HTTP請求,使用request定義的Bean都將產生一個新例項,即每次HTTP請求將會產生不同的Bean例項。只有在Web應用中使用Spring時,該作用域才有效
- session:對於每次HTTP Session,使用session定義的Bean豆漿產生一個新例項。同樣只有在Web應用中使用Spring時,該作用域才有效
- globalsession:每個全域性的HTTP Session,使用session定義的Bean都將產生一個新例項。典型情況下,僅在使用portlet context的時候有效。同樣只有在Web應用中使用Spring時,該作用域才有效
其中比較常用的是singleton和prototype兩種作用域。對於singleton作用域的Bean,每次請求該Bean都將獲得相同的例項。容器負責跟蹤Bean例項的狀態,負責維護Bean例項的生命週期行為;如果一個Bean被設定成prototype作用域,程式每次請求該id的Bean,Spring都會新建一個Bean例項,然後返回給程式。在這種情況下,Spring容器僅僅使用new 關鍵字建立Bean例項,一旦建立成功,容器不在跟蹤例項,也不會維護Bean例項的狀態。
如果不指定Bean的作用域,Spring預設使用singleton作用域。Java在建立Java例項時,需要進行記憶體申請;銷燬例項時,需要完成垃圾回收,這些工作都會導致系統開銷的增加。因此,prototype作用域Bean的建立、銷燬代價比較大。而singleton作用域的Bean例項一旦建立成功,可以重複使用。因此,除非必要,否則儘量避免將Bean被設定成prototype作用域。
97. spring 自動裝配 bean 有哪些方式?
Spring容器負責建立應用程式中的bean同時通過ID來協調這些物件之間的關係。作為開發人員,我們需要告訴Spring要建立哪些bean並且如何將其裝配到一起。
spring中bean裝配有兩種方式:
- 隱式的bean發現機制和自動裝配
- 在java程式碼或者XML中進行顯示配置
當然這些方式也可以配合使用。
98. spring 事務實現方式有哪些?
- 程式設計式事務管理對基於 POJO 的應用來說是唯一選擇。我們需要在程式碼中呼叫beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是程式設計式事務管理。
- 基於 TransactionProxyFactoryBean 的宣告式事務管理
- 基於 @Transactional 的宣告式事務管理
- 基於 Aspectj AOP 配置事務
99. 說一下 spring 的事務隔離?
事務隔離級別指的是一個事務對資料的修改與另一個並行的事務的隔離程度,當多個事務同時訪問相同資料時,如果沒有采取必要的隔離機制,就可能發生以下問題:
- 髒讀:一個事務讀到另一個事務未提交的更新資料。
- 幻讀:例如第一個事務對一個表中的資料進行了修改,比如這種修改涉及到表中的“全部資料行”。同時,第二個事務也修改這個表中的資料,這種修改是向表中插入“一行新資料”。那麼,以後就會發生操作第一個事務的使用者發現表中還存在沒有修改的資料行,就好象發生了幻覺一樣。
- 不可重複讀:比方說在同一個事務中先後執行兩條一模一樣的select語句,期間在此次事務中沒有執行過任何DDL語句,但先後得到的結果不一致,這就是不可重複讀。
100. 說一下 spring mvc 執行流程?
Spring MVC執行流程圖:
Spring執行流程描述:
1. 使用者向伺服器傳送請求,請求被Spring 前端控制Servelt DispatcherServlet捕獲;
2.DispatcherServlet對請求URL進行解析,得到請求資源識別符號(URI)。然後根據該URI,呼叫HandlerMapping獲得該Handler配置的所有相關的物件(包括Handler物件以及Handler物件對應的攔截器),最後以HandlerExecutionChain物件的形式返回;
3.DispatcherServlet 根據獲得的Handler,選擇一個合適的HandlerAdapter;(附註:如果成功獲得HandlerAdapter後,此時將開始執行攔截器的preHandler(...)方法)
4. 提取Request中的模型資料,填充Handler入參,開始執行Handler(Controller)。 在填充Handler的入參過程中,根據你的配置,Spring將幫你做一些額外的工作:
- HttpMessageConveter: 將請求訊息(如Json、xml等資料)轉換成一個物件,將物件轉換為指定的響應資訊
- 資料轉換:對請求訊息進行資料轉換。如String轉換成Integer、Double等
- 資料根式化:對請求訊息進行資料格式化。 如將字串轉換成格式化數字或格式化日期等
- 資料驗證: 驗證資料的有效性(長度、格式等),驗證結果儲存到BindingResult或Error中
5. Handler執行完成後,向DispatcherServlet返回一個ModelAndView物件;
6. 根據返回的ModelAndView,選擇一個適合的ViewResolver(必須是已經註冊到Spring容器中的ViewResolver)返回給DispatcherServlet;
7.ViewResolver 結合Model和View,來渲染檢視;
8. 將渲染結果返回給客戶端。
101. spring mvc 有哪些元件?
Spring MVC的核心元件:
- DispatcherServlet:中央控制器,把請求給轉發到具體的控制類
- Controller:具體處理請求的控制器
- HandlerMapping:對映處理器,負責對映中央處理器轉發給controller時的對映策略
- ModelAndView:服務層返回的資料和檢視層的封裝類
- ViewResolver:檢視解析器,解析具體的檢視
- Interceptors :攔截器,負責攔截我們定義的請求然後做處理工作
102. @RequestMapping 的作用是什麼?
RequestMapping是一個用來處理請求地址對映的註解,可用於類或方法上。用於類上,表示類中的所有響應請求的方法都是以該地址作為父路徑。
RequestMapping註解有六個屬性,下面我們把她分成三類進行說明。
value, method:
- value:指定請求的實際地址,指定的地址可以是URI Template 模式(後面將會說明);
- method:指定請求的method型別, GET、POST、PUT、DELETE等;
consumes,produces
- consumes:指定處理請求的提交內容型別(Content-Type),例如application/json, text/html;
- produces:指定返回的內容型別,僅當request請求頭中的(Accept)型別中包含該指定型別才返回;
params,headers
- params: 指定request中必須包含某些引數值是,才讓該方法處理。
- headers:指定request中必須包含某些指定的header值,才能讓該方法處理請求。