發現網上很多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發生了轉移。
區別:
- 301 redirect: 301 代表永久性轉移(Permanently Moved)。
- 302 redirect: 302 代表暫時性轉移(Temporarily Moved )。
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一下,會報錯:
Blocked a frame with origin “http://localhost:10001” from accessing a frame with origin “http://localhost:10000“. Protocols, domains, and ports must match.
避免該錯誤,可以在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值,才能讓該方法處理請求。