這篇文章專注于Java基礎知識,不涉及List、Map、多線程、鎖相關的內容,需要的可以查看我的其他博客hofe's blog?hhf443.github.io

JDK&JRE&JVM
JDK(Java Development Kit)是針對 Java 開發員的產品,是整個 Java 的核心,包括了 Java 運行環境 JRE、Java 工具(編譯、開發工具)和 Java 核心類庫。
Java Runtime Environment(JRE)是運行 JAVA 程序所必須的環境的集合,包含 JVM 標準實 現及 Java 核心類庫。
JVM 是 Java Virtual Machine(Java 虛擬機)的縮寫,是整個 java 實現跨平臺的最核心的部 分,能夠運行以 Java 語言寫作的軟件程序
跨平臺
字節碼是在虛擬機上運行的,而不是編譯器。換而言之,是因為 JVM 能跨平臺安裝,所以 相應JAVA字節碼便可以跟著在任何平臺上運行。只要JVM自身的代碼能在相應平臺上運行, 即 JVM 可行,則 JAVA 的程序員就可以不用考慮所寫的程序要在哪里運行,反正都是在虛擬 機上運行,然后變成相應平臺的機器語言,而這個轉變并不是程序員應該關心的
一、基本數據類型
0.1*3精度問題(6.6f+1.3f)
可參考這篇:https://mp.weixin.qq.com/s?__biz=MzIwNTk5NjEzNw==&mid=2247490447&idx=2&sn=ef68a5adbad88fe4012b78356a25bdf5&chksm=97293289a05ebb9f29e9d6c96d9f86d4f4a7bd69c47d6fe92a3591d3c3ab76701b40f544d4e8&mpshare=1&scene=23&srcid=&sharer_sharetime=1590383496510&sharer_shareid=d476d18cbe4a83b141ea1ff413565f8c#rd
二、包裝類
2.1 為什么需要包裝類
由于基本數據類型不是對象,所以 java 并不是純面向對象的語言,好處是效率較高(全部 包裝為對象效率較低)。 Java 是一個面向對象的編程語言,基本類型并不具有對象的性質,為了讓基本類型也具有 對象的特征,就出現了包裝類型(如我們在使用集合類型 Collection 時就一定要使用包裝類 型而非基本類型),使得它具有了對象的性質,并且為其 添加了屬性和方法,豐富了基本類型的操作。
2.2 自動裝箱、自動拆箱(編譯器行為)
自動裝箱:可以將基礎數據類型包裝成對應的包裝類
自動拆箱:可以將包裝類轉為對應的基礎數據類型
Integer i = 10000; // 編譯器會改為 new Integer(10000) int i = new Integer(1000);//編譯器會修改為 int i = new Integer(1000).intValue();
自動拆箱時如果包裝類是 null,那么會拋出 NPE
2.3 Integer數組范圍(-128~127)
java中如果Integer不是new出Integer對象,而是Integer.valueOf或者直接賦值如:
Integer b1 = 12;
Integer b2 = 12;
這種情況是在常量池中開辟出同一個空間來存儲12,所以b1和b2都指向12

接下來說說,Integer的緩沖范圍,因為不是在堆區new一個對象,那么在常量池中就必須對其的大小范圍做出一個規定,就是數值多少的可以存放在緩存內
如果超出了范圍,會從堆區new一個Integer對象來存放值
源碼中static final int low = -128;規定了下限為-128,但是最大范圍沒有確定下來,上限可以通過設置JDK的AutoBoxCacheMax參數調整。
所以在比較-128~127內的兩個Integer數據時因為都是常量池的對象,所以==或equals都是true;超過這個范圍會在堆中new 對象,==比較的是內存地址,返回 false。
2.4 == 與 equals的區別
如果兩個引用類型變量使用==運算符,那么比較的是地址,它們分別指向的是否是同一地 址的對象。要求是兩個對象都不是空值,與空值比較返回 false。 ==不能實現比較對象的值是否相同(無法重寫)。
所有對象都有 equals 方法,默認是 Object 類的 equals,其結果與==一樣。 如果希望比較對象的值相同,必須重寫 equals 方法。
public boolean equals(Object obj) { return (this == obj); }
2.5 hashCode 與 equals的區別
Object 中的 equals:
public boolean equals(Object obj) { return (this == obj); }
equals 方法要求滿足:
自反性 a.equals(a)
對稱性 x.equals(y) ==y.equals(x)
一致性 x.equals(y) 多次調用結果一致
對于任意非空引用 x,x.equals(null) 應該返回 false
Object 中的 hashCode:
public native int hashCode();
它是一個本地方法,它的實現與本地機器有關,這里我們暫且認為他返回的是對象存儲的物理位置。
當 equals 方法被重寫時,通常有必要重寫 hashCode 方法,以維護 hashCode 方法的常規約定:值相同的對象必須有相同的 hashCode。
object1.equals(object2)為 true,hashCode 也相同;
hashCode 不同時,object1.equals(object2)為 false;
hashCode 相同時,object1.equals(object2)不一定為 true; // 多個key的hash值相同
2.6 hashCode 與 equals重寫問題
向一個 Hash 結構的集合中添加某個元素時,先調用 hashCode,唯一則存儲,不唯一則再調用 equals,結果相同則不再存儲,結果不同則散列到其他位置。
2.6.1 為什么要重寫equals()方法?
因為object中的equals()方法比較的是對象的引用地址是否相等,如果你需要判斷對象里的內容是否相等,則需要重寫equals()方法。
2.6.2 為什么改寫了equals(),也需要改寫hashcode()
如果你重寫了equals,比如說是基于對象的內容實現的,而保留hashCode的實現(基于內存地址的hash值)不變,那么在添加進map中時需要比對hashcode,很可能某兩個對象明明是“相等”,而hashCode卻不一樣。
2.6.3 為什么改寫了hashcode(),也需要改寫equals()
改寫hashcode()方法是為了讓兩個值相同的對象hashcode也一樣,而不再是基于內存地址,在map中表現為可能是存儲在同一位置的一個對象。而如果不改寫equals,還是基于內存地址進行比較,這樣的話,兩個值相同的對象就不被映射到同一位置。
Hashmap的key可以是任何類型的對象,例如User這種對象,為了保證兩個具有相同屬性的user的hashcode相同,我們就需要改寫hashcode方法,比方把hashcode值的計算與User對象的id關聯起來,那么只要user對象擁有相同id,那么他們的hashcode也能保持一致了,這樣就可以找到在hashmap數組中的位置了。如果這個位置上有多個元素,還需要用key的equals方法在對應位置的鏈表中找到需要的元素,所以只改寫了hashcode方法是不夠的,equals方法也是需要改寫。
2.7 String
String 是 final 類,不可被繼承,也不可重寫一個 java.lang.String(類加載機制)。 一般是使用 StringUtils 來增強 String 的功能。
字符串修改的時候會創建一個新的字符串,編譯時會將+轉為 StringBuilder 的 append 方法。 注意新的字符串是在運行時在堆里創建的。
String#intern(JDK1.7 之后) JDK1.7 之后 JVM 里字符串常量池放入了堆中,之前是放在方法區。 intern()方法設計的初衷,就是重用 String 對象,以節省內存消耗。 一定是 new 得到的字符串才會調用 intern,字符串常量沒有必要去 intern。 當調用 intern 方法時,如果池已經包含一個等于此 String 對象的字符串(該對象由equals(Object) 方法確定),則返回池中的字符串。否則,常量池中直接存儲堆中該字符串 的引用(1.7 之前是常量池中再保存一份該字符串)。
2.8 StringBuffer與StringBuilder
StringBuffer 是線程安全的,StringBuilder 不是線程安全的,但它們兩個中的所有方法都是 相同的。StringBuffer 在 StringBuilder 的方法之上添加了 synchronized,保證線程安全。
StringBuilder 比 StringBuffer 性能好10%-15%。
三、關鍵字
final關鍵字
可以修飾類,函數,變量。
被final修飾的類不可以被繼承,final類中的方法默認是final的
被final修飾的方法不能被重寫
被final修飾的變量是一個常量只能賦值一次
static關鍵字
一句話來概括:方便在沒有創建對象的情況下來進行調用。
修飾內部類、成員變量、成員方法、代碼塊
1、static關鍵字修飾內部類
java里面static一般用來修飾成員變量或函數。但有一種特殊用法是用static修飾內部類,普通類是不允許聲明為靜態的,只有內部類才可以。下面看看如何使用。
public class StaticTest {//static關鍵字修飾內部類public static class InnerClass{InnerClass(){System.out.println("====== 靜態內部類======");}public void InnerMethod() {System.out.println("===== 靜態內部方法=====");}}public static void main(String[] args) {//直接通過StaticTest類名訪問靜態內部類InnerClassInnerClass inner=new StaticTest.InnerClass();//靜態內部類可以和普通類一樣使用inner.InnerMethod();}}/*輸出是* ============= 靜態內部類=============* ============= 靜態內部方法=============*/
如果沒有用static修飾InterClass,則只能new 一個外部類實例。再通過外部實例創建內部類。
成員內部類和靜態內部類的區別:
1)前者只能擁有非靜態成員;后者既可擁有靜態成員,又可擁有非靜態成員
2)前者持有外部類的引用,可以訪問外部類的靜態成員和非靜態成員;后者不持有外部 類的引用,只能訪問外部類的靜態成員
3)前者不能脫離外部類而存在;后者可以
2、static關鍵字修飾方法
修飾方法的時候,其實跟類一樣,可以直接通過類名來進行調用:
public class StaticMethod {public static void test() {System.out.println("======= 靜態方法====");};public static void main(String[] args) {//方式一:直接通過類名StaticMethod.test();//方式二:StaticMethod fdd=new StaticMethod();fdd.test();}}
在靜態方法中不能訪問類的非靜態成員變量和非靜態成員方法; 在非靜態成員方法中是可以訪問靜態成員方法/變量的; 即使沒有顯式地聲明為 static,類的構造器實際上也是靜態方法
3、static關鍵字修飾變量
被static修飾的成員變量叫做靜態變量,也叫做類變量,說明這個變量是屬于這個類的,而不是屬于是對象,沒有被static修飾的成員變量叫做實例變量,說明這個變量是屬于某個具體的對象的。
我們同樣可以使用上面的方式進行調用變量:
public class StaticVar {private static String name="java的架構師技術棧";public static void main(String[] args) {//直接通過類名StaticVar.name;}}
靜態變量和非靜態變量的區別是:靜態變量被所有的對象所共享,在內存中只有一個副本, 它當且僅當在類初次加載時會被初始化。而非靜態變量是對象所擁有的,在創建對象的時候 被初始化,存在多個副本,各個對象擁有的副本互不影響。 靜態成員變量并發下不是線程安全的,并且對象是單例的情況下,非靜態成員變量也不是線 程安全的。 怎么保證變量的線程安全? 只有一個線程寫,其他線程都是讀的時候,加 volatile;線程既讀又寫,可以考慮 Atomic 原 子類和線程安全的集合類;或者考慮 ThreadLocal
4、static關鍵字修飾代碼塊
用來構造靜態代碼塊以優化程序性能。static 塊可以置于類中的任何地方,類中可以有多個 static 塊。在類初次被加載的時候,會按照 static 塊的順序來執行每個 static 塊,并且只會執 行一次。
靜態代碼塊在類第一次被載入時執行,在這里主要是想驗證一下,類初始化的順序。
- 父類靜態變量、父類靜態代碼塊
- 子類靜態變量、子類靜態代碼塊
- 父類普通變量、父類普通代碼塊、父類構造函數
- 子類普通變量、子類普通代碼塊、子類構造函數
四、面向對象
4.1 面向對象與面向過程的本質的區別
在于考慮問題的出發點不同,
面向過程是以事件流程為考慮問題的出發點,
而面向對象則是以參與事件的角色(對象)為考慮問題的出發點
4.2 抽象類與接口
區別:
1)抽象類中方法可以不是抽象的;接口中的方法必須是抽象方法;
2)抽象類中可以有普通的成員變量;接口中的變量必須是 static final 類型的,必須被初始 化 , 接口中只有常量,沒有變量。
3)抽象類只能單繼承,接口可以繼承多個父接口;
4)Java8 中接口中會有 default 方法,即方法可以被實現。
使用場景:
如果要創建不帶任何方法定義和成員變量的基類,那么就應該選擇接口而不是抽象類。
如果知道某個類應該是基類,那么第一個選擇的應該是讓它成為一個接口,只有在必須要有 方法定義和成員變量的時候,才應該選擇抽象類。
因為抽象類中允許存在一個或多個被具體 實現的方法,只要方法沒有被全部實現該類就仍是抽象類
4.3 對象三大特性
面向對象的三個特性:封裝;繼承;多態
封裝:將數據與操作數據的方法綁定起來,隱藏實現細節,對外提供接口。
繼承:代碼重用;可擴展性
多態:允許不同子類對象對同一消息做出不同響應 多態的三個必要條件:繼承、方法的重寫、父類引用指向子類對象
封裝 封裝是指將某事物的屬性和行為包裝到對象中,這個對象只保留一些對外接口使之與外部發生聯系。用戶無需知道對象內部的細節,但可以通過對象對外提供的接口來訪問該對象。
繼承 子類繼承父類的特征和行為,子類可以有父類的方法和屬性,子類也可以對父類進行擴展,也可以提供重寫的方法;繼承的方式有兩種:實現繼承和接口繼承
多態 多態就是指多種狀態,就是說當一個操作在不同的對象時,會產生不同的結果。 多態分為編譯時多態和運行時多態,編譯時多態主要指方法的重載,運行時多態指程序中定義的對象引用所指向的具體類型在運行期間才確定,運行時多態主要通過重寫來實現。 多態的作用:消除類型之間的耦合關系。
那么JAVA的多態是怎么實現的? 接口實現、抽象類、繼承父類進行方法重寫、同一個類中進行方法重載。
4.4 JAVA中重載與重寫的概念?
(Overload)重載:發生在同一個類之中,方法名相同、參數列表不同,與返回值無關、與final無關、與修飾符無關、與異常無關。 (Override)重寫:發生在子類和父類之間,方法名相同、參數列表相同、返回值相同、不能是final的方法、重寫的方法不能有比父類方法更為嚴格的修飾符權限、重寫的方法所拋出的異常不能比父類的更大。
五、引用
強引用
StringReference GC 時不回收 當內存空間不足,Java 虛擬機寧愿拋出 OutOfMemoryError 錯誤,使程序異常終止,也不會 靠隨意回收具有強引用的對象來解決內存不足問題。
軟引用
SoftReference GC 時如果 JVM 內存不足時會回收 軟引用可用來實現內存敏感的高速緩存。 軟引用可以和一個引用隊列(ReferenceQueue) 聯合使用,如果軟引用所引用的對象被垃圾回收,Java 虛擬機就會把這個軟引用加入到與 之關聯的引用隊列中。
弱引用
WeakReference GC 時立即回收 弱引用與軟引用的區別在于:只具有弱引用的對象擁有更短暫的生命周期。 弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃 圾回收,Java 虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
虛引用
PhantomReference 如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。 虛引用主要用來跟蹤對象被垃圾回收的活動。虛引用與軟引用和弱引用的一個區別在于:虛 引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時, 如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用 隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將 要被垃圾回收。程序如果發現某個虛引用已經被加入到引用隊列,那么就可以在所引用的對 象的內存被回收之前采取必要的行動。
六、ThreadLocal
在線程之間共享變量是存在風險的,有時可能要避免共享變量,使用 ThreadLocal 輔助類為 各個線程提供各自的實例。
每個線程內部都會維護一個類似 HashMap 的對象,稱為 ThreadLocalMap,里邊會包含 若干了 Entry(K-V 鍵值對),相應的線程被稱為這些 Entry 的屬主線程; Entry 的 Key 是一個 ThreadLocal 實例,Value 是一個線程特有對象。Entry 的作用即是: 為其屬主線程建立起一個 ThreadLocal 實例與一個線程特有對象之間的對應關系; Entry 對 Key 的引用是弱引用;Entry 對 Value 的引用是強引用。
ThreadLocalMap 的 Key 是弱引用,如果是強引用,ThreadLocal 將無法被釋放內存。 因為如果這里使用普通的 key-value 形式來定義存儲結構,實質上就會造成節點的生命周期 與線程強綁定,只要線程沒有銷毀,那么節點在 GC 分析中一直處于可達狀態,沒辦法被回 收,而程序本身也無法判斷是否可以清理節點。弱引用是 Java 中四檔引用的第三檔,比軟 引用更加弱一些,如果一個對象沒有強引用鏈可達,那么一般活不過下一次 GC。當某個 ThreadLocal 已經沒有強引用可達,則隨著它被垃圾回收,在 ThreadLocalMap 里對應的 Entry 的鍵值會失效,這為 ThreadLocalMap 本身的垃圾清理提供了便利
七、異常
try-catch-finally 中,如果 catch 中 return 了,finally 還會執行嗎?
會執行
1、不管有沒有異常,finally中的代碼都會執行 2、當try、catch中有return時,finally中的代碼依然會繼續執行 3、finally是在return后面的表達式運算之后執行的,此時并沒有return運算之后的值,而是把值保存起來,不管finally對該值做任何的改變,返回的值都不會改變,依然返回保存起來的值。也就是說方法的返回值是在finally運算之前就確定了的。 4、如果return的數據是引用數據類型,而在finally中對該引用數據類型的屬性值的改變起作用,try中的return語句返回的就是在finally中改變后的該屬性的值。 5、finally代碼中最好不要包含return,程序會提前退出,也就是說返回的值不是try或catch中的值
八、反射
概念
Java 中的反射機制是指在運行狀態中,對于任意一個類都能夠知道這個類所有的屬性和方法; 并且對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取信息以及動態調用對象方 法的功能稱為Java 語言的反射機制
Java反射機制的作用?
應用場合:在Java程序中許多對象在運行是都會出現兩種類型:編譯時類型和運行時類型。 編譯時的類型由 聲明對象時用的類型來決定,運行時的類型由實際賦值給對象的類型決定 。如Person p=new Student(); 其中編譯時類型為Person,運行時類型為Student
程序在運行時還可能接收到外部傳入的對象,該對象的編譯時類型為 Object,但是程序有需要調用 該對象的運行時類型的方法。為了解決這些問題,程序需要在運行時發現對象和類的真實信息。 此時就必須使用到反射了 。
總結
- 在運行時能夠判斷任意一個對象所屬的類、創建新類對象
- 在運行時構造任意一個類的對象、判斷任意一個類所具有的成員變量和方法
- 在運行時調用任一對象的方法
應用場景
JDBC中,利用反射動態加載了數據庫驅動程序。
很多框架都用到反射機制,注入屬性,調用方法,如Spring。
Web服務器中利用反射調用了Sevlet的服務方法。
讀取配置文件
如何使用Java的反射?
獲得Class對象的三種方法

創建對象的兩種方法


反射機制的優缺點?
優點:可以動態執行,在運行期間根據業務功能動態執行方法、訪問屬性,最大限度發揮了java的靈活性。 缺點:對性能有影響,這類操作總是慢于直接執行java代碼。