JVM 、JDK 和JRE的區(qū)別
JVM (java 虛擬機)是運行java 字節(jié)碼的虛擬機,不同的系統(tǒng)有特定的 JVM 實現,比如常見的 HotSpot VM 就是 JVM 的一種實現,除此之外還有 J9 VM (常用于 IBM 硬件平臺)等。
JRE 是 java 運行時環(huán)境,包括 JVM 、java 庫、java 命令和其他一些基礎構件,但是 JRE 不能用于創(chuàng)建新程序。
JDK 是功能齊全的 java SDK,包括 JRE、編譯器(javac)和一些工具(如 javadoc 和jdb,能夠創(chuàng)建和編譯程序。
靜態(tài)方法為什么不能調用非靜態(tài)成員?
靜態(tài)方法在類加載的時候就會分配內存,可以通過類名去訪問,而非靜態(tài)成員只有在對象實例化之后才存在,且需要通過類的實例對象去訪問。因此在非靜態(tài)成員不存在的時候,靜態(tài)方法就已經存在了,此時靜態(tài)方法調用還不存在的非靜態(tài)成員是非法的。
重載與重寫的區(qū)別
重載:相同的函數名,參數列表必須不同(返回值類型可以不同)(發(fā)生在編譯器)
重寫:方法名、參數列表必須相同(子類方法的返回值類型應比父類方法返回值類型更小或者相等,如果父類方法返回值類型是 void 或者基本數據類型則子類方法返回值類型不可更改;拋出的異常范圍小于等于父類;訪問修飾符范圍大于等于父類)(private / final / static 修飾的方法不能被重寫,構造方法無法被重寫可以重載)(發(fā)生在運行期)
== 和equals() 的區(qū)別
== 對于基本數據類型來說,比較的是變量的值;對于引用數據類型來說比較的是對象的內存地址。
equals() 不能用來判斷基本數據類型變量,只能用于判斷兩個對象是否相等。如果沒有重寫equals() 則 equals()相當于==,比較的是對象的內存地址,如果重寫了 equals() 則可以比較對象的屬性,即屬性相等則認為兩個對象相等。
為什么重寫 equals() 必須重寫 hashCode()
兩個對象的 hashCode 相同,并且equals() 返回 True ,我們才認為這兩個對象是相等。equals() 返回 True 而 hashCode 并不一定相等,因此需要重寫 hashCode()。
而且在 HashMap 中,判斷是否有重復元素時,是先通過 hashCode 計算元素位置,再通過 equals() 來判斷元素是否相等的。
java 中的基本數據類型
byte 、 short 、 int 、 long 、 float 、 double 、char 、 boolean
基本數據類型的線程安全
8種基本數據類型中 long 類型和 double 類型是64位的,因此在32位的系統(tǒng)下讀寫操作是分兩次的,因此不是線程安全的。如果硬件,操作系統(tǒng),JVM 都是64位的,則單次可以操作64位的數據,讀寫應該是原子性的。
包裝類型的常量池技術
Byte, Short, Integer, Long 這 4 種包裝類默認創(chuàng)建了數值 [-128,127] 的相應類型的緩存數據,Character 創(chuàng)建了數值在 [0,127] 范圍的緩存數據,Boolean 直接返回 True or False。兩種浮點類型的包裝類 Float 和 Double 沒有實現常量池技術。
自動裝箱與拆箱
裝箱:將基本類型用對應的引用類型包裝起來
Integer i=10;//等價于Integer i=Integer.valueOf(10);
拆箱:將包裝類型轉換為基本數據類型
int n=i;//等價于int n=i.intValue();
在進行比較前會自動拆箱。
class AutoUnboxingTest {
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; // 將3自動裝箱成Integer類型
int c = 3;
System.out.println(a == b); // false 兩個引用沒有引用同一對象
System.out.println(a == c); // true a自動拆箱成int類型再和c比較
}
}
面向對象三大特征
封裝:封裝是指把一個對象的狀態(tài)信息隱藏在對象內部,不允許外界直接訪問,只提供一些可以被外界訪問的方法來操作對象屬性。
繼承:用已經存在的類作為基礎來創(chuàng)建新類,新類可以增加新的屬性和方法,也可以用父類的功能,但不能選擇性地繼承父類。繼承提高了代碼的可重用性和可維護性,節(jié)省了創(chuàng)建新類的時間,提高了開發(fā)效率。
多態(tài):多態(tài)具體表現為父類引用指向子類實例。
對象類型和引用類型之間具有繼承(類)/ 實現(接口)的關系;
引用類型變量發(fā)出的方法調用的到底是哪個類中的方法,必須在程序運行期才能確定;
多態(tài)不能調用只在子類存在但在父類不存在的方法;
如果子類重寫了父類的方法,真正執(zhí)行的是子類的方法,否則執(zhí)行的是父類的方法。
java 中的權限修飾符
修飾符修飾方法或屬性:
public:允許跨類和跨包訪問
default:只允許在同一個包中跨類
protected:只允許在類或者子類中訪問,子類可以在不同包中
private:只允許在本類中訪問
注意:protected和private不能用來修飾一般的類,否則會報編譯器不允許的錯誤,內部類除外。
補充:想要這個類的屬性和方法可以被任何子類繼承,我就用protected。想要這個類的屬性和方法不能被任何子類繼承,我就用private。同理,想要這個類被繼承,我就用abstract。我不想這個類被繼承,就用final。
https://www.cnblogs.com/AleiCui/p/12792565.html
接口和抽象類的區(qū)別
1、接口的意義在于可以對行為進行抽象,而抽象類是對事物進行抽象;一個類實現了接口,則說明這個類擁有了這個接口中的所有行為,如果接口中改變了行為,則這個類也要改變。如果一個類繼承了抽象類,則這個類就可以使用抽象類中的方法,并可以重新去實現抽象類中的抽象方法,如果抽象類中改變了非抽象方法,這個類不用做其他改變。
2、抽象類中可以有靜態(tài)變量,實例變量,而接口中必須是final類型修飾的靜態(tài)變量
3、抽象類中可以抽象方法也可以有非抽象方法,接口中只能有抽象方法
4、抽象類中有構造函數,而接口中沒有構造函數
5、抽象類中可以有代碼塊,接口中不能有代碼塊
6、抽象類只能被單繼承,接口可以被多實現。
注意:雖然抽象類中有構造函數,但是抽象類是不能直接實例化的。只有在子類繼承了這個抽象類之后,子類實例化的時候,才會調用(父類)抽象類中的構造函數。
深拷貝和淺拷貝
淺拷貝:復制一個對象時,創(chuàng)建了一個新的對象,對于原對象里的基本數據類型,將其值拷貝進來,對于原對象里的引用數據類型,將其引用拷貝進來。這樣如果原對象更改了引用對象,會對拷貝對象產生影響。
深拷貝:對于原對象里的引用對象,不是拷貝引用,而是創(chuàng)建一個新的對象,這樣原對象更改了引用對象,也不會對拷貝對象產生影響。
引用拷貝:兩個不同的引用指向同一個對象
通過實現 cloneable 接口,重寫 clone() 方法來實現淺拷貝和深拷貝。
實現深拷貝的兩種方式
1)實現 Cloneable 接口,重寫 Object 類中的 clone() ,通過層層克隆來實現深拷貝。
2)通過序列化(都要實現 Serializable 接口)的方法,將對象寫入到流中,再從流中讀取出來。這種方式雖然效率低下,但是可以實現真正意義上的深度克隆。
https://juejin.cn/post/6982209049416859678 "通過序列化和反序列化實現深拷貝"
object 類常見的方法有哪些
public final native Class<?> getClass()//native方法,用于返回當前運行時對象的Class對象,使用了final關鍵字修飾,故不允許子類重寫。
public native int hashCode() //native方法,用于返回對象的哈希碼,主要使用在哈希表中,比如JDK中的HashMap。
public boolean equals(Object obj)//用于比較2個對象的內存地址是否相等,String類對該方法進行了重寫用戶比較字符串的值是否相等。
protected native Object clone() throws CloneNotSupportedException//naitive方法,用于創(chuàng)建并返回當前對象的一份拷貝。一般情況下,對于任何對象 x,表達式 x.clone() != x 為true,x.clone().getClass() == x.getClass() 為true。Object本身沒有實現Cloneable接口,所以不重寫clone方法并且進行調用的話會發(fā)生CloneNotSupportedException異常。
public String toString()//返回類的名字@實例的哈希碼的16進制的字符串。建議Object所有的子類都重寫這個方法。
public final native void notify()//native方法,并且不能重寫。喚醒一個在此對象監(jiān)視器上等待的線程(監(jiān)視器相當于就是鎖的概念)。如果有多個線程在等待只會任意喚醒一個。
public final native void notifyAll()//native方法,并且不能重寫。跟notify一樣,唯一的區(qū)別就是會喚醒在此對象監(jiān)視器上等待的所有線程,而不是一個線程。
public final native void wait(long timeout) throws InterruptedException//native方法,并且不能重寫。暫停線程的執(zhí)行。注意:sleep方法沒有釋放鎖,而wait方法釋放了鎖 。timeout是等待時間。
public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos參數,這個參數表示額外時間(以毫微秒為單位,范圍是 0-999999)。 所以超時的時間還需要加上nanos毫秒。
public final void wait() throws InterruptedException//跟之前的2個wait方法一樣,只不過該方法一直等待,沒有超時時間這個概念
protected void finalize() throws Throwable { }//實例被垃圾回收器回收的時候觸發(fā)的操作
String 、 StringBuilder 、 StringBuffer 的區(qū)別
可變性:String 是不可變的。String 類中保存字符串的數組被 final 修飾且為私有,并且并沒有向外部提供修改這個字符數組的方法;且 String 類被final 修飾,使其不能被繼承,進而避免了子類破壞 String。因此,當String 對象發(fā)生改變時,都會重新生成一個新的 String 對象,然后將引用指向新的 String 對象。(對于 final 修飾的引用類型,其指向的內存地址不可變,但是對象內容是可以發(fā)生變化的)
StringBuilder 和 StringBuffer 也是使用字符數組來保存字符串,但是沒有使用 final 和 private 關鍵字修飾,且還提供了修改字符串的方法,是可變的。
線程安全性:String 對象是不可變的,可視為常量,因此是線程安全的。StringBuffer 對方法加了同步鎖,也是線程安全的。而 StringBuilder 沒有對方法加鎖,是非線程安全的。
為什么要將String設置成不可變
1、不可變的對象可以當做散列表的key
2、不可變的對象本身就是線程安全的,不需要額外進行同步
反射會改變對象的不可變性。
String 的 hashCode 是怎么生成的
https://www.huaweicloud.com/articles/e6ae1a928b6bea00bc83bd481cafa336.html
字符串常量池
字符串常量池是 JVM 為了提升性能和減少內存消耗針對 String 類專門開辟的一塊區(qū)域,主要目的是為了避免字符串的重復創(chuàng)建。
tring aa = "ab"; // 放在常量池中
String bb = "ab"; // 從常量池中查找
System.out.println(aa==bb);// true
JDK1.7 之前運行時常量池邏輯上包含字符串常量池,放在方法區(qū)中。JDK1.7 時字符串常量池被放到了堆中。
泛型
泛型是 JDK5 中引入的一個新特性,泛型提供了編譯時類型安全檢查機制,該機制允許編譯時檢測到非法類型,其本質是參數化類型,即將所操作的數據類型指定為一個參數。
但是,在需要轉型的時候,編譯器會根據所操作數據的類型自動實行安全地強制轉型,所以在運行期,所有的泛型信息都會被擦除,即常說的泛型擦除。
泛形要求能包容的是引用類型,而基本類型在 java 里不屬于對象。所以泛型不能是基本數據類型。(所有對象都繼承了 Object 類型)
(有泛型類、泛型接口、泛型方法)
反射
反射可以讓我們在運行期分析類以及執(zhí)行類中的方法,通過反射可以獲取任意一個類的所有屬性和方法,并可以調用。這也使得反射引入了安全問題,比如可以無視泛型參數的安全檢查。
反射的應用場景
Spring 、Spring Boot 、MyBatis 等框架都大量使用了反射機載。
動態(tài)代理和注解也是基于反射實現的。
注解
注解可以用于修飾類、方法或者屬性,其本質是一個繼承了 Annotation 的特殊接口。
注解只有被解析后才會生效,常見的解析方式有兩種:
編譯期直接掃描:編譯器在編譯代碼時掃描對應的注解并處理,比如某個方法使用 @Override 注解,編譯器在編譯的時候就會檢測當前的方法是否重寫了父類對應的方法。
運行期通過反射處理:框架中自帶的注解都是通過反射處理的。
異常
java 異常類的層次結構:

Exception:程序本身可以處理的異常,可以通過 catch 來進行捕獲。Exception 又可以分為 Checked Exception (受檢查異常,必須處理否則無法通過編譯)和 Unchecked Exception (非受檢查異常,可以不處理,RuntimeException 及其子類都是非受檢異常)。
Error:程序無法處理的錯誤,沒辦法通過 catch 來進行捕獲,發(fā)生錯誤時,JVM 一般會選擇線程終止。
try-catch-finally的使用及注意事項
try 塊: 用于捕獲異常。其后可接零個或多個 catch 塊,如果沒有 catch 塊,則必須跟一個 finally 塊。
catch 塊: 用于處理 try 捕獲到的異常。
finally 塊: 無論是否捕獲或處理異常,finally 塊里的語句都會被執(zhí)行。當在 try 塊或 catch 塊中遇到 return 語句時,finally 語句塊將在方法返回之前被執(zhí)行,且返回值在 finally 塊執(zhí)行之前就已經確定了,不會因為 finally 塊的執(zhí)行而發(fā)生改變。
注意:不要在 finally 語句塊中使用 return! 當 try 語句和 finally 語句中都有 return 語句時,try 語句塊中的 return 語句不會被執(zhí)行。
finally 中的代碼一定會執(zhí)行嗎
在 finally之前虛擬機被終止運行的話(或者線程死亡、CPU關閉),finally 中的代碼就不會被執(zhí)行。
聽說過 try-with-resources 嗎
在面對必須要關閉的資源時,應該優(yōu)先考慮使用 try-with-resources ,可以讓我們更容易編寫必須要關閉的資源的代碼。
序列化與反序列化(串行化與并行化)
序列化:將對象(實例化后的 Class 類)轉換為字節(jié)序列(二進制字節(jié)流),序列化后可以持久化到磁盤上,也可以在網絡上進行傳輸。
反序列化:將字節(jié)序列恢復成對象。
https://juejin.cn/post/6844904176263102472
實現了Serializable接口的類都是可以被序列化的!
- 凡是被
static修飾的字段是不會被序列化的 - 凡是被
transient修飾符修飾的字段也是不會被序列化的(transient只能用于修飾字段)
如果在序列化某個對象時,不希望某個字段被序列化(比如這個字段存放的是隱私值,如:密碼等),那這時就可以用transient修飾符來修飾該字段。這樣在反序列化時,這個字段就會為被置成默認值而非真實值。
java 中的 IO 流
按照流向可以分為輸入流和輸出流;
按照操作單元可以分為字符流和字節(jié)流;

信息的最小存儲單元是字節(jié),為什么需要有字符流操作
字符是由字節(jié)轉換得到的,轉換過程是比較耗時的,且如果不知道編碼類型就很容易出現亂碼問題,所以 I/O流就提供了一個直接操作字符的接口,方便對字符進行流操作。
如果音頻文件、圖片等媒體文件用字節(jié)流比較好,如果涉及到字符的話使用字符流比較好。
內部類
內部類是一個編譯期的概念,一旦編譯成功,就會成為完全不同的兩類。
http://www.itdecent.cn/p/a9467e690eb0
1、由于內部類可以直接訪問外部類中的所有變量,因此我們可以通過內部類來訪問外部類中的私有屬性和方法。(閉包)
2、外部類對內部類的封裝主要表現為,在其它地方訪問內部類,受到了外部類的限制。
3、使用內部類可以間接地進行多重繼承。
內部類變量的可見性
1)靜態(tài)內部類:創(chuàng)建在類中,和成員變量同級,帶有static關鍵字
外部類和靜態(tài)內部類沒有訪問限制。
2)成員內部類:創(chuàng)建在類中,和成員變量同級
外部類不能直接使用內部類中的成員變量,需要創(chuàng)建出內部類對象后再訪問,而創(chuàng)建內部類對象需要用外部類對象來創(chuàng)建。
內部類可以直接訪問外部類中的成員變量和靜態(tài)變量,內部類中不能有靜態(tài)變量。
3)局部內部類:創(chuàng)建在方法內部
外部類不能訪問內部類中的變量
內部類可以直接訪問外部類中成員變量和靜態(tài)變量,不能含有靜態(tài)變量
4)匿名內部類:建立在方法內部,且沒有名稱
場景:只用到匿名內部類的一個實例,且在類定義后馬上就使用到
不能有構造方法和靜態(tài)成員、靜態(tài)方法,局部內部類中的規(guī)則也作用與匿名內部類。