Java后端知識點匯總——Java基礎專題
全套Java知識點匯總目錄,見https://www.cnblogs.com/ying-dong/p/11831922.html
1、解釋下什么是面向對象?面向對象和面向過程的區別?
面向對象是一種基於面向過程的編程思想,是向現實世界模型的自然延伸,這是一種“萬物皆對象”的編程思想。由執行者變為指揮者,在現實生活中的任何物體都可以歸為一類事物,而每一個個體都是一類事物的實例。面向對象的編程是以對象為中心,以消息為驅動。
區別: (1)編程思路不同:面向過程以實現功能的函數開發為主,而面向對象要首先抽象出類、屬性及其方法,然后通過實例化類、執行方法來完成功能。
(2)封裝性:都具有封裝性,但是面向過程是封裝的是功能,而面向對象封裝的是數據和功能。
(3)面向對象具有繼承性和多態性,而面向過程沒有繼承性和多態性,所以面向對象優勢很明顯。
2、面向對象的三大特性?分別解釋下?
(1)封裝:通常認為封裝是把數據和操作數據的方法封裝起來,對數據的訪問只能通過已定義的接口。
(2)繼承:繼承是從已有類得到繼承信息創建新類的過程。提供繼承信息的類被稱為父類(超類/基類),得到繼承信息的被稱為子類(派生類)。
(3)多態:分為編譯時多態(方法重載)和運行時多態(方法重寫)。要實現多態需要做兩件事:一是子類繼承父類並重寫父類中的方法,二是用父類型引用子類型對象,這樣同樣的引用調用同樣的方法 就會根據子類對象的不同而表現出不同的行為。
關於繼承的幾點補充:
(1)子類擁有父類對象所有的屬性和方法(包括私有屬性和私有方法),但是父類中的私有屬性和方法子類是無法訪問,只是擁有。因為在一個子類被創建的時候,首先會在內存中創建一個父類對象,然后在父類對象外部放上子類獨有的屬性,兩者合起來形成一個子類的對象;
(2)子類可以擁有自己屬性和方法;
(3)子類可以用自己的方式實現父類的方法。(重寫)
3、JDK、JRE、JVM 三者之間的關系?JDK(Java Development Kit):是 Java 開發工具包,是整個 Java 的核心,包括了 Java 運行環境 JRE、Java 工具和 Java 基礎類庫。JRE( Java Runtime Environment):是 Java 的運行環境,包含 JVM 標准實現及 Java 核心類庫。JVM(Java Virtual Machine):是 Java 虛擬機,是整個 Java 實現跨平台的最核心的部分,能夠運行以 Java 語言寫作的軟件程序。所有的 Java 程序會首先被編譯為 .class 的類文件,這種類文件可以在虛擬機上執行。
4、重載和重寫的區別?
(1)重載:編譯時多態、同一個類中同名的方法具有不同的參數列表、不能根據返回類型進行區分【因為:函數調用時不能指定類型信息,編譯器不知道你要調哪個函數】;
(2)重寫(又名覆蓋):運行時多態、子類與父類之間、子類重寫父類的方法具有相同的返回類型、更好的訪問權限。
5、Java 中是否可以重寫一個 private 或者 static 方法?
Java 中 static 方法不能被覆蓋,因為方法覆蓋是基於運行時動態綁定的,而 static 方法是編譯時靜態綁定的。static 方法跟類的任何實例都不相關,所以概念上不適用。 Java 中也不可以覆蓋 private 的方法,因為 private 修飾的變量和方法只能在當前類中使用, 如果是其他的類繼承當前類是不能訪問到 private 變量或方法的,當然也不能覆蓋。
靜態方法的補充
靜態的方法可以被繼承,但是不能重寫。如果父類和子類中存在同樣名稱和參數的靜態方法,那么該子類的方法會把原來繼承過來的父類的方法隱藏,而不是重寫。通俗的講就是父類的方法和子類的方法是兩個沒有關系的方法,具體調用哪一個方法是看是哪個對象的引用;這種父子類方法也不在存在多態的性質。
6、構造器是否可以被重寫?
在講繼承的時候我們就知道父類的私有屬性和構造方法並不能被繼承,所以 Constructor 也就不能被 Override(重寫),但是可以 Overload(重載),所以你可以看到一個類中有多個構造函數的情況。
7、構造方法有哪些特性?
(1)名字與類名相同;(2)沒有返回值,但不能用 void 聲明構造函數;
(3)成類的對象時自動執行,無需調用。
8、在 Java 中定義一個不做事且沒有參數的構造方法有什么作用?
Java 程序在執行子類的構造方法之前,如果沒有用 super() 來調用父類特定的構造方法,則會調用父類中“沒有參數的構造方法”。因此,如果父類中只定義了有參數的構造方法,而在子類的構造方法中又沒有用 super() 來調用父類中特定的構造方法,則編譯時將發生錯誤,因為 Java 程序在父類中找不到沒有參數的構造方法可供執行。解決辦法是:在父類里加上一個不做事且沒有參數的構造方法。
9、Java 中創建對象的幾種方式?1、使用 new 關鍵字;2、使用 Class 類的 newInstance 方法,該方法調用無參的構造器創建對象(反射):Class.forName.newInstance();3、使用 clone() 方法;4、反序列化,比如調用 ObjectInputStream 類的 readObject() 方法。
10、抽象類和接口有什么區別?
(1)抽象類中可以定義構造函數,接口不能定義構造函數;
(2)抽象類中可以有抽象方法和具體方法,而接口中只能有抽象方法(public abstract);
(3)抽象類中的成員權限可以是 public、默認、protected(抽象類中抽象方法就是為了重寫,所以不能被 private 修飾),而接口中的成員只可以是 public(方法默認:public abstrat、成員變量默認:public static final);
(4)抽象類中可以包含靜態方法,而接口中不可以包含靜態方法;
JDK8中的改變:
1、在 JDK1.8中,允許在接口中包含帶有具體實現的方法,使用 default 修飾,這類方法就是默認方法。
2、抽象類中可以包含靜態方法,在 JDK1.8 之前接口中不能包含靜態方法,JDK1.8 以后可以包含。之前不能包含是因為,接口不可以實現方法,只可以定義方法,所以不能使用靜態方法(因為靜態方法必須實現)。現在可以包含了,只能直接用接口調用靜態方法。JDK1.8 仍然不可以包含靜態代碼塊。
11、靜態變量和實例變量的區別?
靜態變量:是被 static 修飾的變量,也稱為類變量,它屬於類,因此不管創建多少個對象,靜態變量在內存中有且僅有一個拷貝;靜態變量可以實現讓多個對象共享內存。
實例變量:屬於某一實例,需要先創建對象,然后通過對象才能訪問到它。
12、short s1 = 1;s1 = s1 + 1;有什么錯?那么 short s1 = 1; s1 += 1;呢?有沒有錯誤?
對於 short s1 = 1; s1 = s1 + 1; 來說,在 s1 + 1 運算時會自動提升表達式的類型為 int ,那么將 int 型值賦值給 short 型變量,s1 會出現類型轉換錯誤。對於 short s1 = 1; s1 += 1; 來說,+= 是 Java 語言規定的運算符,Java 編譯器會對它進行特殊處理,因此可以正確編譯。
13、Integer 和 int 的區別?
(1)int 是 Java 的八種基本數據類型之一,而 Integer 是 Java 為 int 類型提供的封裝類;
(2)int 型變量的默認值是 0,Integer 變量的默認值是 null,這一點說明 Integer 可以區分出未賦值和值為 0 的區分;
(3)Integer 變量必須實例化后才可以使用,而 int 不需要。
關於 Integer 和 int 的比較的延伸:
1、由於 Integer 變量實際上是對一個 Integer 對象的引用,所以兩個通過 new 生成的 Integer 變量永遠是不相等的,因為其內存地址是不同的;
2、Integer 變量和 int 變量比較時,只要兩個變量的值是相等的,則結果為 true。因為包裝類 Integer 和基本數據類型 int 類型進行比較時,Java 會自動拆包裝類為 int,然后進行比較,實際上就是兩個 int 型變量在進行比較;
3、非 new 生成的 Integer 變量和 new Integer() 生成的變量進行比較時,結果為 false。因為非 new 生成的 Integer 變量指向的是 Java 常量池中的對象,而 new Integer() 生成的變量指向堆中新建的對象,兩者在內存中的地址不同;
4、對於兩個非 new 生成的 Integer 對象進行比較時,如果兩個變量的值在區間 [-128, 127] 之間,則比較結果為 true,否則為 false。Java 在編譯 Integer i = 100 時,會編譯成 Integer i = Integer.valueOf(100),而 Integer 類型的 valueOf 的源碼如下所示:
public static Integer valueOf(int var0) {
return var0 >= -128 && var0 <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
}
從上面的代碼中可以看出:Java 對於 [-128, 127] 之間的數會進行緩存,比如:Integer i = 127,會將 127 進行緩存,下次再寫 Integer j = 127 的時候,就會直接從緩存中取出,而對於這個區間之外的數就需要 new 了。
- 包裝類的緩存:
Boolean:全部緩存
Byte:全部緩存
Character:<= 127 緩存
Short:-128 — 127 緩存
Long:-128 — 127 緩存
Integer:-128 — 127 緩存
Float:沒有緩存
Doulbe:沒有緩存
14、裝箱和拆箱
自動裝箱是 Java 編譯器在基本數據類型和對應得包裝類之間做的一個轉化。比如:把 int 轉化成 Integer,double 轉化成 Double 等等。反之就是自動拆箱。
原始類型:boolean、char、byte、short、int、long、float、double
封裝類型:Boolean、Character、Byte、Short、Integer、Long、Float、Double
15、switch 語句能否作用在 byte 上,能否作用在 long 上,能否作用在 String 上?
在 switch(expr 1) 中,expr1 只能是一個整數表達式或者枚舉常量。而整數表達式可以是 int 基本數據類型或者 Integer 包裝類型。由於,byte、short、char 都可以隱式轉換為 int,所以,這些類型以及這些類型的包裝類型也都是可以的。而 long 和 String 類型都不符合 switch 的語法規定,並且不能被隱式的轉換為 int 類型,所以,它們不能作用於 switch 語句中。不過,需要注意的是在 JDK1.7 版本之后 switch 就可以作用在 String 上了。
16、字節和字符的區別?
字節是存儲容量的基本單位;字符是數字、字母、漢字以及其他語言的各種符號;1 字節 = 8 個二進制單位,一個字符由一個字節或多個字節的二進制單位組成。
17、String 為什么要設計為不可變類?
在 Java 中將 String 設計成不可變的是綜合考慮到各種因素的結果。主要的原因主要有以下三點:
(1)字符串常量池的需要:字符串常量池是 Java 堆內存中一個特殊的存儲區域, 當創建一個 String 對象時,假如此字符串值已經存在於常量池中,則不會創建一個新的對象,而是引用已經存在的對象;
(2)允許 String 對象緩存 HashCode:Java 中 String 對象的哈希碼被頻繁地使用, 比如在 HashMap 等容器中。字符串不變性保證了 hash 碼的唯一性,因此可以放心地進行緩存。這也是一種性能優化手段,意味着不必每次都去計算新的哈希碼;
(3)String 被許多的 Java 類(庫)用來當做參數,例如:網絡連接地址 URL、文件路徑 path、還有反射機制所需要的 String 參數等, 假若 String 不是固定不變的,將會引起各種安全隱患。
18、String、StringBuilder、StringBuffer 的區別?
String:用於字符串操作,屬於不可變類;【補充:String 不是基本數據類型,是引用類型,底層用 char 數組實現的】
StringBuilder:與 StringBuffer 類似,都是字符串緩沖區,但線程不安全;
StringBuffer:也用於字符串操作,不同之處是 StringBuffer 屬於可變類,對方法加了同步鎖,線程安全。
StringBuffer的補充:
說明:StringBuffer 中並不是所有方法都使用了 Synchronized 修飾來實現同步:
@Override public StringBuffer insert(int dstOffset, CharSequence s) { // Note, synchronization achieved via invocations of other StringBuffer methods // after narrowing of s to specific type // Ditto for toStringCache clearing super.insert(dstOffset, s); return this; }
執行效率:StringBuilder > StringBuffer > String
19、String 字符串修改實現的原理?
當用 String 類型來對字符串進行修改時,其實現方法是首先創建一個 StringBuffer,其次調用 StringBuffer 的 append() 方法,
最后調用 StringBuffer 的 toString() 方法把結果返回。
20、String str = "i" 與 String str = new String("i") 一樣嗎?
不一樣,因為內存的分配方式不一樣。String str = "i" 的方式,Java 虛擬機會將其分配到常量池中;而 String str = new String("i") 則會被分到堆內存中。
public class StringTest { public static void main(String[] args) { String str1 = "abc"; String str2 = "abc"; String str3 = new String("abc"); String str4 = new String("abc"); System.out.println(str1 == str2); // true System.out.println(str1 == str3); // false System.out.println(str3 == str4); // false System.out.println(str3.equals(str4)); // true } }
在執行 String str1 = "abc" 的時候,JVM 會首先檢查字符串常量池中是否已經存在該字符串對象,如果已經存在,那么就不會再創建了,直接返回該字符串在字符串常量池中的內存地址;如果該字符串還不存在字符串常量池中,那么就會在字符串常量池中創建該字符串對象,然后再返回。所以在執行 String str2 = "abc" 的時候,因為字符串常量池中已經存在“abc”字符串對象了,就不會在字符串常量池中再次創建了,所以棧內存中 str1 和 str2 的內存地址都是指向 "abc" 在字符串常量池中的位置,所以 str1 = str2 的運行結果為 true。
而在執行 String str3 = new String("abc") 的時候,JVM 會首先檢查字符串常量池中是否已經存在“abc”字符串,如果已經存在,則不會在字符串常量池中再創建了;如果不存在,則就會在字符串常量池中創建 "abc" 字符串對象,然后再到堆內存中再創建一份字符串對象,把字符串常量池中的 "abc" 字符串內容拷貝到內存中的字符串對象中,然后返回堆內存中該字符串的內存地址,即棧內存中存儲的地址是堆內存中對象的內存地址。String str4 = new String("abc") 是在堆內存中又創建了一個對象,所以 str 3 == str4 運行的結果是 false。str1、str2、str3、str4 在內存中的存儲狀況如下圖所示:
21、String 類的常用方法都有那些?
indexOf():返回指定字符的索引。
charAt():返回指定索引處的字符。
replace():字符串替換。
trim():去除字符串兩端空白。
split():分割字符串,返回一個分割后的字符串數組。
getBytes():返回字符串的 byte 類型數組。
length():返回字符串長度。
toLowerCase():將字符串轉成小寫字母
toUpperCase():將字符串轉成大寫字符。
substring():截取字符串。equals():字符串比較。
22、final 修飾 StringBuffer 后還可以 append 嗎?
可以。final 修飾的是一個引用變量,那么這個引用始終只能指向這個對象,但是這個對象內部的屬性是可以變化的。
官方文檔解釋:
once a final variable has been assigned, it always contains the same value. If a final variable holds a reference to an object, then the state of the object may be changed by operations on the object, but the variable will always refer to the same object.
23、Object 的常用方法有哪些?
clone 方法:用於創建並返回當前對象的一份拷貝;
getClass 方法:用於返回當前運行時對象的 Class;
toString 方法:返回對象的字符串表示形式;
finalize 方法:實例被垃圾回收器回收時觸發的方法;
equals 方法:用於比較兩個對象的內存地址是否相等,一般需要重寫;
hashCode 方法:用於返回對象的哈希值;
notify 方法:喚醒一個在此對象監視器上等待的線程。如果有多個線程在等待只會喚醒一個。
notifyAll 方法:作用跟 notify() 一樣,只不過會喚醒在此對象監視器上等待的所有線程,而不是一個線程。
wait 方法:讓當前對象等待;
.......
24、為什么 wait/notify 方法放在 Object 類中而不是 Thread 類中?
一個很明顯的原因是 Java 提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。如果線程需要等待某些鎖,那么調用對象中的 wait() 方法就有意義了。如果 wait() 方法定義在 Thread 類中,線程正在等待的是哪個鎖就不明顯了。簡單的說,由於 wait,notify 和 notifyAll 都是鎖級別的操作,所以把他們定義在 Object 類中因為鎖屬於對象。
25、final、finally、finalize 的區別?
final:用於聲明屬性、方法和類,分別表示屬性不可變、方法不可覆蓋、被其修飾的類不可繼承;
finally:異常處理語句結構的一部分,表示總是執行;
finallize:Object類的一個方法,在垃圾回收時會調用被回收對象的finalize。
26、finally 塊中的代碼什么時候被執行?
在 Java 語言的異常處理中,finally 塊的作用就是為了保證無論出現什么情況,finally 塊里的代碼一定會被執行。由於程序執行 return 就意味着結束對當前函數的調用並跳出這個函數體,因此任何語句要執行都只能在 return 前執行(除非碰到 exit 函數),因此 finally 塊里的代碼也是在 return 之前執行的。
此外,如果 try-finally 或者 catch-finally 中都有 return,那么 finally 塊中的 return 將會覆蓋別處的 return 語句,最終返回到調用者那里的是 finally 中 return 的值。
27、finally 是不是一定會被執行到?
不一定。下面列舉兩種執行不到的情況:
(1)當程序進入 try 塊之前就出現異常時,會直接結束,不會執行 finally 塊中的代碼;
(2)當程序在 try 塊中強制退出時也不會去執行 finally 塊中的代碼,比如在 try 塊中執行 exit 方法。
28、try-catch-finally 中,如果 catch 中 return 了,finally 還會執行嗎?
會。程序在執行到 return 時會首先將返回值存儲在一個指定的位置,其次去執行 finally 塊,最后再返回。因此,對基本數據類型,在 finally 塊中改變 return 的值沒有任何影響,直接覆蓋掉;而對引用類型是有影響的,返回的是在 finally 對 前面 return 語句返回對象的修改值。
29、try-catch-finally 中那個部分可以省略?catch 可以省略。try 只適合處理運行時異常,try+catch 適合處理運行時異常+普通異常。也就是說,如果你只用 try 去處理普通異常卻不加以 catch 處理,編譯是通不過的,因為編譯器硬性規定,普通異常如果選擇捕獲,則必須用 catch 顯示聲明以便進一步處理。而運行時異常在編譯時沒有如此規定,所以 catch 可以省略,你加上 catch 編譯器也覺得無可厚非。
30、static 關鍵字的作用?
(1)靜態變量:又稱為類變量,也就是說這個變量屬於類的,類所有的實例都共享靜態變量,可以直接通過類名來訪問它。靜態變量在內存中只存在一份;
(2)靜態方法:靜態方法在類加載的時候就存在了,它不依賴於任何實例。所以靜態方法必須有實現,也就是說它不能是抽象方法。只能訪問所屬類的靜態字段和靜態方法,方法中不能有 this 和 super 關鍵字;
(3)靜態語句塊:靜態語句塊在類初始化時運行一次;
(4)靜態內部類:非靜態內部類依賴於外部類的實例,而靜態內部類不需要。靜態內部類不能訪問外部類的非靜態的變量和方法;
(5)初始化順序:靜態變量和靜態語句塊優先於實例變量和普通語句塊,靜態變量和靜態語句塊的初始化順序取決於它們在代碼中的順序。
public static String staticField = "靜態變量"; static { System.out.println("靜態語句塊"); ---- 1 } public String field = "實例變量"; ---- 2 { System.out.println("普通語句塊"); ----3 } // 最后才是構造函數的初始化 public InitialOrderTest() { System.out.println("構造函數"); ----4 }
初始化補充:
存在繼承的情況下,初始化順序為:
1. 父類(靜態變量、靜態語句塊) 2. 子類(靜態變量、靜態語句塊)3. 父類(實例變量、普通語句塊)4. 父類(構造函數)5. 子類(實例變量、普通語句塊)6. 子類(構造函數)
31、super 關鍵字的作用?
(1)訪問父類的構造函數:可以使用 super() 函數訪問父類的構造函數,從而委托父類完成一些初始化的工作。
(2)訪問父類的成員:如果子類重寫了父類的某個方法,可以通過使用 super 關鍵字來引用父類的方法實現。
(3)this 和 super 不能同時出現在一個構造函數里面,因為 this 必然會調用其它的構造函數,其它的構造函數必然也會有 super 語句的存在,所以在同一個構造函數里面有相同的語句,就失去了語句的意義,編譯器也不會通過。
32、transient 關鍵字的作用?
對於不想進行序列化的變量,使用 transient 關鍵字修飾。transient 關鍵字的作用是:阻止實例中那些用此關鍵字修飾的的變量序列化。當對象被反序列化時,被 transient 修飾的變量值不會被持久化和恢復。transient 只能修飾變量,不能修飾類和方法。
33、== 和 equals 的區別?
==:如果比較的對象是基本數據類型,則比較的是數值是否相等;如果比較的是引用數據類型,則比較的是對象的地址值是否相等。
equals 方法:用來比較兩個對象的內容是否相等。注意:equals 方法不能用於比較基本數據類型的變量。如果沒有對 equals 方法進行重寫,則比較的是引用類型的變量所指向的對象的地址(很多類重新了 equals 方法,比如 String、Integer 等把它變成了值比較,所以一般情況下 equals 比較的是值是否相等)。
34、兩個對象的 hashCode() 相同,則 equals() 也一定為 true 嗎?
兩個對象的 hashCode() 相同,equals() 不一定為 true。因為在散列表中,hashCode() 相等即兩個鍵值對的哈希值相等,然而哈希值相等,並不一定能得出鍵值對相等【散列沖突】。
35、為什么重寫 equals() 就一定要重寫 hashCode() 方法?
這個問題應該是有個前提,就是你需要用到 HashMap、HashSet 等 Java 集合,用不到哈希表的話,其實僅僅重寫 equals() 方法也可以。而工作中的場景是常常用到 Java 集合,所以 Java 官方建議重寫 equals() 就一定要重寫 hashCode() 方法。
對於對象集合的判重,如果一個集合含有 10000 個對象實例,僅僅使用 equals() 方法的話,那么對於一個對象判重就需要比較 10000 次,隨着集合規模的增大,時間開銷是很大的。但是同時使用哈希表的話,就能快速定位到對象的大概存儲位置,並且在定位到大概存儲位置后,后續比較過程中,如果兩個對象的 hashCode 不相同,也不再需要調用 equals() 方法,從而大大減少了 equals() 比較次數。
所以從程序實現原理上來講的話,既需要 equals() 方法,也需要 hashCode() 方法。那么既然重寫了 equals(),那么也要重寫 hashCode() 方法,以保證兩者之間的配合關系。
hashCode()與equals()的相關規定:
1、如果兩個對象相等,則 hashCode 一定也是相同的;2、兩個對象相等,對兩個對象分別調用 equals 方法都返回 true;3、兩個對象有相同的 hashCode 值,它們也不一定是相等的;4、因此,equals 方法被覆蓋過,則 hashCode 方法也必須被覆蓋;5、hashCode() 的默認行為是對堆上的對象產生獨特值。如果沒有重寫 hashCode(),則該 class 的兩個對象無論如何都不會相等(即使這兩個對象指向相同的數據)。
36、& 和 && 的區別?
Java 中 && 和 & 都是表示與的邏輯運算符,都表示邏輯運輸符 and,當兩邊的表達式都為 true 的時候,整個運算結果才為 true,否則為 false。
&&:有短路功能,當第一個表達式的值為 false 的時候,則不再計算第二個表達式;
&:不管第一個表達式結果是否為 true,第二個都會執行。除此之外,& 還可以用作位運算符:當 & 兩邊的表達式不是 Boolean 類型的時候,& 表示按位操作。
37、Java 中的參數傳遞時傳值呢?還是傳引用?
Java 的參數是以值傳遞的形式傳入方法中,而不是引用傳遞。當傳遞方法參數類型為基本數據類型(數字以及布爾值)時,一個方法是不可能修改一個基本數據類型的參數。當傳遞方法參數類型為引用數據類型時,一個方法將修改一個引用數據類型的參數所指向對象的值。即使 Java 函數在傳遞引用數據類型時,也只是拷貝了引用的值罷了,之所以能修改引用數據是因為它們同時指向了一個對象,但這仍然是按值調用而不是引用調用。
38、Java 中的 Math.round(-1.5) 等於多少?
等於 -1,因為在數軸上取值時,中間值(0.5)向右取整,所以正 0.5 是往上取整,負 0.5 是直接舍棄。
39、兩個二進制數的異或結果是什么?
兩個二進制數異或結果是這兩個二進制數差的絕對值。表達式如下:a^b = |a-b|。
兩個二進制 a 與 b 異或,即 a 和 b 兩個數按位進行運算。如果對應的位相同,則為 0(相當於對應的算術相減),如果不同即為 1(相當於對應的算術相加)。由於二進制每個位只有兩種狀態,要么是 0,要么是 1,則按位異或操作可表達為按位相減取值相對值,再按位累加。
40、Error 和 Exception 的區別?
Error 類和 Exception 類的父類都是 Throwable 類。主要區別如下:
Error 類: 一般是指與虛擬機相關的問題,如:系統崩潰、虛擬機錯誤、內存空間不足、方法調用棧溢出等。這類錯誤將會導致應用程序中斷,僅靠程序本身無法恢復和預防;
Exception 類:分為運行時異常和受檢查的異常。
41、運行時異常與受檢異常有何異同?
運行時異常:如:空指針異常、指定的類找不到、數組越界、方法傳遞參數錯誤、數據類型轉換錯誤。可以編譯通過,但是一運行就停止了,程序不會自己處理;受檢查異常:要么用 try … catch… 捕獲,要么用 throws 聲明拋出,交給父類處理。
42、throw 和 throws 的區別?(1)throw:在方法體內部,表示拋出異常,由方法體內部的語句處理;throw 是具體向外拋出異常的動作,所以它拋出的是一個異常實例;(2)throws:在方法聲明后面,表示如果拋出異常,由該方法的調用者來進行異常的處理;表示出現異常的可能性,並不一定會發生這種異常。
43、常見的異常類有哪些?
NullPointerException:當應用程序試圖訪問空對象時,則拋出該異常。SQLException:提供關於數據庫訪問錯誤或其他錯誤信息的異常。
IndexOutOfBoundsException:指示某排序索引(例如對數組、字符串或向量的排序)超出范圍時拋出。
FileNotFoundException:當試圖打開指定路徑名表示的文件失敗時,拋出此異常。
IOException:當發生某種 I/O 異常時,拋出此異常。此類是失敗或中斷的 I/O 操作生成的異常的通用類。
ClassCastException:當試圖將對象強制轉換為不是實例的子類時,拋出該異常。IllegalArgumentException:拋出的異常表明向方法傳遞了一個不合法或不正確的參數。
44、主線程可以捕獲到子線程的異常嗎?
線程設計的理念:“線程的問題應該線程自己本身來解決,而不要委托到外部”。
正常情況下,如果不做特殊的處理,在主線程中是不能夠捕獲到子線程中的異常的。如果想要在主線程中捕獲子線程的異常,我們可以用如下的方式進行處理,使用 Thread 的靜態方法。
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandle());
45、Java 的泛型是如何工作的 ? 什么是類型擦除 ?
泛型是通過類型擦除來實現的,編譯器在編譯時擦除了所有類型相關的信息,所以在運行時不存在任何類型相關的信息。例如:List<String> 在運行時僅用一個 List 來表示。這樣做的目的,是確保能和 Java 5 之前的版本開發二進制類庫進行兼容。
類型擦除:泛型信息只存在於代碼編譯階段,在進入 JVM 之前,與泛型相關的信息會被擦除掉,專業術語叫做類型擦除。在泛型類被類型擦除的時候,之前泛型類中的類型參數部分如果沒有指定上限,如 <T> 則會被轉譯成普通的 Object 類型,如果指定了上限如 <T extends String> 則類型參數就被替換成類型上限。
補充:
List<String> list = new ArrayList<String>();
1、兩個 String 其實只有第一個起作用,后面一個沒什么卵用,只不過 JDK7 才開始支持 List<String>list = new ArrayList<> 這種寫法。
2、第一個 String 就是告訴編譯器,List 中存儲的是 String 對象,也就是起類型檢查的作用,之后編譯器會擦除泛型占位符,以保證兼容以前的代碼。
46、什么是泛型中的限定通配符和非限定通配符 ?
限定通配符對類型進行了限制。有兩種限定通配符,一種是<? extends T> 它通過確保類型必須是 T 的子類來設定類型的上界,另一種是<? super T>它通過確保類型必須是 T 的父類來設定類型的下界。泛型類型必須用限定內的類型來進行初始化,否則會導致編譯錯誤。另一方面 <?> 表示了非限定通配符,因為 <?> 可以用任意類型來替代。
47、List<? extends T> 和 List <? super T> 之間有什么區別 ?
這兩個 List 的聲明都是限定通配符的例子,List<? extends T> 可以接受任何繼承自 T 的類型的 List,而List <? super T> 可以接受任何 T 的父類構成的 List。例如 List<? extends Number> 可以接受 List<Integer> 或 List<Float>。
補充:
Array 不支持泛型,要用 List 代替 Array,因為 List 可以提供編譯器的類型安全保證,而 Array卻不能。
48、如何實現對象的克隆?
(1)實現 Cloneable 接口並重寫 Object 類中的 clone() 方法;(2)實現 Serializable 接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深克隆。
49、深克隆和淺克隆的區別?
(1)淺克隆:拷貝對象和原始對象的引用類型引用同一個對象。淺克隆只是復制了對象的引用地址,兩個對象指向同一個內存地址,所以修改其中任意的值,另一個值都會隨之變化,這就是淺克隆。(2)深克隆:拷貝對象和原始對象的引用類型引用不同對象。深拷貝是將對象及值復制過來,兩個對象修改其中任意的值另一個值不會改變,這就是深拷貝(例:JSON.parse() 和 JSON.stringify(),但是此方法無法復制函數類型)。
克隆的補充:
深克隆的實現就是在引用類型所在的類實現 Cloneable 接口,並使用 public 訪問修飾符重寫 clone 方法。Java 中定義的 clone 沒有深淺之分,都是統一的調用 Object 的 clone 方法。為什么會有深克隆的概念?是由於我們在實現的過程中刻意的嵌套了 clone 方法的調用。也就是說深克隆就是在需要克隆的對象類型的類中重新實現克隆方法 clone()。
50、什么是 Java 的序列化,如何實現 Java 的序列化?
對象序列化是一個用於將對象狀態轉換為字節流的過程,可以將其保存到磁盤文件中或通過網絡發送到任何其他程序。從字節流創建對象的相反的過程稱為反序列化。而創建的字節流是與平台無關的,在一個平台上序列化的對象可以在不同的平台上反序列化。序列化是為了解決在對象流進行讀寫操作時所引發的問題。
序列化的實現:將需要被序列化的類實現 Serializable 接口,該接口沒有需要實現的方法,只是用於標注該對象是可被序列化的,然后使用一個輸出流(如:FileOutputStream)來構造一個 ObjectOutputStream 對象,接着使用 ObjectOutputStream 對象的 writeObject(Object obj) 方法可以將參數為 obj 的對象寫出,要恢復的話則使用輸入流。
51、什么情況下需要序列化?
(1)當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;
(2)當你想用套接字在網絡上傳送對象的時候;(3)當你想通過 RMI 傳輸對象的時候。
52、Java 中的反射是什么意思?有哪些應用場景?
每個類都有一個 Class 對象,包含了與類有關的信息。當編譯一個新類時,會產生一個同名的 .class 文件,該文件內容保存着 Class 對象。類加載相當於 Class 對象的加載,類在第一次使用時才動態加載到 JVM 中。也可以使用 Class.forName("com.mysql.jdbc.Driver") 這種方式來控制類的加載,該方法會返回一個 Class 對象。
反射可以提供運行時的類信息,並且這個類可以在運行時才加載進來,甚至在編譯時期該類的 .class 不存在也可以加載進來。Class 和 java.lang.reflect 一起對反射提供了支持,java.lang.reflect 類庫主要包含了以下三個類:
(1)Field :可以使用 get() 和 set() 方法讀取和修改 Field 對象關聯的字段;
(2)Method :可以使用 invoke() 方法調用與 Method 對象關聯的方法;
(3)Constructor :可以用 Constructor 創建新的對象。
應用舉例:工廠模式,使用反射機制,根據全限定類名獲得某個類的 Class 實例。
53、反射的優缺點?
優點:
運行期類型的判斷,class.forName() 動態加載類,提高代碼的靈活度;
缺點:盡管反射非常強大,但也不能濫用。如果一個功能可以不用反射完成,那么最好就不用。在我們使用反射技術時,下面幾條內容應該牢記於心。(1)性能開銷 :反射涉及了動態類型的解析,所以 JVM 無法對這些代碼進行優化。因此,反射操作的效率要比那些非反射操作低得多。我們應該避免在經常被執行的代碼或對性能要求很高的程序中使用反射。
(2)安全限制 :使用反射技術要求程序必須在一個沒有安全限制的環境中運行。如果一個程序必須在有安全限制的環境中運行,如 Applet,那么這就是個問題了。
(3)內部暴露:由於反射允許代碼執行一些在正常情況下不被允許的操作(比如:訪問私有的屬性和方法),所以使用反射可能會導致意料之外的副作用,這可能導致代碼功能失調並破壞可移植性。反射代碼破壞了抽象性,因此當平台發生改變的時候,代碼的行為就有可能也隨着變化。
54、Java 中的動態代理是什么?有哪些應用?
動態代理:當想要給實現了某個接口的類中的方法,加一些額外的處理。比如說加日志,加事務等。可以給這個類創建一個代理,故名思議就是創建一個新的類,這個類不僅包含原來類方法的功能,而且還在原來的基礎上添加了額外處理的新功能。這個代理類並不是定義好的,是動態生成的。具有解耦意義,靈活,擴展性強。動態代理的應用:Spring 的 AOP 、加事務、加權限、加日志。
55、怎么實現動態代理?
首先必須定義一個接口,還要有一個 InvocationHandler(將實現接口的類的對象傳遞給它)處理類。再有一個工具類 Proxy(習慣性將其稱為代理類,因為調用它的 newInstance() 可以產生代理對象,其實它只是一個產生代理對象的工具類)。利用到 InvocationHandler,拼接代理類源碼,將其編譯生成代理類的二進制碼,利用加載器加載,並將其實例化產生代理對象,最后返回。
每一個動態代理類都必須要實現 InvocationHandler 這個接口,並且每個代理類的實例都關聯到了一個 handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由 InvocationHandler 這個接口的 invoke 方法來進行調用。我們來看看 InvocationHandler 這個接口的唯一一個方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代我們所代理的那個真實對象method: 指代的是我們所要調用真實對象的某個方法的 Method 對象args: 指代的是調用真實對象某個方法時接受的參數Proxy 類的作用是動態創建一個代理對象的類。它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException
loader:一個 ClassLoader 對象,定義了由哪個 ClassLoader 對象來對生成的代理對象進行加載;interfaces:一個 Interface 對象的數組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了 handler:一個 InvocationHandler 對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個 InvocationHandler 對象上。
通過 Proxy.newProxyInstance 創建的代理對象是在 Jvm 運行時動態生成的一個對象,它並不是我們的 InvocationHandler 類型,也不是我們定義的那組接口的類型,而是在運行是動態生成的一個對象。
56、Java 中的 IO 流的分類?說出幾個你熟悉的實現類?
按功能來分:輸入流(input)、輸出流(output)。按類型來分:字節流 和 字符流。
字節流:InputStream/OutputStream 是字節流的抽象類,這兩個抽象類又派生了若干子類,不同的子類分別處理不同的操作類型。具體子類如下所示:
字符流:Reader/Writer 是字符的抽象類,這兩個抽象類也派生了若干子類,不同的子類分別處理不同的操作類型。
57、字節流和字符流有什么區別?
字節流按 8 位傳輸,以字節為單位輸入輸出數據,字符流按 16 位傳輸,以字符為單位輸入輸出數據。
但是不管文件讀寫還是網絡發送接收,信息的最小存儲單元都是字節。
58、BIO、NIO、AIO 有什么區別?
BIO:Block IO 同步阻塞式 IO,就是我們平常使用的傳統 IO,它的特點是模式簡單使用方便,並發處理能力低。同步阻塞I/O模式,數據的讀取寫入必須阻塞在一個線程內等待其完成。在活動連接數不是特別高(小於單機 1000)的情況下,這種模型是比較不錯的,可以讓每一個連接專注於自己的 I/O 並且編程模型簡單,也不用過多考慮系統的過載、限流等問題。線程池本身就是一個天然的漏斗,可以緩沖一些系統處理不了的連接或請求。但是,當面對十萬甚至百萬級連接的時候,傳統的 BIO 模型是無能為力的。因此,我們需要一種更高效的 I/O 處理模型來應對更高的並發量。NIO:New IO 同步非阻塞 IO,是傳統 IO 的升級,客戶端和服務器端通過 Channel(通道)通訊,實現了多路復用。NIO 是一種同步非阻塞的 I/O 模型,在 Java1.4 中引入了 NIO 框架,對應 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解為 Non-blocking,不單純是 New。它支持面向緩沖的,基於通道的 I/O 操作方法。NIO 提供了與傳統BIO模型中的 Socket 和 ServerSocket 相對應的 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實現,兩種通道都支持阻塞和非阻塞兩種模式。阻塞模式使用就像傳統中的支持一樣,比較簡單,但是性能和可靠性都不好;非阻塞模式正好與之相反。對於低負載、低並發的應用程序,可以使用同步阻塞 I/O 來提升開發速率和更好的維護性;對於高負載、高並發的(網絡)應用,應使用 NIO 的非阻塞模式來開發。
AIO:Asynchronous IO 是 NIO 的升級,也叫 NIO2,實現了異步非堵塞 IO ,異步 IO 的操作基於事件和回調機制。也就是應用操作之后會直接返回,不會堵塞在那里,當后台處理完成,操作系統會通知相應的線程進行后續的操作。AIO 是異步 IO 的縮寫,雖然 NIO 在網絡操作中,提供了非阻塞的方法,但是 NIO 的 IO 行為還是同步的。對於 NIO 來說,我們的業務線程是在 IO 操作准備好時,得到通知,接着就由這個線程自行進行 IO 操作,IO操作本身是同步的。