網(wǎng)站是自己做還是讓別人仿愛(ài)鏈接網(wǎng)如何使用
目錄
Java面向?qū)ο笥心男┨卣?#xff0c;如何應(yīng)用
Java基本數(shù)據(jù)類(lèi)型及所占字節(jié)
Java中重寫(xiě)和重載有哪些區(qū)別
jdk1.8的新特性有哪些
內(nèi)部類(lèi)
1. 成員內(nèi)部類(lèi)(Member Inner Class):
2.? ?靜態(tài)內(nèi)部類(lèi)(Static Nested Class):
靜態(tài)內(nèi)部類(lèi)的特點(diǎn):
靜態(tài)內(nèi)部類(lèi)和非靜態(tài)內(nèi)部類(lèi)的區(qū)別:
3. **局部?jī)?nèi)部類(lèi)(Local Inner Class)**:
4. **匿名內(nèi)部類(lèi)(Anonymous Inner Class)**:
泛型
final和static的區(qū)別
接口和抽象類(lèi)有哪些區(qū)別
怎樣聲明一個(gè)類(lèi)不會(huì)被繼承,什么場(chǎng)景下會(huì)用
深拷貝和淺拷貝
序列化
反射介紹
反射的步驟反射的步驟如下。
創(chuàng)建對(duì)象的幾種方式
@Contended注解有什么用
Java中有四種引用類(lèi)型
虛引用
Java中鎖的分類(lèi)
Java中==和equals有哪些區(qū)別
String、StringBuffer、StringBuilder區(qū)別及使用場(chǎng)景
String類(lèi)和常量池
String對(duì)象的兩種創(chuàng)建方式
3.2:String類(lèi)型的常量池比較特殊。
Java代理的幾種實(shí)現(xiàn)方式
靜態(tài)代理
第二種:動(dòng)態(tài)代理,包含JDK代理和CGLIB動(dòng)態(tài)代理
JDK代理
CGLIB動(dòng)態(tài)代理
JDK動(dòng)態(tài)代理和CGLIB兩種動(dòng)態(tài)代理的比較
hashcode和equals如何使用
異常分類(lèi)
Java異常處理方式
throw,throws的區(qū)別
自定義異常在生產(chǎn)中如何應(yīng)用
過(guò)濾器與攔截器的區(qū)別
Integer常見(jiàn)面試題
值傳遞和引用傳遞有什么區(qū)別
集合
?集合和數(shù)組的區(qū)別
集合框架底層數(shù)據(jù)結(jié)構(gòu)
線(xiàn)程安全的集合
HashMap的put方法的具體流程?
HashMap原理是什么,在jdk1.7和1.8中有什么區(qū)別
HashMap和HashTable的區(qū)別及底層實(shí)現(xiàn)
HashMap和HashTable對(duì)比
HashMap擴(kuò)容優(yōu)化:
為什么hashmap擴(kuò)容的時(shí)候是兩倍?
hashmap線(xiàn)程安全的方式?
說(shuō)一下 HashSet 的實(shí)現(xiàn)原理? - HashSet如何檢查重復(fù)?HashSet是如何保證數(shù)據(jù)不可重復(fù)的?
ArrayList和LinkedList有什么區(qū)別
ArrayList擴(kuò)容
Array和ArrayList的區(qū)別
List和數(shù)組之間的轉(zhuǎn)換
數(shù)組類(lèi)型和集合
高并發(fā)中的集合有哪些問(wèn)題
ConcurrentHashMap底層原理是什么?
Java面向?qū)ο笥心男┨卣?#xff0c;如何應(yīng)用
-
封裝(Encapsulation):封裝是指將數(shù)據(jù)和對(duì)數(shù)據(jù)的操作封裝在對(duì)象內(nèi)部,隱藏其具體實(shí)現(xiàn)細(xì)節(jié),并通過(guò)公共接口進(jìn)行訪(fǎng)問(wèn)。封裝可以提高代碼的安全性、可維護(hù)性和可復(fù)用性。
-
繼承(Inheritance):繼承是指允許一個(gè)類(lèi)繼承另一個(gè)類(lèi)的屬性和方法。通過(guò)繼承,子類(lèi)可以獲得父類(lèi)的屬性和方法,并可以在此基礎(chǔ)上進(jìn)行擴(kuò)展或修改。繼承實(shí)現(xiàn)了代碼的重用和層次化組織。
-
多態(tài)(Polymorphism):多態(tài)是指同一個(gè)類(lèi)型的對(duì)象在不同的情況下表現(xiàn)出不同的行為。通過(guò)多態(tài),可以在編譯時(shí)不確定具體的對(duì)象類(lèi)型,而在運(yùn)行時(shí)確定調(diào)用的方法。多態(tài)使得代碼具有靈活性和擴(kuò)展性。
-
抽象(Abstraction):抽象是指從對(duì)象的共同特征中提取出抽象類(lèi)或接口,用來(lái)描述一組相關(guān)的對(duì)象。抽象類(lèi)和接口定義了對(duì)象的共同行為和規(guī)范,可以通過(guò)繼承和實(shí)現(xiàn)來(lái)實(shí)現(xiàn)具體的功能。
如何應(yīng)用Java面向?qū)ο蟮奶卣?#xff1a;
-
封裝:將相關(guān)的數(shù)據(jù)和行為封裝在對(duì)象內(nèi)部,通過(guò)合適的訪(fǎng)問(wèn)修飾符(例如private、protected、public)限制訪(fǎng)問(wèn)權(quán)限。同時(shí),提供合適的公共方法來(lái)操作對(duì)象的數(shù)據(jù)。
-
繼承:通過(guò)使用extends關(guān)鍵字來(lái)實(shí)現(xiàn)繼承關(guān)系,讓子類(lèi)繼承父類(lèi)的屬性和方法??梢允褂美^承來(lái)實(shí)現(xiàn)代碼的重用和層次化組織。
-
多態(tài):通過(guò)使用多態(tài),可以根據(jù)不同的實(shí)際對(duì)象類(lèi)型來(lái)調(diào)用相應(yīng)的方法,實(shí)現(xiàn)不同的行為??梢酝ㄟ^(guò)方法的重寫(xiě)(Override)和接口的實(shí)現(xiàn)(Implement)來(lái)實(shí)現(xiàn)多態(tài)。
-
抽象:當(dāng)遇到一組有共同特征的對(duì)象時(shí),可以使用抽象類(lèi)或接口來(lái)定義這些對(duì)象的共同行為和規(guī)范。通過(guò)繼承和實(shí)現(xiàn)來(lái)實(shí)現(xiàn)具體的功能。
以上是Java面向?qū)ο蟮奶卣骱腿绾螒?yīng)用的簡(jiǎn)要介紹。在實(shí)際開(kāi)發(fā)中,根據(jù)具體情況靈活應(yīng)用這些特征,可以使代碼更加有組織、可擴(kuò)展和易維護(hù)。
Java基本數(shù)據(jù)類(lèi)型及所占字節(jié)
Java中重寫(xiě)和重載有哪些區(qū)別
在Java中,重寫(xiě)(Override)和重載(Overload)是兩個(gè)常用的概念,用于實(shí)現(xiàn)多態(tài)性。它們之間的區(qū)別如下:
-
重寫(xiě)(Override):
-
重寫(xiě)指的是子類(lèi)重新定義了父類(lèi)中已有的方法,具有相同的方法名、參數(shù)列表和返回類(lèi)型。
-
重寫(xiě)方法必須在繼承關(guān)系中存在,即子類(lèi)覆蓋父類(lèi)的方法。
-
重寫(xiě)方法的訪(fǎng)問(wèn)修飾符不能比父類(lèi)更嚴(yán)格,可以更寬松或相同。
-
重寫(xiě)方法不能拋出比父類(lèi)更寬泛的異常。
-
在運(yùn)行時(shí),根據(jù)對(duì)象的實(shí)際類(lèi)型調(diào)用對(duì)應(yīng)的重寫(xiě)方法,實(shí)現(xiàn)多態(tài)性。
-
-
重載(Overload):
-
重載指的是在同一個(gè)類(lèi)中定義了多個(gè)具有相同名字但參數(shù)列表不同的方法。
-
重載方法的返回類(lèi)型可以相同也可以不同,但不能僅僅通過(guò)返回類(lèi)型來(lái)區(qū)分方法。
-
重載方法的訪(fǎng)問(wèn)修飾符可以相同也可以不同。
-
重載方法可以?huà)伋鋈我獾漠惓!?/p>
-
在編譯時(shí),根據(jù)方法調(diào)用時(shí)提供的參數(shù)類(lèi)型和數(shù)量來(lái)確定調(diào)用哪個(gè)重載方法。
-
總結(jié)來(lái)說(shuō),重寫(xiě)用于子類(lèi)重新定義父類(lèi)方法的實(shí)現(xiàn),而重載用于同一個(gè)類(lèi)中根據(jù)參數(shù)的不同來(lái)定義多個(gè)方法。重寫(xiě)是實(shí)現(xiàn)多態(tài)性的關(guān)鍵,而重載則提供了更多的靈活性和便利性。
jdk1.8的新特性有哪些
Java 8 在發(fā)布時(shí)引入了許多新的語(yǔ)言特性和 API 改進(jìn)。以下是 JDK 1.8 中一些主要的新特性:
1. **Lambda 表達(dá)式**:Lambda 表達(dá)式是 Java 8 中引入的一項(xiàng)重要特性,它簡(jiǎn)化了匿名內(nèi)部類(lèi)的使用,使代碼更加簡(jiǎn)潔、易讀。Lambda 表達(dá)式可以在函數(shù)式接口中使用,通過(guò)箭頭符號(hào) "->" 將參數(shù)和方法體分隔開(kāi)。
2. **Stream API**:Stream 是 Java 8 中引入的用于處理集合數(shù)據(jù)的 API,提供了豐富的中間操作和結(jié)束操作,可以使代碼更具表現(xiàn)力和可讀性,并且支持并行操作。
3. **方法引用**:方法引用是一種簡(jiǎn)化 Lambda 表達(dá)式的語(yǔ)法,可以直接引用已有方法作為 Lambda 表達(dá)式的實(shí)現(xiàn)。
4. **接口的默認(rèn)方法和靜態(tài)方法**:在 Java 8 中,接口可以定義默認(rèn)方法和靜態(tài)方法,使接口可以包含具體實(shí)現(xiàn)而不僅僅是抽象方法,這樣可以更好地支持接口的擴(kuò)展和演進(jìn)。
5. **Optional 類(lèi)**:Optional 類(lèi)是一個(gè)容器類(lèi),用于處理可能為空的值,避免空指針異常,并鼓勵(lì)更好的代碼實(shí)踐。
6. **新的日期和時(shí)間 API**:Java 8 引入了新的日期時(shí)間 API(java.time 包),提供了更好的日期和時(shí)間處理方式,包括不可變性、線(xiàn)程安全性和清晰的設(shè)計(jì)。
7. **CompletableFuture 類(lèi)**:CompletableFuture 是 Java 8 中引入的用于異步編程的類(lèi),通過(guò)它可以更容易地實(shí)現(xiàn)并發(fā)和異步操作。
8. **重復(fù)注解**:Java 8 允許在相同的地方多次使用同一個(gè)注解,這樣可以避免代碼中出現(xiàn)大量相同的注解。
9. **Java 類(lèi)庫(kù)的改進(jìn)**:Java 8 中還做了許多類(lèi)庫(kù)的改進(jìn)和增強(qiáng),包括新的工具類(lèi)、函數(shù)式接口、默認(rèn)方法等。
Java 8 的這些新特性使得 Java 編程變得更加現(xiàn)代化、高效和簡(jiǎn)潔,提升了開(kāi)發(fā)人員的編碼體驗(yàn)和生產(chǎn)效率。
內(nèi)部類(lèi)
在 Java 中,有四種類(lèi)型的內(nèi)部類(lèi),它們分別是:成員內(nèi)部類(lèi)(Member Inner Class)、靜態(tài)內(nèi)部類(lèi)(Static Nested Class)、局部?jī)?nèi)部類(lèi)(Local Inner Class)和匿名內(nèi)部類(lèi)(Anonymous Inner Class)。下面我會(huì)分別介紹這四種內(nèi)部類(lèi),并為每種內(nèi)部類(lèi)舉一個(gè)簡(jiǎn)單的代碼示例:
1. 成員內(nèi)部類(lèi)(Member Inner Class):
? ?- 成員內(nèi)部類(lèi)是定義在另一個(gè)類(lèi)中的普通類(lèi),可以訪(fǎng)問(wèn)外部類(lèi)的實(shí)例成員和方法。
? ?
```java
public class Outer {
? ? private int outerField;
? ? public class Inner {
? ? ? ? public void display() {
? ? ? ? ? ? System.out.println("OuterField: " + outerField);
? ? ? ? }
? ? }
}
```
2.? ?靜態(tài)內(nèi)部類(lèi)(Static Nested Class):
? ?- 靜態(tài)內(nèi)部類(lèi)是嵌套在外部類(lèi)中并被聲明為 static 的類(lèi),可以直接通過(guò)外部類(lèi)訪(fǎng)問(wèn)靜態(tài)內(nèi)部類(lèi)。
? ?
```java
public class Outer {
? ? private static int outerStaticField;
? ? public static class StaticInner {
? ? ? ? public void display() {
? ? ? ? ? ? System.out.println("OuterStaticField: " + outerStaticField);
? ? ? ? }
? ? }
}
```
靜態(tài)內(nèi)部類(lèi)是嵌套在外部類(lèi)中并被聲明為 static 的類(lèi),它和非靜態(tài)內(nèi)部類(lèi)有一些特點(diǎn)和區(qū)別:
靜態(tài)內(nèi)部類(lèi)的特點(diǎn):
1. 靜態(tài)內(nèi)部類(lèi)可以直接通過(guò)外部類(lèi)訪(fǎng)問(wèn),無(wú)需實(shí)例化外部類(lèi)。
2. 靜態(tài)內(nèi)部類(lèi)不能訪(fǎng)問(wèn)外部類(lèi)的非靜態(tài)成員,但可以訪(fǎng)問(wèn)外部類(lèi)的靜態(tài)成員。
3. 靜態(tài)內(nèi)部類(lèi)的實(shí)例可以獨(dú)立存在,不依賴(lài)于外部類(lèi)的實(shí)例。
4. 靜態(tài)內(nèi)部類(lèi)通常用來(lái)作為外部類(lèi)的幫助類(lèi),或者與外部類(lèi)相關(guān)但又不依賴(lài)于外部類(lèi)實(shí)例的邏輯。
靜態(tài)內(nèi)部類(lèi)和非靜態(tài)內(nèi)部類(lèi)的區(qū)別:
1. **訪(fǎng)問(wèn)外部類(lèi)成員**:靜態(tài)內(nèi)部類(lèi)不能直接訪(fǎng)問(wèn)外部類(lèi)的非靜態(tài)成員變量和方法,而非靜態(tài)內(nèi)部類(lèi)可以直接訪(fǎng)問(wèn)外部類(lèi)的所有成員。
2. **實(shí)例化**:靜態(tài)內(nèi)部類(lèi)的實(shí)例不依賴(lài)于外部類(lèi)的實(shí)例,可以直接使用outerClass.StaticInnerClass的方式實(shí)例化,而非靜態(tài)內(nèi)部類(lèi)需要通過(guò)外部類(lèi)的實(shí)例來(lái)創(chuàng)建。
3. **靜態(tài)性**:靜態(tài)內(nèi)部類(lèi)本身就是靜態(tài)的,因此可以包含靜態(tài)成員,而非靜態(tài)內(nèi)部類(lèi)無(wú)法包含靜態(tài)成員。
4. **使用場(chǎng)景**:靜態(tài)內(nèi)部類(lèi)適合作為獨(dú)立實(shí)體存在,或者與外部類(lèi)無(wú)關(guān)但又需要在同一文件中定義的類(lèi);非靜態(tài)內(nèi)部類(lèi)通常用于與外部類(lèi)有關(guān)聯(lián)的邏輯,需要訪(fǎng)問(wèn)外部類(lèi)的實(shí)例成員。
總的來(lái)說(shuō),靜態(tài)內(nèi)部類(lèi)和非靜態(tài)內(nèi)部類(lèi)都有各自的優(yōu)點(diǎn)和適用場(chǎng)景,選擇哪種方式取決于需求和設(shè)計(jì)目的。靜態(tài)內(nèi)部類(lèi)通常用于幫助類(lèi)或獨(dú)立實(shí)體,而非靜態(tài)內(nèi)部類(lèi)通常用于與外部類(lèi)相關(guān)聯(lián)的邏輯。
3. **局部?jī)?nèi)部類(lèi)(Local Inner Class)**:
? ?- 局部?jī)?nèi)部類(lèi)是定義在方法內(nèi)部的類(lèi),只能在包含它的方法中使用,通常用于解決特定問(wèn)題或局部邏輯。
??
```java
public class Outer {
? ? public void display() {
? ? ? ? class LocalInner {
? ? ? ? ? ? public void show() {
? ? ? ? ? ? ? ? System.out.println("Local Inner Class");
? ? ? ? ? ? }
? ? ? ? }
? ? ? ??
? ? ? ? LocalInner localInner = new LocalInner();
? ? ? ? localInner.show();
? ? }
}
```
4. **匿名內(nèi)部類(lèi)(Anonymous Inner Class)**:
匿名內(nèi)部類(lèi)是一種沒(méi)有顯示定義類(lèi)名的內(nèi)部類(lèi),通常在創(chuàng)建對(duì)象的同時(shí)定義類(lèi)并實(shí)例化對(duì)象,適用于只需要一次性使用的情況。以下是匿名內(nèi)部類(lèi)的特點(diǎn):
1. **沒(méi)有類(lèi)名**:匿名內(nèi)部類(lèi)沒(méi)有類(lèi)名,通常直接在使用的地方通過(guò) new 關(guān)鍵字創(chuàng)建對(duì)象并定義類(lèi)。
? ?
2. **實(shí)現(xiàn)接口或繼承父類(lèi)**:匿名內(nèi)部類(lèi)通常用于實(shí)現(xiàn)接口或繼承父類(lèi),并在創(chuàng)建對(duì)象時(shí)直接實(shí)現(xiàn)接口方法或重寫(xiě)父類(lèi)方法。
3. **可以訪(fǎng)問(wèn)外部類(lèi)的成員**:匿名內(nèi)部類(lèi)可以訪(fǎng)問(wèn)外部類(lèi)的成員變量和方法,但是需要這些成員變量和方法是 final 或是 effectively final 的(Java 8 之后允許訪(fǎng)問(wèn)非 final 的局部變量)。
4. **一次性使用**:匿名內(nèi)部類(lèi)適用于只需要一次性使用、不需要長(zhǎng)期保存引用的情況,可以簡(jiǎn)化代碼結(jié)構(gòu)。
5. **可以引用外部類(lèi)的局部變量**:Java 8 之后,匿名內(nèi)部類(lèi)可以訪(fǎng)問(wèn)外部方法中的局部變量,前提是這些局部變量需要是 final 或 effectively final 的。
6. **簡(jiǎn)化代碼**:匿名內(nèi)部類(lèi)可以減少編寫(xiě)類(lèi)定義的代碼量,并且可以更直觀地展現(xiàn)代碼邏輯。
雖然匿名內(nèi)部類(lèi)在某些情況下能夠帶來(lái)便利,但也應(yīng)該注意避免濫用匿名內(nèi)部類(lèi),特別是在邏輯復(fù)雜或需要復(fù)用的情況下,最好還是考慮使用具名的內(nèi)部類(lèi)或獨(dú)立類(lèi)來(lái)實(shí)現(xiàn)相應(yīng)的功能。
```java
public class Outer {
? ? public void display() {
? ? ? ? Runnable runnable = new Runnable() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? System.out.println("Anonymous Inner Class");
? ? ? ? ? ? }
? ? ? ? };
? ? ? ??
? ? ? ? new Thread(runnable).start();
? ? }
}
```
以上是四種內(nèi)部類(lèi)的簡(jiǎn)單介紹和代碼示例,通過(guò)使用不同類(lèi)型的內(nèi)部類(lèi),可以實(shí)現(xiàn)更靈活的代碼設(shè)計(jì)和結(jié)構(gòu)化。每種內(nèi)部類(lèi)都有不同的應(yīng)用場(chǎng)景和特性,可以根據(jù)實(shí)際需求選擇合適的內(nèi)部類(lèi)類(lèi)型。
泛型
Java中的泛型是一種類(lèi)型參數(shù)化的機(jī)制,允許在類(lèi)、接口和方法中使用參數(shù)化類(lèi)型。通過(guò)使用泛型,可以將類(lèi)型的具體實(shí)例化延遲到使用時(shí),提高代碼的靈活性、可復(fù)用性和類(lèi)型安全性。
Java的泛型主要包括以下幾個(gè)方面:
-
泛型類(lèi)(Generic Class): 使用泛型類(lèi)可以在定義類(lèi)時(shí)指定一個(gè)或多個(gè)類(lèi)型參數(shù),這些參數(shù)可以在類(lèi)內(nèi)部作為類(lèi)型的占位符使用。使用泛型類(lèi)可以創(chuàng)建具有不同類(lèi)型參數(shù)的實(shí)例,從而提供了更靈活的數(shù)據(jù)類(lèi)型支持。
例如,在定義一個(gè)
List
時(shí)可以使用泛型參數(shù)來(lái)指定列表元素類(lèi)型,如List<String>
表示元素類(lèi)型為字符串的列表。 -
泛型接口(Generic Interface): 泛型接口與泛型類(lèi)類(lèi)似,可以在接口定義中使用類(lèi)型參數(shù)。通過(guò)泛型接口,可以創(chuàng)建實(shí)現(xiàn)指定類(lèi)型參數(shù)的接口的實(shí)例。
例如,
Comparable<T>
是一個(gè)泛型接口,用于實(shí)現(xiàn)可比較的對(duì)象。其中的類(lèi)型參數(shù)T
表示待比較的對(duì)象的類(lèi)型。 -
泛型方法(Generic Method): 泛型方法可以在方法內(nèi)部獨(dú)立地使用泛型類(lèi)型,可以有自己的類(lèi)型參數(shù)。使用泛型方法可以在方法調(diào)用時(shí)指定不同的類(lèi)型,并在方法內(nèi)部進(jìn)行參數(shù)和返回類(lèi)型的類(lèi)型推斷。
例如,
Collections
類(lèi)中的sort
方法就是一個(gè)泛型方法,可以對(duì)不同類(lèi)型的數(shù)組進(jìn)行排序。它根據(jù)方法調(diào)用時(shí)傳入的參數(shù)類(lèi)型進(jìn)行類(lèi)型推斷。 -
通配符和上界(Wildcard and Upper Bound): 在使用泛型時(shí),可以使用通配符
?
來(lái)表示未知的類(lèi)型,通常用于讀取操作。通過(guò)使用上界,可以限制通配符所代表的類(lèi)型的范圍。例如,
List<?>
表示一個(gè)未知類(lèi)型的列表,可以獲取列表中的元素,但無(wú)法添加任何元素。而List<? extends Number>
表示一個(gè)類(lèi)型為Number及其子類(lèi)的列表,限制了可以添加的元素類(lèi)型。
泛型的優(yōu)勢(shì)包括代碼復(fù)用性高、提高代碼的類(lèi)型安全性、減少類(lèi)型轉(zhuǎn)換的錯(cuò)誤以及提供更強(qiáng)大的編譯時(shí)類(lèi)型檢查。通過(guò)在Java中使用泛型,可以編寫(xiě)更靈活和可維護(hù)的代碼,并提高代碼的可讀性和可擴(kuò)展性。
final和static的區(qū)別
final
和static
是Java中兩個(gè)關(guān)鍵字,它們有不同的用途和含義:
-
final
關(guān)鍵字:-
修飾變量:
final
修飾的變量表示一個(gè)最終的常量,即不可再改變的值。一旦被賦初值后,該變量的值不能再被修改。final
變量通常用大寫(xiě)字母命名,并在聲明時(shí)或構(gòu)造函數(shù)中進(jìn)行初始化。 -
修飾方法:
final
修飾的方法表示該方法是最終方法,子類(lèi)無(wú)法對(duì)其進(jìn)行重寫(xiě)。該方法在繼承關(guān)系中起到穩(wěn)定和約束的作用。 -
修飾類(lèi):
final
修飾的類(lèi)表示該類(lèi)是最終類(lèi),不能被繼承。該類(lèi)一般是不希望被修改或繼承的基礎(chǔ)類(lèi)。
-
-
static
關(guān)鍵字:-
修飾變量:
static
修飾的變量是靜態(tài)變量(類(lèi)變量),它屬于類(lèi)而不屬于對(duì)象。靜態(tài)變量在內(nèi)存中只有一個(gè)副本,被所有對(duì)象共享??梢酝ㄟ^(guò)類(lèi)名直接訪(fǎng)問(wèn)靜態(tài)變量,無(wú)需創(chuàng)建實(shí)例。 -
修飾方法:
static
修飾的方法是靜態(tài)方法(類(lèi)方法),它屬于類(lèi)而不屬于對(duì)象。靜態(tài)方法不依賴(lài)對(duì)象的實(shí)例,無(wú)法訪(fǎng)問(wèn)非靜態(tài)成員變量,只能訪(fǎng)問(wèn)類(lèi)的靜態(tài)成員??梢灾苯邮褂妙?lèi)名調(diào)用靜態(tài)方法。 -
修飾代碼塊:
static
修飾的代碼塊是靜態(tài)代碼塊,它在類(lèi)初始化時(shí)執(zhí)行,且只執(zhí)行一次。
-
主要區(qū)別:
-
final
關(guān)鍵字表示最終性,用于修飾不可變的變量、最終方法以及不可繼承的類(lèi),強(qiáng)調(diào)不可修改或擴(kuò)展的特性。 -
static
關(guān)鍵字表示靜態(tài)性,用于修飾類(lèi)級(jí)別的變量、方法和代碼塊,強(qiáng)調(diào)共享和類(lèi)級(jí)別的訪(fǎng)問(wèn)方式。
總之,final
和static
在Java中有不同的用途和含義,final
修飾的是最終性和不可修改的特性,而static
修飾的是靜態(tài)性和共享性的特性。
雖然final
和static
在Java中的用途和含義不同,但它們也有一些相同點(diǎn):
-
共享性:無(wú)論是
final
還是static
修飾的成員(變量、方法或代碼塊),它們都是類(lèi)級(jí)別的,即在類(lèi)的所有實(shí)例之間共享。 -
靜態(tài)訪(fǎng)問(wèn):
final
修飾的成員以及static
修飾的成員,都可以通過(guò)類(lèi)名直接訪(fǎng)問(wèn),不需要實(shí)例化對(duì)象。 -
聲明周期:
final
和static
修飾的成員都在類(lèi)初始化時(shí)創(chuàng)建,并且在整個(gè)程序的生命周期中保持不變。 -
常量:
final
修飾的變量可以用來(lái)表示常量,而靜態(tài)常量常常使用final
和static
一起修飾,用于表示類(lèi)級(jí)別的常量。
雖然這些相同點(diǎn)存在,但要注意的是,final
和static
的主要作用是不同的。final
主要用于表示最終性和不可修改性,而static
主要用于表示靜態(tài)性和共享性。它們的使用場(chǎng)景和語(yǔ)義上仍然有所區(qū)別。
接口和抽象類(lèi)有哪些區(qū)別
接口(Interface)和抽象類(lèi)(Abstract Class)是面向?qū)ο缶幊讨械膬蓚€(gè)重要概念,它們之間有以下區(qū)別:
-
定義方式:
-
接口:接口只能定義抽象方法和常量,不能包含具體的方法實(shí)現(xiàn)。接口中的方法默認(rèn)為
public abstract
,常量默認(rèn)為public static final
,不需要顯式聲明。 -
抽象類(lèi):抽象類(lèi)可以包含抽象方法和具體方法的聲明,也可以包含成員變量。抽象類(lèi)通過(guò)使用
abstract
關(guān)鍵字來(lái)聲明抽象方法,不需要顯式標(biāo)識(shí)成員變量和具體方法。
-
-
繼承關(guān)系:
-
接口:一個(gè)類(lèi)可以實(shí)現(xiàn)(implement)多個(gè)接口,通過(guò)關(guān)鍵字
implements
來(lái)實(shí)現(xiàn)接口。接口之間可以實(shí)現(xiàn)多繼承,一個(gè)接口可以繼承多個(gè)其他接口。一個(gè)類(lèi)實(shí)現(xiàn)接口時(shí),必須實(shí)現(xiàn)接口中定義的所有方法。 -
抽象類(lèi):一個(gè)類(lèi)可以繼承(extends)一個(gè)抽象類(lèi),通過(guò)關(guān)鍵字
extends
來(lái)繼承抽象類(lèi)。抽象類(lèi)之間只能實(shí)現(xiàn)單繼承,一個(gè)抽象類(lèi)只能繼承一個(gè)其他類(lèi)或抽象類(lèi)。子類(lèi)繼承抽象類(lèi)時(shí),必須實(shí)現(xiàn)抽象類(lèi)中的抽象方法。
-
-
實(shí)例化對(duì)象:
-
接口:接口不能直接被實(shí)例化,即不能通過(guò)
new
關(guān)鍵字來(lái)創(chuàng)建接口的對(duì)象。但可以通過(guò)實(shí)現(xiàn)接口的類(lèi)來(lái)創(chuàng)建對(duì)象,并將其賦給接口類(lèi)型的引用。 -
抽象類(lèi):抽象類(lèi)不能直接被實(shí)例化,即不能通過(guò)
new
關(guān)鍵字來(lái)創(chuàng)建抽象類(lèi)的對(duì)象。但可以通過(guò)實(shí)現(xiàn)抽象類(lèi)的子類(lèi)來(lái)創(chuàng)建對(duì)象,并將其賦給抽象類(lèi)類(lèi)型的引用。
-
-
特殊功能:
-
接口:接口可以用于實(shí)現(xiàn)多態(tài),通過(guò)接口類(lèi)型的引用來(lái)調(diào)用實(shí)現(xiàn)類(lèi)的方法。
-
抽象類(lèi):抽象類(lèi)可以包含抽象方法和具體方法的實(shí)現(xiàn),從而提供默認(rèn)行為給子類(lèi)使用。子類(lèi)可以選擇性地實(shí)現(xiàn)抽象方法,對(duì)于不需要修改的方法,可以繼承抽象類(lèi)中的具體實(shí)現(xiàn)。
-
總的來(lái)說(shuō),接口和抽象類(lèi)都是用來(lái)實(shí)現(xiàn)多態(tài)和約束子類(lèi)的機(jī)制,但在定義方式、繼承關(guān)系、實(shí)例化對(duì)象和特殊功能等方面存在一些區(qū)別。根據(jù)具體的需求和設(shè)計(jì)場(chǎng)景,可以選擇使用接口或抽象類(lèi)來(lái)實(shí)現(xiàn)代碼的靈活性和重用性。
相同:
1.不能夠?qū)嵗?/p>
2.可以將抽象類(lèi)和接口類(lèi)型作為引用類(lèi)型
3.一個(gè)類(lèi)如果繼承了某個(gè)抽象類(lèi)或者實(shí)現(xiàn)了某個(gè)接口都需要對(duì)其中的抽象方法全部進(jìn)行實(shí)現(xiàn),否則該類(lèi)仍然需要
被聲明為抽象類(lèi)
怎樣聲明一個(gè)類(lèi)不會(huì)被繼承,什么場(chǎng)景下會(huì)用
如果一個(gè)類(lèi)被final修飾,此類(lèi)不可以有子類(lèi),不能被其它類(lèi)繼承,如果一個(gè)中的所有方法都沒(méi)有重寫(xiě)的需要,當(dāng)前類(lèi)沒(méi)有子類(lèi)也罷,就可以使用final修飾類(lèi)。
深拷貝和淺拷貝
Java中的拷貝操作分為深拷貝和淺拷貝兩種方式,它們的區(qū)別在于拷貝過(guò)程中是否創(chuàng)建新的對(duì)象以及如何復(fù)制對(duì)象的成員。
淺拷貝(Shallow Copy): 淺拷貝是一種簡(jiǎn)單的拷貝方式,它創(chuàng)建一個(gè)新的對(duì)象,然后將原始對(duì)象的字段值復(fù)制到新對(duì)象中。但是,如果字段值是引用類(lèi)型,淺拷貝只會(huì)復(fù)制引用,而不是創(chuàng)建一個(gè)新的引用對(duì)象。因此,新對(duì)象和原始對(duì)象會(huì)共享相同的引用對(duì)象,對(duì)其中一個(gè)對(duì)象所做的修改會(huì)影響另一個(gè)對(duì)象。
淺拷貝(Shallow Copy)是指在拷貝對(duì)象時(shí),只復(fù)制對(duì)象本身和對(duì)象中的基本數(shù)據(jù)類(lèi)型成員,而不復(fù)制對(duì)象中的引用類(lèi)型成員。簡(jiǎn)單來(lái)說(shuō),淺拷貝只是拷貝了對(duì)象的引用,而不是創(chuàng)建一個(gè)新的獨(dú)立對(duì)象。
以下是一個(gè)Java代碼示例,展示了如何進(jìn)行淺拷貝:
class Person implements Cloneable {private String name;private int age;private Address address; // 引用類(lèi)型成員變量 ?public Person(String name, int age, Address address) {this.name = name;this.age = age;this.address = address;} ?public void setAddress(Address address) {this.address = address;} ?@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();} ?@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";} } ? class Address {private String city; ?public Address(String city) {this.city = city;} ?@Overridepublic String toString() {return "Address [city=" + city + "]";} } ? public class ShallowCopyExample {public static void main(String[] args) throws CloneNotSupportedException {Address address = new Address("New York");Person person1 = new Person("John", 25, address); ?// 淺拷貝Person person2 = (Person) person1.clone(); ?// 修改person2的成員變量person2.setName("Mike");person2.setAddress(new Address("London")); ?System.out.println("person1: " + person1);System.out.println("person2: " + person2);} }
在上述示例中,Person
類(lèi)包含了一個(gè)引用類(lèi)型的成員變量address
,而Address
類(lèi)只有一個(gè)簡(jiǎn)單的city
屬性。通過(guò)調(diào)用clone()
方法進(jìn)行淺拷貝,將person1
對(duì)象的內(nèi)容復(fù)制到person2
對(duì)象。當(dāng)修改person2
對(duì)象的成員變量時(shí),person1
對(duì)象的成員變量也會(huì)發(fā)生變化,因?yàn)樗鼈児蚕硗粋€(gè)引用類(lèi)型的成員變量。
輸出結(jié)果如下:
person1: Person [name=John, age=25, address=Address [city=London]] person2: Person [name=Mike, age=25, address=Address [city=London]]
可以看到,person2
對(duì)象修改了address
引用的內(nèi)容,導(dǎo)致person1
對(duì)象的address
也發(fā)生了變化。這就是淺拷貝的特點(diǎn),只復(fù)制了引用,而沒(méi)有創(chuàng)建新的獨(dú)立對(duì)象。
深拷貝(Deep Copy): 深拷貝是一種更為復(fù)雜的拷貝方式,它不僅創(chuàng)建一個(gè)新的對(duì)象,還會(huì)遞歸復(fù)制對(duì)象的所有引用類(lèi)型字段,包括它們所引用的對(duì)象,以保證復(fù)制后的對(duì)象與原始對(duì)象完全獨(dú)立。因此,新對(duì)象和原始對(duì)象擁有各自獨(dú)立的引用對(duì)象,互不影響。
在Java中,實(shí)現(xiàn)深拷貝的方式有多種,包括:
-
使用實(shí)現(xiàn)了Cloneable接口的clone方法來(lái)實(shí)現(xiàn)深拷貝。需要在被拷貝的類(lèi)中重寫(xiě)clone方法,并在該方法中對(duì)引用類(lèi)型字段進(jìn)行深度拷貝。
-
使用序列化和反序列化來(lái)實(shí)現(xiàn)深拷貝。通過(guò)將對(duì)象序列化為字節(jié)流,然后再進(jìn)行反序列化,可以創(chuàng)建一個(gè)新的獨(dú)立對(duì)象。
-
使用第三方庫(kù),比如Apache Commons Lang中的SerializationUtils類(lèi)或者Google Gson,它們提供了更便捷的深拷貝方式。
需要注意的是,并非所有的類(lèi)都是可深拷貝的,如果類(lèi)的字段包含不可變對(duì)象或者其他具有深度狀態(tài)的對(duì)象,可能需要特殊處理來(lái)確保新對(duì)象的獨(dú)立性。 同時(shí),在進(jìn)行對(duì)象拷貝時(shí),還需要考慮性能和內(nèi)存使用的問(wèn)題,因?yàn)樯羁截惪赡苄枰f歸地復(fù)制整個(gè)對(duì)象圖,可能會(huì)導(dǎo)致性能和內(nèi)存開(kāi)銷(xiāo)的增加。因此,在選擇拷貝方式時(shí),需要根據(jù)具體需求和場(chǎng)景來(lái)決定使用淺拷貝還是深拷貝。
序列化
Java序列化是指將對(duì)象轉(zhuǎn)化為字節(jié)流的過(guò)程,可以將對(duì)象保存到文件、傳輸?shù)骄W(wǎng)絡(luò)或者在進(jìn)程間進(jìn)行通信。反序列化則是將字節(jié)流轉(zhuǎn)化為對(duì)象的過(guò)程。Java的序列化機(jī)制主要通過(guò)ObjectOutputStream和ObjectInputStream來(lái)實(shí)現(xiàn)。
在以下情況下,我們通常需要實(shí)現(xiàn)Java序列化:
-
對(duì)象持久化:當(dāng)我們需要將對(duì)象保存到磁盤(pán)或數(shù)據(jù)庫(kù)中,以便之后重新讀取和恢復(fù)時(shí),可以使用Java序列化。通過(guò)將對(duì)象轉(zhuǎn)為字節(jié)流,我們可以將其寫(xiě)入文件或數(shù)據(jù)庫(kù)中。這對(duì)于需要長(zhǎng)期保存對(duì)象狀態(tài)的應(yīng)用場(chǎng)景非常有用,比如緩存或數(shù)據(jù)存儲(chǔ)。
-
進(jìn)程間通信:當(dāng)我們需要在不同的Java進(jìn)程之間進(jìn)行通信時(shí),可以使用Java序列化來(lái)傳遞對(duì)象。通過(guò)將對(duì)象轉(zhuǎn)為字節(jié)流,我們可以將其傳輸給其他進(jìn)程,并在接收端進(jìn)行反序列化恢復(fù)為對(duì)象。這在分布式系統(tǒng)、遠(yuǎn)程調(diào)用以及消息傳遞等場(chǎng)景下有廣泛應(yīng)用。
需要注意的是,為了使對(duì)象可以被序列化,相關(guān)的類(lèi)需要實(shí)現(xiàn)Serializable接口,這是一個(gè)標(biāo)記接口,僅起到標(biāo)識(shí)該類(lèi)可以被序列化的作用。同時(shí),類(lèi)中的所有域也必須是可序列化的,即要么是基本類(lèi)型,要么是實(shí)現(xiàn)了Serializable接口的對(duì)象。
然而,并不是所有的場(chǎng)景都適合使用Java序列化。在一些需要高性能、傳輸大量數(shù)據(jù)或數(shù)據(jù)結(jié)構(gòu)頻繁改變的情況下,可能不適合使用序列化來(lái)傳輸對(duì)象,而選擇其他的序列化方法或者數(shù)據(jù)交換格式。此外,需要特別注意序列化對(duì)版本升級(jí)的兼容性問(wèn)題,因?yàn)樾蛄谢膶?duì)象需要保證版本一致,否則可能導(dǎo)致反序列化失敗。
反射介紹
反射(Reflection)是指在程序運(yùn)行時(shí)動(dòng)態(tài)地獲取、操作和修改類(lèi)或?qū)ο蟮膶傩?、方法和?gòu)造函數(shù)等信息的能力。通過(guò)反射,我們可以在運(yùn)行時(shí)檢查類(lèi)、實(shí)例化對(duì)象、調(diào)用方法、獲取和修改字段的值,以及操作構(gòu)造函數(shù)等。
Java中的反射API位于java.lang.reflect
包下,提供了一組類(lèi)和接口,用于實(shí)現(xiàn)反射功能。常用的反射類(lèi)和接口包括以下幾個(gè):
-
Class
類(lèi):表示一個(gè)類(lèi)或接口的運(yùn)行時(shí)對(duì)象,可以獲取類(lèi)的構(gòu)造函數(shù)、方法、字段等信息。 -
Constructor
類(lèi):表示類(lèi)的構(gòu)造函數(shù),用于創(chuàng)建類(lèi)的實(shí)例對(duì)象。 -
Method
類(lèi):表示類(lèi)的方法,可以用于調(diào)用方法并獲取方法的信息。 -
Field
類(lèi):表示類(lèi)的字段,可以用于獲取和修改字段的值。
反射的主要應(yīng)用場(chǎng)景包括:
-
動(dòng)態(tài)加載類(lèi):在運(yùn)行時(shí)通過(guò)類(lèi)名字符串來(lái)動(dòng)態(tài)加載并實(shí)例化對(duì)象。
-
運(yùn)行時(shí)獲取類(lèi)的信息:獲取類(lèi)的構(gòu)造函數(shù)、方法、字段等信息,包括注解、修飾符等。
-
動(dòng)態(tài)調(diào)用方法:在運(yùn)行時(shí)通過(guò)方法名和參數(shù)類(lèi)型,動(dòng)態(tài)調(diào)用類(lèi)的方法。
-
對(duì)私有成員的訪(fǎng)問(wèn):通過(guò)反射可以獲取和修改類(lèi)的私有字段和方法。
-
生成動(dòng)態(tài)代理:使用反射可以在運(yùn)行時(shí)生成代理對(duì)象,并在代理對(duì)象中增加額外的邏輯。
使用反射需要注意以下幾點(diǎn):
-
反射操作相對(duì)于直接調(diào)用代碼的執(zhí)行效率較低,因?yàn)樯婕暗讲檎?、解析和?zhí)行步驟。
-
反射破壞了封裝性,可以訪(fǎng)問(wèn)和修改原本無(wú)法訪(fǎng)問(wèn)的成員,因此需要謹(jǐn)慎使用。
-
由于反射在編譯期無(wú)法進(jìn)行類(lèi)型檢查,可能會(huì)在運(yùn)行時(shí)拋出未檢查的異常,需要進(jìn)行異常處理和類(lèi)型判斷。
總結(jié)來(lái)說(shuō),反射是一種強(qiáng)大而靈活的機(jī)制,提供了在運(yùn)行時(shí)動(dòng)態(tài)操作類(lèi)和對(duì)象的能力。它在某些情況下能夠簡(jiǎn)化代碼編寫(xiě)和提供更大的靈活性,但需要慎重使用,并考慮其可能帶來(lái)的性能和安全性方面的影響。
反射的步驟反射的步驟如下。
使用反射的步驟主要包括以下幾個(gè):
-
獲取類(lèi)的
Class
對(duì)象:首先需要獲取目標(biāo)類(lèi)的Class
對(duì)象,可以通過(guò)類(lèi)名、對(duì)象實(shí)例或者Class類(lèi)的forName()
方法來(lái)獲取。// 通過(guò)類(lèi)名獲取Class對(duì)象 Class<?> clazz = MyClass.class; ? // 通過(guò)對(duì)象實(shí)例獲取Class對(duì)象 MyClass obj = new MyClass(); Class<?> clazz = obj.getClass(); ? // 通過(guò)類(lèi)的全限定名獲取Class對(duì)象 Class<?> clazz = Class.forName("com.example.MyClass");
-
獲取構(gòu)造函數(shù)對(duì)象(可選):如果需要通過(guò)構(gòu)造函數(shù)創(chuàng)建對(duì)象,可以通過(guò)
Class
對(duì)象的getConstructor()
、getDeclaredConstructor()
方法獲取目標(biāo)構(gòu)造函數(shù)對(duì)象。// 獲取指定參數(shù)類(lèi)型的公共構(gòu)造函數(shù)對(duì)象 Constructor<?> constructor = clazz.getConstructor(String.class, int.class); ? // 獲取所有參數(shù)類(lèi)型的構(gòu)造函數(shù)對(duì)象(包括私有構(gòu)造函數(shù)) Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class); ? // 禁用訪(fǎng)問(wèn)檢查,允許訪(fǎng)問(wèn)私有構(gòu)造函數(shù) constructor.setAccessible(true);
-
創(chuàng)建對(duì)象(可選):如果獲取了構(gòu)造函數(shù)對(duì)象,可以使用
Constructor
對(duì)象的newInstance()
方法創(chuàng)建目標(biāo)類(lèi)的實(shí)例。// 使用構(gòu)造函數(shù)對(duì)象創(chuàng)建對(duì)象實(shí)例 MyClass obj = (MyClass) constructor.newInstance("example", 123);
-
獲取方法對(duì)象:通過(guò)
Class
對(duì)象的getMethod()
、getDeclaredMethod()
方法獲取目標(biāo)方法對(duì)象。// 獲取指定名稱(chēng)和參數(shù)類(lèi)型的公共方法對(duì)象 Method method = clazz.getMethod("methodName", int.class, String.class); ? // 獲取所有名稱(chēng)和參數(shù)類(lèi)型的方法對(duì)象(包括私有方法) Method method = clazz.getDeclaredMethod("methodName", int.class, String.class); ? // 禁用訪(fǎng)問(wèn)檢查,允許訪(fǎng)問(wèn)私有方法 method.setAccessible(true);
-
調(diào)用方法:通過(guò)方法對(duì)象的
invoke()
方法調(diào)用目標(biāo)方法。// 調(diào)用方法 Object result = method.invoke(obj, 123, "example");
-
獲取和設(shè)置字段的值:通過(guò)
Class
對(duì)象的getField()
、getDeclaredField()
方法獲取目標(biāo)字段對(duì)象。// 獲取公共字段對(duì)象 Field field = clazz.getField("fieldName"); ? // 獲取所有字段對(duì)象(包括私有字段) Field field = clazz.getDeclaredField("fieldName"); ? // 禁用訪(fǎng)問(wèn)檢查,允許訪(fǎng)問(wèn)私有字段 field.setAccessible(true); ? // 獲取字段的值 Object value = field.get(obj); ? // 設(shè)置字段的值 field.set(obj, newValue);
注意:在使用反射時(shí),需要注意訪(fǎng)問(wèn)修飾符(public、private等),需禁用訪(fǎng)問(wèn)檢查才能訪(fǎng)問(wèn)和修改私有成員。此外,還需要處理可能拋出的異常,如找不到構(gòu)造函數(shù)、方法或字段等。
創(chuàng)建對(duì)象的幾種方式
在Java中,我們可以使用以下幾種方式來(lái)創(chuàng)建對(duì)象:
-
使用new關(guān)鍵字:
ClassName obj = new ClassName();
這是最常見(jiàn)的創(chuàng)建對(duì)象的方式。通過(guò)使用new關(guān)鍵字,我們可以在堆中分配內(nèi)存,并創(chuàng)建一個(gè)新的對(duì)象。
-
使用Class的newInstance()方法:
ClassName obj = (ClassName) Class.forName("ClassName").newInstance();
Class.forName("ClassName")
會(huì)返回一個(gè)代表ClassName類(lèi)的Class對(duì)象,然后通過(guò)調(diào)用newInstance()
方法來(lái)創(chuàng)建該類(lèi)的對(duì)象。需要注意的是,這種方式要求ClassName類(lèi)有一個(gè)無(wú)參的構(gòu)造函數(shù),否則會(huì)拋出InstantiationException異常。
-
使用Constructor類(lèi)的newInstance()方法:
Constructor<ClassName> constructor = ClassName.class.getConstructor(); ClassName obj = constructor.newInstance();
這種方式使用反射的方式來(lái)創(chuàng)建對(duì)象。首先,我們獲取到ClassName類(lèi)的Constructor對(duì)象,然后使用newInstance()
方法來(lái)創(chuàng)建對(duì)象。同樣需要注意,這種方式要求ClassName類(lèi)有一個(gè)無(wú)參的構(gòu)造函數(shù)。
-
使用clone()方法:
ClassName obj = (ClassName) otherObj.clone();
這種方式是通過(guò)對(duì)象的clone()方法來(lái)創(chuàng)建一個(gè)對(duì)象的副本。需要注意的是,類(lèi)必須實(shí)現(xiàn)Cloneable接口并重寫(xiě)clone()方法,否則會(huì)拋出CloneNotSupportedException異常。
-
使用反序列化:
ObjectInputStream in = new ObjectInputStream(new FileInputStream("filename")); ClassName obj = (ClassName) in.readObject();
通過(guò)將對(duì)象序列化到文件中,然后再反序列化回來(lái)來(lái)創(chuàng)建對(duì)象。需要注意的是,類(lèi)必須實(shí)現(xiàn)Serializable接口。
這些是創(chuàng)建對(duì)象的常見(jiàn)方式,在不同的場(chǎng)景下可以選擇適合的方式來(lái)創(chuàng)建對(duì)象。每種方式都有其適用的情況和注意事項(xiàng)。
@Contended注解有什么用
這個(gè)注解是為了解決偽共享問(wèn)題而存在的
Java緩存?zhèn)喂蚕?#xff08;Cache False Sharing)是指多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)不同變量,但這些變量被存儲(chǔ)在相鄰的緩存行中,導(dǎo)致在多線(xiàn)程并發(fā)更新變量時(shí),由于緩存一致性協(xié)議的原因,會(huì)頻繁地使緩存行無(wú)效,降低了性能。
這個(gè)問(wèn)題通常出現(xiàn)在多線(xiàn)程環(huán)境中,當(dāng)多個(gè)線(xiàn)程同時(shí)修改一個(gè)共享的數(shù)據(jù)結(jié)構(gòu)中的不同變量時(shí),由于緩存行的對(duì)齊以及緩存一致性的機(jī)制,每個(gè)線(xiàn)程更新變量時(shí),可能會(huì)同時(shí)使得其他線(xiàn)程緩存的行無(wú)效,導(dǎo)致額外的緩存同步開(kāi)銷(xiāo)。
(出現(xiàn)在緩存L1上)
這個(gè)注解會(huì)讓當(dāng)前類(lèi)的屬性,獨(dú)占一個(gè)緩存行。在共享數(shù)據(jù)結(jié)構(gòu)的變量之間增加一些無(wú)意義的填充變量,使得相鄰的變量在不同的緩存行中,從而避免偽共享。
Java中有四種引用類(lèi)型
-
強(qiáng)引用(Strong Reference):最常見(jiàn)的引用類(lèi)型,也是默認(rèn)的引用類(lèi)型。使用強(qiáng)引用,一個(gè)對(duì)象不會(huì)被垃圾回收器回收,只有在沒(méi)有任何強(qiáng)引用指向它時(shí),才會(huì)被回收。
-
軟引用(Soft Reference):通過(guò)軟引用,可以讓對(duì)象在內(nèi)存不足時(shí)被回收。垃圾回收器在進(jìn)行回收時(shí),通常會(huì)保留軟引用對(duì)象,只有當(dāng)內(nèi)存不足時(shí),才會(huì)回收這些對(duì)象。
Object referent = new Object();
SoftReference<Object> softReference = new SoftReference<>(referent);
-
弱引用(Weak Reference):使用弱引用,可以讓對(duì)象在下一次垃圾回收時(shí)被回收。垃圾回收器在回收時(shí),不論內(nèi)存是否充足,都會(huì)回收掉只有弱引用指向的對(duì)象。
-
虛引用(Phantom Reference):虛引用是最弱的一種引用類(lèi)型,它的存在幾乎沒(méi)有實(shí)際的意義??梢杂锰撘脕?lái)跟蹤對(duì)象被垃圾回收器回收的過(guò)程,無(wú)法通過(guò)虛引用訪(fǎng)問(wèn)對(duì)象,需要配合引用隊(duì)列(ReferenceQueue)一起使用。
這四種引用類(lèi)型的關(guān)系是:強(qiáng)引用 > 軟引用 > 弱引用 > 虛引用。對(duì)象在沒(méi)有任何引用指向時(shí),會(huì)被回收。軟引用和弱引用可以讓對(duì)象在內(nèi)存不足時(shí)被回收,虛引用可以讓對(duì)象在被回收的同時(shí)收到通知。
使用不同的引用類(lèi)型,可以更靈活地控制對(duì)象的生命周期和回收時(shí)機(jī),適應(yīng)不同的內(nèi)存管理需求。需要注意的是,虛引用的使用相對(duì)較少,一般在某些高級(jí)的內(nèi)存管理場(chǎng)景中才會(huì)涉及。
虛引用
虛引用(Phantom Reference)是Java中最弱的一種引用類(lèi)型。與其他引用類(lèi)型不同,虛引用的存在幾乎沒(méi)有實(shí)際的意義,它主要用于跟蹤對(duì)象被垃圾回收器回收的過(guò)程。
以下是虛引用的一些特點(diǎn)和使用場(chǎng)景:
-
虛引用的創(chuàng)建:虛引用可以通過(guò)創(chuàng)建
PhantomReference
對(duì)象來(lái)實(shí)現(xiàn)。虛引用對(duì)象需要傳入一個(gè)引用隊(duì)列(ReferenceQueue),用于在對(duì)象被回收時(shí)接收通知。Object referent = new Object(); ReferenceQueue<Object> queue = new ReferenceQueue<>(); PhantomReference<Object> phantomReference = new PhantomReference<>(referent, queue);
-
無(wú)法通過(guò)虛引用訪(fǎng)問(wèn)對(duì)象:與其他引用不同,虛引用無(wú)法通過(guò)
get()
方法獲得對(duì)應(yīng)的對(duì)象。任何時(shí)候,使用虛引用的get()
方法都會(huì)返回null
。Object obj = phantomReference.get(); // 返回null
-
接收回收通知:當(dāng)對(duì)象被垃圾回收器回收時(shí),虛引用所關(guān)聯(lián)的對(duì)象將被放入引用隊(duì)列中??梢酝ㄟ^(guò)引用隊(duì)列來(lái)獲取被回收的對(duì)象信息,進(jìn)行相關(guān)的處理操作。
ReferenceQueue<Object> queue = new ReferenceQueue<>(); // ... PhantomReference<Object> phantomReference = new PhantomReference<>(referent, queue); // ... Reference<?> reference = queue.poll(); if (reference != null) {// 執(zhí)行相關(guān)處理操作 }
-
虛引用的應(yīng)用場(chǎng)景:虛引用的應(yīng)用場(chǎng)景比較少見(jiàn),一般在一些高級(jí)的內(nèi)存管理場(chǎng)景中使用。例如,你可以使用虛引用來(lái)實(shí)現(xiàn)一些本地資源的釋放,在對(duì)象被垃圾回收時(shí)進(jìn)行清理操作,比如關(guān)閉文件句柄、釋放網(wǎng)絡(luò)連接等。
class ResourceCleaner {private ReferenceQueue<Object> queue = new ReferenceQueue<>(); ?// 注冊(cè)虛引用,關(guān)聯(lián)清理操作public void register(Object resource, Runnable cleanupAction) {PhantomReference<Object> phantomReference = new PhantomReference<>(resource, queue);// ...} ?// 在適當(dāng)?shù)臅r(shí)機(jī)執(zhí)行清理操作public void cleanup() {Reference<?> reference = queue.poll();while (reference != null) {// 執(zhí)行相關(guān)清理操作reference.clear();// ...reference = queue.poll();}} }
需要注意的是,因?yàn)樘撘玫拇嬖趲缀鯖](méi)有實(shí)際的意義,開(kāi)發(fā)中使用虛引用的場(chǎng)景較少,而且需要謹(jǐn)慎使用。錯(cuò)誤使用虛引用可能會(huì)導(dǎo)致一些不可預(yù)測(cè)的問(wèn)題,因此在使用虛引用時(shí)應(yīng)仔細(xì)評(píng)估和規(guī)劃。
Java中鎖的分類(lèi)
在Java中,鎖可以按照以下幾種分類(lèi)標(biāo)準(zhǔn)來(lái)進(jìn)行劃分:
-
公平鎖與非公平鎖: 公平鎖是指多個(gè)線(xiàn)程按照請(qǐng)求的順序獲取鎖,而非公平鎖則沒(méi)有這樣的保證。在公平鎖中,線(xiàn)程們按照先來(lái)先服務(wù)的原則排隊(duì)獲取鎖;而在非公平鎖中,鎖會(huì)傾向于允許當(dāng)前已拿到鎖的線(xiàn)程再次獲取鎖。
-
互斥鎖與共享鎖: 互斥鎖(Exclusive Lock)是一種獨(dú)占鎖,它只允許一個(gè)線(xiàn)程在同一時(shí)間獲取鎖,并阻止其他線(xiàn)程訪(fǎng)問(wèn)被保護(hù)資源。而共享鎖(Shared Lock)允許多個(gè)線(xiàn)程同時(shí)獲取鎖,并共享被保護(hù)資源的訪(fǎng)問(wèn)權(quán)限?;コ怄i用于保護(hù)臨界區(qū),而共享鎖用于并發(fā)讀操作。
-
寫(xiě)鎖與讀寫(xiě)鎖: 寫(xiě)鎖與讀寫(xiě)鎖適用于對(duì)讀寫(xiě)操作進(jìn)行區(qū)分的場(chǎng)景。寫(xiě)鎖(Write Lock)是獨(dú)占鎖,只允許一個(gè)線(xiàn)程進(jìn)行寫(xiě)操作,并且阻塞其他線(xiàn)程的讀寫(xiě)操作。讀寫(xiě)鎖(ReadWrite Lock)允許多個(gè)線(xiàn)程同時(shí)進(jìn)行讀操作,但只允許一個(gè)線(xiàn)程進(jìn)行寫(xiě)操作。讀操作之間不會(huì)互斥,讀與寫(xiě)操作之間互斥。
-
悲觀鎖與樂(lè)觀鎖: 悲觀鎖(Pessimistic Locking)是一種保守策略,它假設(shè)會(huì)有其他線(xiàn)程對(duì)共享資源進(jìn)行修改,因此在訪(fǎng)問(wèn)共享資源之前進(jìn)行加鎖。悲觀鎖的典型例子就是 synchronized 關(guān)鍵字和 ReentrantLock 類(lèi)。相反,樂(lè)觀鎖(Optimistic Locking)假設(shè)并發(fā)沖突很少發(fā)生,不主動(dòng)加鎖,而是在更新操作時(shí)檢查數(shù)據(jù)是否被其他線(xiàn)程修改過(guò)。
請(qǐng)注意,這些分類(lèi)標(biāo)準(zhǔn)并不是嚴(yán)格獨(dú)立的,而是相互關(guān)聯(lián)的,同一個(gè)鎖可能涵蓋不同分類(lèi)標(biāo)準(zhǔn)的特性。在實(shí)際應(yīng)用中,根據(jù)具體需求,可以選擇合適的鎖類(lèi)型來(lái)實(shí)現(xiàn)線(xiàn)程同步和資源訪(fǎng)問(wèn)控制。
Java中==和equals有哪些區(qū)別
equals 和== 最大的區(qū)別是一個(gè)是方法一個(gè)是運(yùn)算符。
==:如果比較的對(duì)象是基本數(shù)據(jù)類(lèi)型,則比較的是數(shù)值是否相等;如果比較的是引用數(shù)據(jù)類(lèi)型,則比較的是對(duì)象
的地址值是否相等。
equals():用來(lái)比較方法兩個(gè)對(duì)象的內(nèi)容是否相等。
注意:equals 方法不能用于基本數(shù)據(jù)類(lèi)型的變量,如果沒(méi)有對(duì) equals 方法進(jìn)行重寫(xiě),則比較的是引用類(lèi)型的變量所指向的對(duì)象的地址。
String、StringBuffer、StringBuilder區(qū)別及使用場(chǎng)景
String、StringBuffer和StringBuilder都是Java中用于處理字符串的類(lèi),它們?cè)谛阅?、線(xiàn)程安全性和可變性方面有所不同。
-
String(不可變字符串):
-
String對(duì)象是不可變的,一旦創(chuàng)建就不能被修改。每次對(duì)字符串進(jìn)行操作(連接、替換等),都會(huì)創(chuàng)建一個(gè)新的String對(duì)象。
-
因?yàn)樽址遣豢勺兊?#xff0c;所以String對(duì)象是線(xiàn)程安全的。
-
適合于字符串不經(jīng)常變化的場(chǎng)景,例如作為方法參數(shù)、類(lèi)屬性等。
-
-
StringBuffer(可變字符串,線(xiàn)程安全):
-
StringBuffer對(duì)象是可變的,可以進(jìn)行字符串的修改、追加、插入和刪除等操作。它是線(xiàn)程安全的,因此適用于多線(xiàn)程環(huán)境。
-
每次對(duì)StringBuffer的操作都是在原有對(duì)象的基礎(chǔ)上進(jìn)行的,不會(huì)創(chuàng)建新的對(duì)象。
-
適合于字符串經(jīng)常需要變化、需要線(xiàn)程安全的場(chǎng)景,例如在多線(xiàn)程環(huán)境下進(jìn)行字符串處理的情況。
-
-
StringBuilder(可變字符串,非線(xiàn)程安全):
-
StringBuilder對(duì)象也是可變的,可以進(jìn)行字符串的修改、追加、插入和刪除等操作。與StringBuffer不同的是,StringBuilder是非線(xiàn)程安全的。
-
每次對(duì)StringBuilder的操作都是在原有對(duì)象的基礎(chǔ)上進(jìn)行的,不會(huì)創(chuàng)建新的對(duì)象。
-
適合于字符串經(jīng)常需要變化,且在單線(xiàn)程環(huán)境下進(jìn)行字符串處理的場(chǎng)景,例如在循環(huán)中進(jìn)行大量字符串拼接的情況。
-
-
String類(lèi)是不可變的,一旦創(chuàng)建就不能修改,每次修改都會(huì)創(chuàng)建一個(gè)新的對(duì)象;
-
StringBuffer和StringBuilder類(lèi)是可變的,可以隨意修改其中的內(nèi)容,不會(huì)創(chuàng)建新的對(duì)象。
-
StringBuffer類(lèi)是線(xiàn)程安全的,而StringBuilder類(lèi)是非線(xiàn)程安全的。
String類(lèi)和常量池
String對(duì)象的兩種創(chuàng)建方式
String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1==str2);//false
這兩種不同的創(chuàng)建方法是有區(qū)別的,第一種方式是在常量池中拿對(duì)象,第二種直接在堆內(nèi)存中創(chuàng)建一個(gè)新對(duì)象(如果常量池中沒(méi)有的話(huà)會(huì)在常量池里創(chuàng)建一個(gè))。
記住:只要使用new方法,便需要?jiǎng)?chuàng)建新的對(duì)象。
3.2:String類(lèi)型的常量池比較特殊。
它的主要使用方法有兩種:
1、直接使用雙引號(hào)聲明出來(lái)的對(duì)象會(huì)直接存儲(chǔ)到常量池中。
2、如果不是雙引號(hào)聲明的String對(duì)象,可以使用String提供的intern方法。String.intern()是一個(gè)Native方法,它的作用是:如果運(yùn)行時(shí)常量池中已經(jīng)包含一個(gè)等于此String對(duì)象內(nèi)容的字符串,則返回常量池中該字符串的引用;如果沒(méi)有則在常量池中創(chuàng)建與此String內(nèi)容相同的字符串,并返回常量池中創(chuàng)建字符串的引用。
JDK6和JDK7的區(qū)別:
JDK6:
1、如果常量池中有,則不會(huì)放入。返回已有的常量池中的對(duì)象地址
2、如果沒(méi)有,則將對(duì)象復(fù)制一份,并將放入到常量池中,并放回對(duì)象地址
JDK7之后:
1、如果常量池中有,則不會(huì)放入。返回已有的常量池中的對(duì)象地址
2、如果沒(méi)有,則將對(duì)象的引用地址復(fù)制一份,放入到常量池中,并返回常量池中的引用地址
public class StringTest2 {public static void main(String[] args) {String s = new String("a")+new String("b");String s2 =s.intern();System.out.println(s2 =="ab");System.out.println(s =="ab");}
}
DK6下輸出:true false
JDK7之后輸出:true true
看到上面的結(jié)果可能還存在疑慮,我們接著分析一下1、String s = "ab";創(chuàng)建了一個(gè)對(duì)象,在編譯已經(jīng)確定要放入常量池 2、String s = “a”+ “b”;常量字符串拼接,底層優(yōu)化為“ab”,和上面一樣也生成一個(gè)對(duì)象。 3、String s = new String("ab");創(chuàng)建了兩個(gè)對(duì)象,通過(guò)查看字節(jié)碼文件:
一個(gè)對(duì)象時(shí)new出來(lái)的另外一個(gè)對(duì)象是字符串常量池中的對(duì)象“ab”,字節(jié)碼指令:ldc 4、String s = new String("a") + new String("b");字節(jié)碼顯示創(chuàng)建了6個(gè)對(duì)象
,
1、new StringBuilder對(duì)象
2、new String("a")
3、常量池中的a4、new String("b")
5、常量池中的b深入刨析StringBuilder的toString,調(diào)用的是new String(char[])
6、new String("ab"),此時(shí)常量池中并沒(méi)有ab這個(gè)字符串強(qiáng)調(diào)一下toString()的調(diào)用,
先從常量池中找,沒(méi)有在常量池中生成“ab” 再看看相關(guān)字符串的內(nèi)容代碼
String s1 = new String("計(jì)算機(jī)");
String s2 = s1.intern();
String s3 = "計(jì)算機(jī)";
System.out.println(s2);//計(jì)算機(jī)
System.out.println(s1 == s2);//false,因?yàn)橐粋€(gè)是堆內(nèi)存中的String對(duì)象一個(gè)是常量池中的String對(duì)象,
System.out.println(s3 == s2);//true,因?yàn)閮蓚€(gè)都是常量池中的String對(duì)象String str1 = "str";
String str2 = "ing";String str3 = "str" + "ing";//常量池中的對(duì)象
String str4 = str1 + str2; //在堆上創(chuàng)建的新的對(duì)象
String str5 = "string";//常量池中的對(duì)象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
盡量避免多個(gè)字符串拼接,因?yàn)檫@樣會(huì)生成新對(duì)象。如果需要改變字符串的話(huà)可以使用StringBuffer和StringBuilder
Java代理的幾種實(shí)現(xiàn)方式
靜態(tài)代理
,只能靜態(tài)的代理某些類(lèi)或者某些方法,不推薦使用,功能比較弱,但是編碼簡(jiǎn)單
// 定義一個(gè)共同的接口 interface Calculator {int add(int a, int b); } ? // 實(shí)現(xiàn)真正的計(jì)算類(lèi) class CalculatorImpl implements Calculator {@Overridepublic int add(int a, int b) {return a + b;} } ? // 創(chuàng)建代理類(lèi),并實(shí)現(xiàn)共同的接口 class CalculatorProxy implements Calculator {private Calculator calculator; ?// 在構(gòu)造函數(shù)中傳入真正的計(jì)算類(lèi)對(duì)象public CalculatorProxy(Calculator calculator) {this.calculator = calculator;} ?@Overridepublic int add(int a, int b) {// 在調(diào)用真正對(duì)象的方法之前執(zhí)行額外的邏輯System.out.println("Before calculation..."); ?// 調(diào)用真正對(duì)象的方法int result = calculator.add(a, b); ?// 在調(diào)用真正對(duì)象的方法之后執(zhí)行額外的邏輯System.out.println("After calculation..."); ?return result;} } ? public class Main {public static void main(String[] args) {// 創(chuàng)建真正的計(jì)算類(lèi)對(duì)象Calculator calculator = new CalculatorImpl(); ?// 創(chuàng)建代理類(lèi)對(duì)象,將真正的計(jì)算類(lèi)對(duì)象傳入Calculator proxy = new CalculatorProxy(calculator); ?// 調(diào)用代理對(duì)象的方法int result = proxy.add(5, 3);System.out.println("Result: " + result);} }
第二種:動(dòng)態(tài)代理,包含JDK代理和CGLIB動(dòng)態(tài)代理
JDK代理
JDK動(dòng)態(tài)代理是Java提供的一種動(dòng)態(tài)創(chuàng)建代理對(duì)象的機(jī)制。它基于Java反射機(jī)制,在運(yùn)行時(shí)動(dòng)態(tài)生成代理類(lèi)和代理實(shí)例。JDK動(dòng)態(tài)代理只能針對(duì)接口進(jìn)行代理,它通過(guò)Proxy類(lèi)和InvocationHandler接口來(lái)實(shí)現(xiàn)。
以下是JDK動(dòng)態(tài)代理的基本步驟:
-
定義一個(gè)接口:首先需要定義一個(gè)共同的接口,該接口包含被代理對(duì)象的方法。
-
創(chuàng)建一個(gè)InvocationHandler對(duì)象:InvocationHandler接口是JDK動(dòng)態(tài)代理的核心,它包含一個(gè)invoke方法,用于處理代理對(duì)象方法的調(diào)用。自定義一個(gè)類(lèi)來(lái)實(shí)現(xiàn)InvocationHandler接口,并在invoke方法中編寫(xiě)處理邏輯。
-
使用Proxy類(lèi)創(chuàng)建代理對(duì)象:使用Proxy類(lèi)的
newProxyInstance
方法動(dòng)態(tài)創(chuàng)建代理對(duì)象。該方法需要傳入三個(gè)參數(shù):ClassLoader,代理接口數(shù)組和InvocationHandler對(duì)象。 -
通過(guò)代理對(duì)象調(diào)用方法:通過(guò)代理對(duì)象調(diào)用接口中的方法,實(shí)際上會(huì)觸發(fā)InvocationHandler的invoke方法,并在該方法中執(zhí)行具體的代理邏輯。
下面是一個(gè)簡(jiǎn)單的示例代碼:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; ? // 定義接口 interface Calculator {int add(int a, int b); } ? // 實(shí)現(xiàn)InvocationHandler接口 class CalculatorInvocationHandler implements InvocationHandler {private Calculator target; ?public CalculatorInvocationHandler(Calculator target) {this.target = target;} ?@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 在方法調(diào)用之前添加額外邏輯System.out.println("Before calculation..."); ?// 調(diào)用真正對(duì)象的方法Object result = method.invoke(target, args); ?// 在方法調(diào)用之后添加額外邏輯System.out.println("After calculation..."); ?return result;} } ? public class Main {public static void main(String[] args) {// 創(chuàng)建真正的計(jì)算類(lèi)對(duì)象Calculator target = new CalculatorImpl(); ?// 創(chuàng)建InvocationHandler對(duì)象,將真正的計(jì)算類(lèi)對(duì)象傳入InvocationHandler handler = new CalculatorInvocationHandler(target); ?// 使用Proxy類(lèi)創(chuàng)建代理對(duì)象Calculator proxy = (Calculator) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class<?>[]{Calculator.class},handler); ?// 調(diào)用代理對(duì)象的方法int result = proxy.add(5, 3);System.out.println("Result: " + result);} }
在上述代碼中,我們定義了一個(gè)接口Calculator
,并實(shí)現(xiàn)了InvocationHandler
接口的CalculatorInvocationHandler
類(lèi)。在invoke
方法中,我們可以在方法調(diào)用前后添加額外的邏輯。在Main
類(lèi)中,我們創(chuàng)建了真正的計(jì)算類(lèi)對(duì)象,并使用Proxy類(lèi)的newProxyInstance
方法創(chuàng)建代理對(duì)象。通過(guò)代理對(duì)象調(diào)用方法時(shí),實(shí)際上會(huì)調(diào)用invoke
方法,并在其中執(zhí)行代理邏輯。
運(yùn)行以上代碼,你將看到額外的邏輯在方法調(diào)用前后被執(zhí)行,并獲得正確的計(jì)算結(jié)果。這就是JDK動(dòng)態(tài)代理的基本原理。與靜態(tài)代理相比,JDK動(dòng)態(tài)代理更加靈活,可以適用于各種接口的代理場(chǎng)景。
CGLIB動(dòng)態(tài)代理
CGLIB(Code Generation Library)是一個(gè)強(qiáng)大的第三方類(lèi)庫(kù),用于在運(yùn)行時(shí)擴(kuò)展Java類(lèi)的功能。它通過(guò)生成繼承被代理類(lèi)的子類(lèi),并重寫(xiě)父類(lèi)的方法來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理。相比JDK動(dòng)態(tài)代理,CGLIB動(dòng)態(tài)代理不需要接口的支持,可以代理類(lèi)而不僅僅是接口。
以下是使用CGLIB動(dòng)態(tài)代理的基本步驟:
-
引入相關(guān)依賴(lài):在項(xiàng)目中加入CGLIB的依賴(lài),例如Maven項(xiàng)目可以添加以下依賴(lài):
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version> </dependency>
-
定義一個(gè)被代理的類(lèi):不需要實(shí)現(xiàn)接口的普通類(lèi)。
-
創(chuàng)建MethodInterceptor對(duì)象:MethodInterceptor是CGLIB提供的核心接口,包含一個(gè)intercept方法,在該方法中編寫(xiě)處理邏輯。
-
使用Enhancer創(chuàng)建代理對(duì)象:Enhancer是CGLIB提供的用于創(chuàng)建代理對(duì)象的類(lèi)。通過(guò)設(shè)置父類(lèi)、接口、攔截器等參數(shù),調(diào)用create方法動(dòng)態(tài)生成代理對(duì)象。
-
通過(guò)代理對(duì)象調(diào)用方法:通過(guò)代理對(duì)象調(diào)用方法,實(shí)際上會(huì)觸發(fā)MethodInterceptor的intercept方法,并在該方法中執(zhí)行具體的代理邏輯。
下面是一個(gè)簡(jiǎn)單的示例代碼:
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; ? import java.lang.reflect.Method; ? // 定義被代理的類(lèi) class Calculator {public int add(int a, int b) {return a + b;} } ? // 實(shí)現(xiàn)MethodInterceptor接口 class CalculatorMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 在方法調(diào)用之前添加額外邏輯System.out.println("Before calculation..."); ?// 調(diào)用真正對(duì)象的方法Object result = proxy.invokeSuper(obj, args); ?// 在方法調(diào)用之后添加額外邏輯System.out.println("After calculation..."); ?return result;} } ? public class Main {public static void main(String[] args) {// 創(chuàng)建Enhancer對(duì)象Enhancer enhancer = new Enhancer(); ?// 設(shè)置父類(lèi)(被代理類(lèi))enhancer.setSuperclass(Calculator.class); ?// 設(shè)置攔截器enhancer.setCallback(new CalculatorMethodInterceptor()); ?// 創(chuàng)建代理對(duì)象Calculator proxy = (Calculator) enhancer.create(); ?// 調(diào)用代理對(duì)象的方法int result = proxy.add(5, 3);System.out.println("Result: " + result);} }
在上述代碼中,我們定義了一個(gè)被代理的類(lèi)Calculator
,并實(shí)現(xiàn)了CGLIB的MethodInterceptor
接口來(lái)編寫(xiě)代理邏輯。通過(guò)設(shè)置父類(lèi)和攔截器,使用Enhancer類(lèi)創(chuàng)建代理對(duì)象。通過(guò)代理對(duì)象調(diào)用方法時(shí),實(shí)際上會(huì)觸發(fā)MethodInterceptor的intercept方法,并在其中執(zhí)行代理邏輯。
運(yùn)行以上代碼,你將看到額外的邏輯在方法調(diào)用前后被執(zhí)行,并獲得正確的計(jì)算結(jié)果。這就是CGLIB動(dòng)態(tài)代理的基本原理。與JDK動(dòng)態(tài)代理不同,CGLIB動(dòng)態(tài)代理不需要接口的支持,可以代理普通類(lèi)。然而,由于使用了繼承機(jī)制,CGLIB不能代理被標(biāo)記為final的類(lèi)和方法。
JDK動(dòng)態(tài)代理和CGLIB兩種動(dòng)態(tài)代理的比較
JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理是兩種常用的代理實(shí)現(xiàn)方式,它們具有不同的特點(diǎn)和適用場(chǎng)景。下面是它們的區(qū)別以及各自的優(yōu)缺點(diǎn):
JDK動(dòng)態(tài)代理:
-
基于接口:JDK動(dòng)態(tài)代理只能代理接口,需要目標(biāo)類(lèi)實(shí)現(xiàn)一個(gè)或多個(gè)接口。
-
使用Java反射機(jī)制:JDK動(dòng)態(tài)代理是通過(guò)Proxy類(lèi)和InvocationHandler接口實(shí)現(xiàn)的,利用Java反射機(jī)制生成代理類(lèi)和代理實(shí)例。
-
平臺(tái)獨(dú)立性:JDK動(dòng)態(tài)代理是Java標(biāo)準(zhǔn)庫(kù)的一部分,因此具有很好的平臺(tái)獨(dú)立性,不依賴(lài)第三方庫(kù)。
-
性能較低:相比CGLIB動(dòng)態(tài)代理,JDK動(dòng)態(tài)代理在生成代理類(lèi)和調(diào)用方法時(shí)的性能較差。這是由于JDK動(dòng)態(tài)代理在生成代理類(lèi)時(shí)需要使用反射,以及在代理時(shí)涉及到方法調(diào)用的轉(zhuǎn)發(fā)。
-
無(wú)法代理final類(lèi)和方法:JDK動(dòng)態(tài)代理由于基于接口,因此無(wú)法代理被標(biāo)記為final的類(lèi)和方法。
CGLIB動(dòng)態(tài)代理:
-
基于繼承:CGLIB動(dòng)態(tài)代理可以直接代理普通類(lèi),不需要實(shí)現(xiàn)接口。它通過(guò)繼承目標(biāo)類(lèi)的方式實(shí)現(xiàn)代理。
-
使用ASM字節(jié)碼操作庫(kù):CGLIB動(dòng)態(tài)代理使用ASM庫(kù)操作字節(jié)碼,在運(yùn)行時(shí)動(dòng)態(tài)生成代理類(lèi)。
-
性能較高:相對(duì)于JDK動(dòng)態(tài)代理,CGLIB動(dòng)態(tài)代理在生成代理類(lèi)和調(diào)用方法時(shí)的性能更高。這是因?yàn)镃GLIB動(dòng)態(tài)代理直接繼承目標(biāo)類(lèi),省去了方法調(diào)用的轉(zhuǎn)發(fā)。
-
無(wú)法代理final方法:由于CGLIB動(dòng)態(tài)代理是通過(guò)繼承實(shí)現(xiàn)的,因此無(wú)法代理被標(biāo)記為final的方法。但是,可以代理被final修飾的類(lèi)。
綜合來(lái)說(shuō),JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理各有優(yōu)缺點(diǎn):
-
JDK動(dòng)態(tài)代理適用于代理接口的場(chǎng)景,具有很好的平臺(tái)獨(dú)立性,但性能較差。
-
CGLIB動(dòng)態(tài)代理適用于代理普通類(lèi)的場(chǎng)景,性能較高,但對(duì)final方法和類(lèi)的代理受限。
因此,在選擇動(dòng)態(tài)代理方式時(shí),需根據(jù)具體的需求和場(chǎng)景來(lái)選擇適合的代理方式。
hashcode和equals如何使用
hashCode()和equals()是Java中的兩個(gè)重要方法,都源自于java.lang.Object,用于對(duì)象的比較和哈希映射。下面是它們的使用方法:
-
hashCode()方法:
-
hashCode()方法用于計(jì)算對(duì)象的哈希碼(hash code),返回一個(gè)int類(lèi)型的值。
-
hashCode()方法的常規(guī)約定是,對(duì)于相等的對(duì)象,調(diào)用hashCode()方法應(yīng)該返回相同的值。然而,對(duì)于不相等的對(duì)象,hashCode()方法返回相同的值并不是必需的。
-
在重寫(xiě)equals()方法時(shí),通常也需要同時(shí)重寫(xiě)hashCode()方法,以保證在存儲(chǔ)對(duì)象的哈希集合(如HashMap、HashSet)中能正常工作。
-
重寫(xiě)hashCode()方法時(shí),應(yīng)遵循以下原則:
-
如果兩個(gè)對(duì)象通過(guò)equals()方法比較是相等的,則它們的hashCode()方法的返回值必須相等。
-
如果兩個(gè)對(duì)象通過(guò)equals()方法比較不相等(即對(duì)象不相等),它們的hashCode()方法的返回值可以相等,也可以不相等。
-
-
-
equals()方法:
-
equals()方法用于比較兩個(gè)對(duì)象是否相等,返回一個(gè)boolean類(lèi)型的值。
-
默認(rèn)情況下,equals()方法比較的是對(duì)象的引用,即判斷兩個(gè)對(duì)象是否指向同一個(gè)內(nèi)存地址。但是,可以根據(jù)需要重寫(xiě)equals()方法,以便自定義對(duì)象的相等條件。
-
重寫(xiě)equals()方法時(shí),應(yīng)遵循以下原則:
-
對(duì)稱(chēng)性:如果a.equals(b)返回true,則b.equals(a)也應(yīng)返回true。
-
自反性:對(duì)于任何非null的引用值x,x.equals(x)都應(yīng)返回true。
-
傳遞性:如果a.equals(b)返回true,且b.equals(c)返回true,則a.equals(c)也應(yīng)返回true。
-
一致性:對(duì)于任何非null的引用值x和y,多次調(diào)用x.equals(y)應(yīng)始終返回相同的結(jié)果,前提是對(duì)象上沒(méi)有修改導(dǎo)致equals()比較的結(jié)果發(fā)生變化。
-
對(duì)于任何非null的引用值x,x.equals(null)都應(yīng)返回false。
-
-
異常分類(lèi)
在Java中,異常分為三種不同的類(lèi)型:
-
受檢異常(Checked Exception): 受檢異常是指在代碼中明確需要進(jìn)行處理的異常,在方法聲明中通過(guò)throws關(guān)鍵字聲明,或者在方法內(nèi)部通過(guò)try-catch語(yǔ)句進(jìn)行捕獲和處理。受檢異常通常表示程序可能面臨的外部環(huán)境異常,需要程序員在代碼中顯式處理,否則編譯時(shí)會(huì)報(bào)錯(cuò)。例如,IOException、SQLException等。
-
運(yùn)行時(shí)異常(Runtime Exception): 運(yùn)行時(shí)異常是指在程序執(zhí)行過(guò)程中可能出現(xiàn)的異常,通常是由程序錯(cuò)誤或異常情況引起的。與受檢異常不同的是,運(yùn)行時(shí)異常不要求在代碼中顯式處理,并且也不需要在方法聲明中聲明throws關(guān)鍵字。當(dāng)發(fā)生運(yùn)行時(shí)異常時(shí),如果沒(méi)有進(jìn)行顯式處理,則會(huì)沿著方法調(diào)用棧向上拋出,直到被捕獲或?qū)е鲁绦蚪K止。例如,NullPointerException、ArrayIndexOutOfBoundsException等。
-
錯(cuò)誤(Error): 錯(cuò)誤是指無(wú)法通過(guò)代碼來(lái)處理的嚴(yán)重問(wèn)題,通常是由虛擬機(jī)或系統(tǒng)錯(cuò)誤引起的。錯(cuò)誤表示JVM或系統(tǒng)發(fā)生了嚴(yán)重的問(wèn)題,無(wú)法恢復(fù)和處理,一般不需要程序員進(jìn)行處理。例如,OutOfMemoryError、StackOverflowError等。
Java異常類(lèi)繼承自Throwable
類(lèi),其中受檢異常繼承自Exception
,運(yùn)行時(shí)異常繼承自RuntimeException
,錯(cuò)誤繼承自Error
。通過(guò)了解和正確處理異常,可以增加程序的可靠性,并提供適當(dāng)?shù)腻e(cuò)誤處理和容錯(cuò)機(jī)制。
Java異常處理方式
在Java中,有三種主要的異常處理方式:
-
try-catch塊: 使用try-catch塊可以捕獲和處理異常。try塊用于包含可能拋出異常的代碼,catch塊用于捕獲并處理try塊中拋出的異常。語(yǔ)法如下:
try {// 可能拋出異常的代碼 } catch (ExceptionType1 e1) {// 處理異常類(lèi)型 1 } catch (ExceptionType2 e2) {// 處理異常類(lèi)型 2 } finally {// 可選的finally塊,用于無(wú)論是否發(fā)生異常都會(huì)執(zhí)行的代碼 }
在try塊中,如果發(fā)生異常,則會(huì)跳轉(zhuǎn)到與異常類(lèi)型匹配的catch塊,執(zhí)行相應(yīng)的處理代碼。如果沒(méi)有匹配的catch塊,異常會(huì)傳播到調(diào)用棧的上一層。無(wú)論是否發(fā)生異常,finally塊中的代碼都會(huì)被執(zhí)行。
-
throws聲明: 使用throws關(guān)鍵字可以在方法的聲明中指定該方法可能拋出的異常。將異常以throws聲明的方式拋出,可以將異常的處理責(zé)任交給調(diào)用該方法的地方。示例代碼如下:
public void methodName() throws ExceptionType1, ExceptionType2 {// 可能拋出異常的代碼 }
當(dāng)方法中的代碼拋出了異常,調(diào)用該方法的地方可以選擇捕獲異常并處理,或者繼續(xù)將異常上拋到更高層調(diào)用棧中進(jìn)行處理。
-
使用finally塊: finally塊用于在try-catch塊中的代碼執(zhí)行完畢后,無(wú)論是否發(fā)生異常,都會(huì)執(zhí)行的代碼塊。finally塊通常用于釋放資源或進(jìn)行必要的清理操作,例如關(guān)閉文件、釋放資源等。語(yǔ)法如下:
try {// 可能拋出異常的代碼 } catch (ExceptionType e) {// 處理異常 } finally {// 無(wú)論是否發(fā)生異常,都會(huì)執(zhí)行的代碼 }
注意,finally塊可以省略,try塊和catch塊可以單獨(dú)存在。在沒(méi)有catch塊的情況下,try塊中拋出的異常會(huì)被上層調(diào)用棧處理或繼續(xù)上拋。
通過(guò)合理地使用這些異常處理方式,可以增加代碼的健壯性和容錯(cuò)性,更好地處理異常情況,提高程序的穩(wěn)定性。
throw,throws的區(qū)別
throw
和throws
是Java中異常處理的兩個(gè)關(guān)鍵字,它們有以下區(qū)別:
-
throw關(guān)鍵字:
throw
關(guān)鍵字用于手動(dòng)拋出一個(gè)異常對(duì)象。它通常用于方法內(nèi)部,用來(lái)拋出指定的異常,使得異常在方法內(nèi)部被捕獲或在調(diào)用棧中傳播。例如:public void method() {if (condition) {throw new ExceptionType("Error occurred");} }
在上述代碼中,如果滿(mǎn)足某個(gè)條件,
throw
語(yǔ)句會(huì)拋出一個(gè)指定的異常對(duì)象,使得異常在方法內(nèi)部被捕獲或在調(diào)用棧中傳播。 -
throws關(guān)鍵字:
throws
關(guān)鍵字用于方法的聲明中,用于指定該方法可能拋出的異常類(lèi)型。它提供了一種聲明異常的機(jī)制,使得調(diào)用該方法的代碼可以采取相應(yīng)的異常處理措施。例如:public void method() throws ExceptionType1, ExceptionType2 {// 可能拋出這兩種異常類(lèi)型的代碼 }
在上述代碼中,
throws
關(guān)鍵字后面列出了方法可能拋出的異常類(lèi)型。當(dāng)調(diào)用該方法時(shí),調(diào)用者可以選擇捕獲這些異常并處理,或者將異常進(jìn)一步上拋。
總結(jié):
-
throw
關(guān)鍵字用于手動(dòng)拋出異常,表示在代碼的某個(gè)條件成立時(shí),主動(dòng)地拋出異常對(duì)象。 -
throws
關(guān)鍵字用于方法的聲明中,指定該方法可能拋出的異常類(lèi)型,并將異常處理的責(zé)任轉(zhuǎn)移給調(diào)用該方法的代碼。 -
throw
拋出的異常是通過(guò)關(guān)鍵字new
創(chuàng)建的對(duì)象,而throws
聲明的異常是指定的異常類(lèi)型。 -
throw
用于方法內(nèi)部,throws
用于方法的聲明中。
需要注意的是,throw
和throws
關(guān)鍵字并不直接處理異常,它們只是在異常處理時(shí)的一種機(jī)制,實(shí)際的異常處理通過(guò)try-catch
塊或者上層調(diào)用棧來(lái)完成。
自定義異常在生產(chǎn)中如何應(yīng)用
Java雖然提供了豐富的異常處理類(lèi),但是在項(xiàng)目中還會(huì)經(jīng)常使用自定義異常,其主要原因是Java提供的異常類(lèi)在某些情況下還是不能滿(mǎn)足實(shí)際需球。例如以下情況: 1、系統(tǒng)中有些錯(cuò)誤是符合Java語(yǔ)法,但不符合業(yè)務(wù)邏輯。
2、在分層的軟件結(jié)構(gòu)中,通常是在表現(xiàn)層統(tǒng)一對(duì)系統(tǒng)其他層次的異常進(jìn)行捕獲處理。
過(guò)濾器與攔截器的區(qū)別
過(guò)濾器(Filter)和攔截器(Interceptor)都是用于在Web應(yīng)用中對(duì)請(qǐng)求進(jìn)行處理和攔截的組件,但它們之間有一些區(qū)別:
-
含義:
-
過(guò)濾器(Filter):過(guò)濾器是在Servlet容器中執(zhí)行的功能組件,對(duì)請(qǐng)求和響應(yīng)進(jìn)行預(yù)處理和后處理。它可以修改請(qǐng)求和響應(yīng)的內(nèi)容,或者對(duì)請(qǐng)求進(jìn)行驗(yàn)證、安全性檢查、日志記錄等操作。
-
攔截器(Interceptor):攔截器也是用于對(duì)請(qǐng)求進(jìn)行預(yù)處理和后處理的組件,但是攔截器是在Spring MVC框架內(nèi)部執(zhí)行的。它可以在請(qǐng)求被調(diào)度到處理器之前和之后進(jìn)行一些公共的任務(wù),如身份驗(yàn)證、權(quán)限檢查、日志記錄等。
-
-
使用場(chǎng)景:
-
過(guò)濾器(Filter):過(guò)濾器主要用于對(duì)HTTP請(qǐng)求和響應(yīng)進(jìn)行處理,可以對(duì)請(qǐng)求的URL、參數(shù)、頭部等進(jìn)行過(guò)濾和處理。
-
攔截器(Interceptor):攔截器主要用于對(duì)Controller的請(qǐng)求進(jìn)行預(yù)處理和后處理,在請(qǐng)求到達(dá)Controller之前和離開(kāi)Controller之后執(zhí)行一些公共的任務(wù)、處理業(yè)務(wù)邏輯。
-
-
執(zhí)行順序:
-
過(guò)濾器(Filter):過(guò)濾器在Servlet容器中配置,并以鏈?zhǔn)浇Y(jié)構(gòu)執(zhí)行。對(duì)于一個(gè)請(qǐng)求,過(guò)濾器按照配置的順序依次執(zhí)行,可以有多個(gè)過(guò)濾器配置,并且可以跨越多個(gè)Web應(yīng)用。
-
攔截器(Interceptor):攔截器是在Spring MVC的上下文中配置的,并且只對(duì)DispatcherServlet的請(qǐng)求進(jìn)行攔截。在一個(gè)請(qǐng)求中,攔截器的執(zhí)行順序由配置的順序決定,同一個(gè)攔截器鏈上的多個(gè)攔截器按照配置的順序依次執(zhí)行。
-
總之,過(guò)濾器適合處理通用的URL級(jí)別的請(qǐng)求處理,例如編碼轉(zhuǎn)換、安全性驗(yàn)證等。攔截器更加適合對(duì)Controller級(jí)別的請(qǐng)求進(jìn)行處理,例如權(quán)限檢查、日志記錄等。通過(guò)合理配置過(guò)濾器和攔截器,可以實(shí)現(xiàn)對(duì)請(qǐng)求的不同層面的處理和攔截,以滿(mǎn)足不同業(yè)務(wù)需求。
過(guò)濾器(Filter)和攔截器(Interceptor)是在Web應(yīng)用程序中用于處理和攔截請(qǐng)求的組件,它們之間有以下詳細(xì)區(qū)別:
-
執(zhí)行時(shí)機(jī):
-
過(guò)濾器:過(guò)濾器是在Servlet容器中執(zhí)行的,對(duì)請(qǐng)求和響應(yīng)進(jìn)行預(yù)處理和后處理。它們?cè)谡?qǐng)求進(jìn)入Servlet容器之前被調(diào)用,并在請(qǐng)求離開(kāi)容器后執(zhí)行。過(guò)濾器可以在請(qǐng)求到達(dá)Servlet之前修改請(qǐng)求和響應(yīng)內(nèi)容,以及在響應(yīng)返回給客戶(hù)端之前對(duì)其進(jìn)行處理。
-
攔截器:攔截器是在Spring MVC框架內(nèi)部執(zhí)行的,主要用于對(duì)Controller的請(qǐng)求進(jìn)行預(yù)處理和后處理。攔截器在請(qǐng)求到達(dá)Controller之前和離開(kāi)Controller之后執(zhí)行,可以在請(qǐng)求處理之前做一些通用的準(zhǔn)備工作,以及在請(qǐng)求處理完成后進(jìn)行一些公共的收尾工作。
-
-
作用范圍:
-
過(guò)濾器:過(guò)濾器是在Servlet容器中配置的,對(duì)請(qǐng)求進(jìn)行過(guò)濾處理。過(guò)濾器可以作用于多個(gè)Servlet和多個(gè)Web應(yīng)用程序,可以配置在web.xml中,并通過(guò)URL模式指定對(duì)哪些請(qǐng)求生效。
-
攔截器:攔截器是在Spring MVC的上下文中配置的,主要對(duì)DispatcherServlet的請(qǐng)求進(jìn)行攔截處理。攔截器只作用于Spring MVC中的請(qǐng)求,并且只對(duì)DispatcherServlet的請(qǐng)求生效。
-
-
觸發(fā)條件:
-
過(guò)濾器:過(guò)濾器可以對(duì)所有的請(qǐng)求進(jìn)行過(guò)濾處理,包括靜態(tài)資源請(qǐng)求。它們是基于URL模式進(jìn)行匹配,可以以鏈?zhǔn)浇Y(jié)構(gòu)依次執(zhí)行多個(gè)過(guò)濾器。
-
攔截器:攔截器只在DispatcherServlet中執(zhí)行,并且只對(duì)具體的Controller請(qǐng)求進(jìn)行攔截。攔截器是基于HandlerMapping進(jìn)行匹配,只有當(dāng)請(qǐng)求與某個(gè)Controller匹配成功時(shí),相關(guān)的攔截器才會(huì)觸發(fā)執(zhí)行。
-
-
依賴(lài)框架:
-
過(guò)濾器:過(guò)濾器是Servlet容器的一部分,獨(dú)立于其他框架。它們可以用于任何基于Servlet規(guī)范的Web應(yīng)用程序,如JavaEE等。
-
攔截器:攔截器是Spring MVC框架的一部分,依賴(lài)于Spring MVC框架。它們可以利用Spring MVC框架提供的功能,如依賴(lài)注入、AOP等。
-
總的來(lái)說(shuō),過(guò)濾器和攔截器都是用于對(duì)請(qǐng)求進(jìn)行處理和攔截的組件,但它們所處的執(zhí)行時(shí)機(jī)、作用范圍、觸發(fā)條件和依賴(lài)框架等方面存在一些差異。根據(jù)具體的需求和場(chǎng)景,可以選擇合適的過(guò)濾器或攔截器來(lái)實(shí)現(xiàn)請(qǐng)求的處理和攔截邏輯。
5,。配置文件不同
-
過(guò)濾器(Filter)配置:過(guò)濾器的配置是在web.xml文件中進(jìn)行的,屬于Servlet容器的配置。在web.xml中,可以通過(guò)
<filter>
和<filter-mapping>
元素來(lái)配置過(guò)濾器。其中,<filter>
用于聲明過(guò)濾器的類(lèi)和名稱(chēng),<filter-mapping>
用于指定過(guò)濾器的名稱(chēng)和要過(guò)濾的URL模式或Servlet名稱(chēng)。 -
攔截器(Interceptor)配置:攔截器的配置是在Spring MVC的配置文件中進(jìn)行的,屬于Spring MVC框架的配置。要配置攔截器,需要在配置文件中聲明攔截器,并將其添加到攔截器鏈中??梢允褂?code><mvc:interceptor>元素或在Java配置中使用
addInterceptor()
方法來(lái)配置攔截器。在配置攔截器時(shí),需要指定攔截器類(lèi)、要攔截的URL模式、排除的URL模式等。
Integer常見(jiàn)面試題
1.介紹一下自動(dòng)裝箱和自動(dòng)拆箱
java的八種基本類(lèi)型都對(duì)應(yīng)著相應(yīng)的包裝類(lèi)型
總的來(lái)說(shuō):裝箱就是自動(dòng)將基本數(shù)據(jù)類(lèi)型轉(zhuǎn)換為包裝器類(lèi)型;拆箱就是自動(dòng)將包裝器類(lèi)型轉(zhuǎn)換為基本數(shù)據(jù)類(lèi)型。所以在運(yùn)算賦值過(guò)程中,會(huì)自動(dòng)進(jìn)行拆箱和裝箱。
拆箱裝箱的過(guò)程 :
1)拆箱:Integer total = 99
實(shí)際上是調(diào)用了Integer total = Integer.valueOf(99) 這句代碼
2)裝箱:nt totalprim = total;
實(shí)際上行是調(diào)用了 int totalprim = total.intValue();這句代碼
但是實(shí)際上拆箱裝箱需要考慮常量池的存在!(下面會(huì)講到)
2. Integer創(chuàng)建對(duì)象的幾種方式和區(qū)別
在JVM虛擬機(jī)中有一塊內(nèi)存為常量池,常量池中除了包含代碼中所定義的各種基本類(lèi)型(如int、long等等)和對(duì)象型(如String及數(shù)組)的常量值還,還包含一些以文本形式出現(xiàn)的符號(hào)引用
對(duì)于基本數(shù)據(jù),常量池對(duì)每種基本數(shù)據(jù)都有一個(gè)區(qū)間,在此區(qū)間中的數(shù),都從常量池中存取共享!但是除了new創(chuàng)建對(duì)象的方式除外。
以Integer為例:
(-128——127為一個(gè)區(qū)間)
Integer total = 99
這句賦值的確是會(huì)是自動(dòng)裝箱,但是返回的地址卻不是在堆中,而是在常量池中,因?yàn)?9屬于【-128,,127】區(qū)間。也就是說(shuō)以這種方式創(chuàng)建的對(duì)象,都是取的一個(gè)地址!
??? ??? ?Integer t1 = 99;//常量池
? ? ? ? Integer t2 = 99;//常量池
? ? ? ? System.out.println(t1 == t2);//true ?
Integer total = 128;
這句賦值也會(huì)進(jìn)行自動(dòng)裝箱,但是由于不在區(qū)間內(nèi),所以取到的對(duì)象地址是在堆中。不會(huì)進(jìn)行對(duì)象共享!每次都會(huì)創(chuàng)建新的對(duì)象
??? ??? ?Integer t3 = 128;//堆
? ? ? ? Integer t4 = 128;//堆
? ? ? ? System.out.println(t3 == t4);//false
Integer total = Integer.valueOf(99) ,Integer total= Integer.valueOf(128)
這兩種創(chuàng)建方式和上面的賦值是一樣的,因?yàn)樯厦娴淖詣?dòng)裝箱源碼調(diào)用的就是這個(gè)方法!
? ?Integer tt1 = Integer.valueOf(99);//常量池
? ? Integer tt2 = Integer.valueOf(99);//常量池
? ? System.out.println(tt1 == tt2);//true ?
? ? Integer tt3 = Integer.valueOf(128);//堆
? ? Integer tt4 = Integer.valueOf(128);//堆
? ? System.out.println(tt3 == tt4);//fasle ?
Integer total = new Integer(99)
使用new關(guān)鍵字創(chuàng)建對(duì)象的時(shí)候,就不需要考慮常量池的問(wèn)題,無(wú)論數(shù)值大小,都從堆中創(chuàng)建!
?? ??? ?Integer n1 = new Integer(99);//堆
? ? ? ? Integer n2 = new Integer(99);//堆
? ? ? ? System.out.println(n1 == n2);//fasle
總結(jié):
1)一共三種創(chuàng)建方式:
前兩種是看似不同,其實(shí)內(nèi)部機(jī)制完全相同,因?yàn)闀?huì)自動(dòng)裝箱!但是一定要注意到常量池的問(wèn)題。
?? ??? ?Integer t1 = 99;//常量池
? ? ? ? Integer t4 = 128;//堆
? ? ? ? Integer tt2 = Integer.valueOf(99);//常量池
? ? ? ? Integer tt4 = Integer.valueOf(128);//堆
??
? ? ? ? Integer n1 = new Integer(99);//堆
? ? ? ? Integer n2 = new Integer(99);//堆
2)在面試過(guò)程中如果遇到考查Integer的情況,基本都會(huì)給一段代碼,判斷輸出是true還是fasle,這時(shí)候只要仔細(xì)分析對(duì)象的創(chuàng)建方式,以及返回的地址來(lái)源即可!
3.常見(jiàn)考查代碼
總結(jié):
兩個(gè)數(shù)都是用==或者Integer.valueOf()方法賦值的話(huà),只要比較數(shù)的大小,在【-128,127】之間就相同,不在就不同
兩個(gè)數(shù)都是用new關(guān)鍵字創(chuàng)建的話(huà),無(wú)論數(shù)值大小,一定不同
一個(gè)數(shù)用new,一個(gè)數(shù)用==或者Integer.valueOf(),也一定不同!
Integer in= new Integer(127);
Integer in2 = new Integer(127);
System.out.println(in==in2);//false
System.out.println(in.equals(in2));//true
Integer in3= new Integer(128);
Integer in4 = new Integer(128);
System.out.println(in3==in4);//false
System.out.println(in3.equals(in4));//true
Integer in5= 128;
Integer in6 = 128;
System.out.println(in5==in6);//false
System.out.println(in5.equals(in6));//true
Integer in7= 127;
Integer in8 = 127;
System.out.println(in7==in8);//true
System.out.println(in7.equals(in8));//true
值傳遞和引用傳遞有什么區(qū)別
值傳遞和引用傳遞是傳遞參數(shù)時(shí)的兩種不同方式,它們之間的區(qū)別主要在于傳遞的是什么。
1. **值傳遞**:
? ?- 值傳遞是指將變量的值復(fù)制一份傳遞給函數(shù)或方法。
? ?- 在值傳遞中,傳遞的是變量的實(shí)際值,而不是變量本身。
? ?- 當(dāng)函數(shù)或方法使用傳遞的參數(shù)時(shí),會(huì)操作參數(shù)值的副本,原始變量不受影響。
? ?- 在 Java 中,傳遞基本數(shù)據(jù)類(lèi)型時(shí)是值傳遞的方式。
2. **引用傳遞**:
? ?- 引用傳遞是指將變量的引用(內(nèi)存地址)傳遞給函數(shù)或方法。
? ?- 在引用傳遞中,傳遞的是變量的實(shí)際引用,函數(shù)或方法可以通過(guò)該引用訪(fǎng)問(wèn)和修改原始變量。
? ?- 當(dāng)函數(shù)或方法使用傳遞的引用時(shí),操作的是原始變量的值,可以改變?cè)甲兞康臓顟B(tài)。
? ?- 在某些語(yǔ)言中支持引用傳遞,比如 C++,但在 Java 中并不存在“引用傳遞”的概念。
在 Java 中,雖然對(duì)象引用作為參數(shù)傳遞給方法時(shí)傳遞的是引用的副本(即地址的副本),但實(shí)際上 Java 是使用值傳遞的方式。因?yàn)閭鬟f的是引用的值(地址的副本),而不是引用本身。這意味著在方法內(nèi)雖然可以改變對(duì)象狀態(tài),卻無(wú)法改變引用指向的對(duì)象。
總的來(lái)說(shuō),Java 中只有值傳遞這一種傳遞參數(shù)的方式,但對(duì)于對(duì)象引用的處理方式與傳統(tǒng)的值傳遞有一些微妙的區(qū)別。希望這個(gè)解答對(duì)你有所幫助。如有任何問(wèn)題,請(qǐng)繼續(xù)提問(wèn)。
集合
?集合和數(shù)組的區(qū)別
集合(Collection)和數(shù)組(Array)是在編程中常用的數(shù)據(jù)結(jié)構(gòu),它們有以下幾點(diǎn)區(qū)別:
1. **數(shù)據(jù)類(lèi)型**:
? ?- 數(shù)組是一種固定大小的、存儲(chǔ)相同數(shù)據(jù)類(lèi)型元素的連續(xù)內(nèi)存區(qū)域。
? ?- 集合是一種動(dòng)態(tài)大小的、可以存儲(chǔ)不同數(shù)據(jù)類(lèi)型對(duì)象的數(shù)據(jù)結(jié)構(gòu)。
2. **長(zhǎng)度/大小**:
? ?- 數(shù)組的長(zhǎng)度是固定的,一旦創(chuàng)建就無(wú)法改變。
? ?- 集合是動(dòng)態(tài)的,可以根據(jù)需要?jiǎng)討B(tài)添加或刪除元素,大小是可變的。
3. **類(lèi)型**:
? ?- 數(shù)組可以包含基本數(shù)據(jù)類(lèi)型和對(duì)象類(lèi)型。
? ?- 集合一般是針對(duì)對(duì)象類(lèi)型的,可以存儲(chǔ)任意類(lèi)型的對(duì)象。
4. **語(yǔ)法**:
? ?- 數(shù)組的聲明和初始化方式比較簡(jiǎn)單,如 `int[] arr = new int[5]`。
? ?- 集合的聲明和初始化需要使用相關(guān)的集合類(lèi),如 `List<String> list = new ArrayList<>()`。
5. **功能**:
? ?- 集合提供了豐富的方法和功能,如增刪改查、排序、遍歷等。
? ?- 數(shù)組的功能相對(duì)簡(jiǎn)單,主要是通過(guò)索引訪(fǎng)問(wèn)元素,沒(méi)有內(nèi)置的方法來(lái)進(jìn)行常見(jiàn)操作。
6. **擴(kuò)展性**:
? ?- 集合比數(shù)組更具擴(kuò)展性和靈活性,可以更方便地進(jìn)行元素的增刪改查操作。
? ?- 數(shù)組在大小固定和數(shù)據(jù)類(lèi)型一致的情況下使用更加高效。
總的來(lái)說(shuō),集合更加靈活和功能豐富,適用于動(dòng)態(tài)數(shù)據(jù)結(jié)構(gòu)的場(chǎng)景,而數(shù)組更適合于靜態(tài)、大小固定的數(shù)據(jù)集合。在實(shí)際編程中,根據(jù)需要選擇合適的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)和操作數(shù)據(jù),常常會(huì)根據(jù)特定的場(chǎng)景來(lái)選擇使用數(shù)組或集合。希望以上區(qū)別對(duì)你有所幫助,如有任何問(wèn)題或需要進(jìn)一步了解,請(qǐng)隨時(shí)提出。
集合框架底層數(shù)據(jù)結(jié)構(gòu)
Java 集合框架中的不同集合類(lèi)底層使用不同的數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn),下面是一些常見(jiàn)的集合類(lèi)及其底層數(shù)據(jù)結(jié)構(gòu):
1. **ArrayList**:
? ?- ArrayList 使用數(shù)組作為底層數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)元素。
? ?- 當(dāng)數(shù)組空間不足時(shí),會(huì)進(jìn)行擴(kuò)容操作(通常是當(dāng)前容量的 1.5 倍),以保證能夠繼續(xù)添加元素。
2. **LinkedList**:
? ?- LinkedList 使用雙向鏈表來(lái)存儲(chǔ)元素。
? ?- 鏈表的每個(gè)節(jié)點(diǎn)都保存了元素值以及指向前一個(gè)節(jié)點(diǎn)和后一個(gè)節(jié)點(diǎn)的引用。
3. **HashMap**:
? ?- HashMap 使用哈希表(數(shù)組 + 鏈表/紅黑樹(shù))來(lái)存儲(chǔ)鍵值對(duì)。
? ?- 哈希表通過(guò)鍵的哈希值來(lái)計(jì)算存儲(chǔ)位置,解決哈希沖突的方法有拉鏈法和開(kāi)放定址法。
4. **HashSet**:
? ?- HashSet 內(nèi)部使用 HashMap 來(lái)存儲(chǔ)元素。
? ?- HashSet 中的元素存儲(chǔ)在 HashMap 的 key 中,value 則使用一個(gè)靜態(tài)常量。
5. **TreeMap**:
? ?- TreeMap 使用紅黑樹(shù)(Red-Black Tree)作為底層數(shù)據(jù)結(jié)構(gòu)。
? ?- 紅黑樹(shù)是一種自平衡二叉搜索樹(shù),可以保證元素按照 key 的自然順序(或自定義比較器)排列。
6. **LinkedHashMap**:
? ?- LinkedHashMap 繼承自 HashMap,使用哈希表和雙向鏈表來(lái)維護(hù)元素的順序。
? ?- 可以保持元素插入順序或訪(fǎng)問(wèn)順序不變。
這些是 Java 集合框架中一些常見(jiàn)集合類(lèi)的底層數(shù)據(jù)結(jié)構(gòu),不同的數(shù)據(jù)結(jié)構(gòu)在不同場(chǎng)景下有著各自的優(yōu)劣勢(shì)。了解集合類(lèi)底層數(shù)據(jù)結(jié)構(gòu)有助于更好地理解集合類(lèi)的特性和性能表現(xiàn),從而更好地選擇適合的集合類(lèi)來(lái)滿(mǎn)足需求。希望以上信息能夠幫助你理解集合框架中常見(jiàn)集合類(lèi)的底層數(shù)據(jù)結(jié)構(gòu)。如有任何問(wèn)題或需要進(jìn)一步了解,請(qǐng)隨時(shí)提出。
線(xiàn)程安全的集合
在 Java 中,部分集合類(lèi)是線(xiàn)程安全的,也就是說(shuō)它們?cè)诙嗑€(xiàn)程環(huán)境下可以安全地進(jìn)行并發(fā)操作而無(wú)需額外的同步措施。以下是一些常見(jiàn)的線(xiàn)程安全集合類(lèi):
1. **Vector**:Vector 是一個(gè)線(xiàn)程安全的動(dòng)態(tài)數(shù)組,與 ArrayList 類(lèi)似,但所有的方法都是同步的。
2. **Stack**:Stack 是一個(gè)基于 Vector 實(shí)現(xiàn)的棧,也是線(xiàn)程安全的。
3. **Hashtable**:Hashtable 是一個(gè)線(xiàn)程安全的哈希表,與 HashMap 類(lèi)似,但所有的方法都是同步的。
4. **Collections.synchronizedList(List<T> list)**:通過(guò) Collections 工具類(lèi)的 synchronizedList 方法可以創(chuàng)建一個(gè)線(xiàn)程安全的 List。
5. **ConcurrentHashMap**:ConcurrentHashMap 是 Java 并發(fā)包中提供的線(xiàn)程安全的哈希表實(shí)現(xiàn),使用分段鎖技術(shù)來(lái)提高并發(fā)性能。
6. **CopyOnWriteArrayList**:CopyOnWriteArrayList 是一個(gè)線(xiàn)程安全的動(dòng)態(tài)數(shù)組,采用寫(xiě)時(shí)復(fù)制(Copy-On-Write)策略,在寫(xiě)操作時(shí)會(huì)復(fù)制一份新的數(shù)組,因此讀操作不會(huì)阻塞寫(xiě)操作,適合讀多寫(xiě)少的場(chǎng)景。
7. **CopyOnWriteArraySet**:CopyOnWriteArraySet 是 CopyOnWriteArrayList 的 Set 實(shí)現(xiàn),也是線(xiàn)程安全的。
這些線(xiàn)程安全的集合類(lèi)提供了在多線(xiàn)程環(huán)境下安全地操作集合的方法,避免了線(xiàn)程競(jìng)態(tài)條件和并發(fā)修改異常。在選擇集合類(lèi)時(shí),根據(jù)具體的需求和場(chǎng)景來(lái)考慮是否需要線(xiàn)程安全的集合類(lèi)。需要注意的是,雖然線(xiàn)程安全集合類(lèi)可以提供基本的線(xiàn)程安全性,但在特定復(fù)雜場(chǎng)景下可能仍需要額外的同步控制。希望以上信息對(duì)你有所幫助,如有任何問(wèn)題或需要進(jìn)一步了解,請(qǐng)隨時(shí)提出。
HashMap的put方法的具體流程?
HashMap 的 put 方法是向 HashMap 中添加鍵值對(duì)的方法,在 Java 中實(shí)現(xiàn)了哈希表的功能,其具體流程如下:
1. **計(jì)算鍵的哈希值**:首先,HashMap 會(huì)根據(jù)鍵的 hashCode 方法計(jì)算鍵的哈希值。如果鍵為 null,則哈希值為 0。
2. **計(jì)算存儲(chǔ)位置**:接著,HashMap 根據(jù)哈希值以及 HashMap 的容量進(jìn)行計(jì)算,確定鍵值對(duì)在數(shù)組中的存儲(chǔ)位置(也稱(chēng)為桶(Bucket))。
3. **查找是否存在相同鍵**:在確定的存儲(chǔ)位置上,HashMap 需要檢查是否已經(jīng)存在相同哈希值的鍵,如果存在相同哈希值的鍵,則需要繼續(xù)比較鍵的 equals 方法來(lái)確定是否是同一個(gè)鍵。
4. **插入/替換鍵值對(duì)**:如果沒(méi)有找到相同的鍵,則直接插入鍵值對(duì);如果找到了相同的鍵,則會(huì)替換相同鍵的值。
5. **檢查是否需要進(jìn)行擴(kuò)容**:在插入后,HashMap 會(huì)檢查當(dāng)前已存儲(chǔ)的鍵值對(duì)數(shù)量是否超過(guò)了負(fù)載因子乘以容量(負(fù)載因子用于控制 HashMap 擴(kuò)容的時(shí)機(jī)),如果超過(guò),則會(huì)觸發(fā)擴(kuò)容操作。
6. **進(jìn)行擴(kuò)容**:擴(kuò)容操作會(huì)創(chuàng)建一個(gè)新的數(shù)組,將現(xiàn)有的鍵值對(duì)重新計(jì)算存儲(chǔ)位置后插入到新數(shù)組中,同時(shí)更新 HashMap 的容量和閾值等屬性。
總的來(lái)說(shuō),HashMap 的 put 方法首先根據(jù)鍵的哈希值確定存儲(chǔ)位置,然后根據(jù)鍵的 equals 方法比較鍵是否相同,最后進(jìn)行插入或替換操作。在插入過(guò)程中會(huì)根據(jù)負(fù)載因子是否超過(guò)閾值來(lái)觸發(fā)擴(kuò)容操作。這樣可以保證 HashMap 的高效性和動(dòng)態(tài)擴(kuò)容能力。
希望以上信息對(duì)你有所幫助,如有任何問(wèn)題或需要進(jìn)一步了解,請(qǐng)隨時(shí)提出。
HashMap原理是什么,在jdk1.7和1.8中有什么區(qū)別
HashMap 根據(jù)鍵的 hashCode 值存儲(chǔ)數(shù)據(jù),大多數(shù)情況下可以直接定位到它的值,因而具有很快的訪(fǎng)問(wèn)速度,但遍歷順序卻是不確定的。 HashMap最多只允許一條記錄的鍵為null,允許多條記錄的值為 null。HashMap 非線(xiàn)程安全,即任一時(shí)刻可以有多個(gè)線(xiàn)程同時(shí)寫(xiě) HashMap,可能會(huì)導(dǎo)致數(shù)據(jù)的不一致。如果需要滿(mǎn)足線(xiàn)程安全,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有線(xiàn)程安全的能力,或者使用 ConcurrentHashMap。我們用下面這張圖來(lái)介紹
HashMap 的結(jié)構(gòu)。
JAVA7 實(shí)現(xiàn)
大方向上,HashMap 里面是一個(gè)數(shù)組,然后數(shù)組中每個(gè)元素是一個(gè)單向鏈表。上圖中,每個(gè)綠色
的實(shí)體是嵌套類(lèi) Entry 的實(shí)例,Entry 包含四個(gè)屬性:key, value, hash 值和用于單向鏈表的 next。
-
capacity:當(dāng)前數(shù)組容量,始終保持 2^n,可以擴(kuò)容,擴(kuò)容后數(shù)組大小為當(dāng)前的 2 倍。
-
loadFactor:負(fù)載因子,默認(rèn)為 0.75。
-
threshold:擴(kuò)容的閾值,等于 capacity * loadFactor
JAVA8實(shí)現(xiàn)
Java8 對(duì) HashMap 進(jìn)行了一些修改,最大的不同就是利用了紅黑樹(shù),所以其由 數(shù)組+鏈表+紅黑樹(shù) 組成。
根據(jù) Java7 HashMap 的介紹,我們知道,查找的時(shí)候,根據(jù) hash 值我們能夠快速定位到數(shù)組的具體下標(biāo),但是之后的話(huà),需要順著鏈表一個(gè)個(gè)比較下去才能找到我們需要的,時(shí)間復(fù)雜度取決
于鏈表的長(zhǎng)度,為 O(n)。為了降低這部分的開(kāi)銷(xiāo),在 Java8 中,當(dāng)鏈表中的元素超過(guò)了 8 個(gè)以后,會(huì)將鏈表轉(zhuǎn)換為紅黑樹(shù),在這些位置進(jìn)行查找的時(shí)候可以降低時(shí)間復(fù)雜度為 O(logN)。紅黑樹(shù)的插入和查找性能更好。
-
JDK 1.8中,對(duì)于哈希碰撞的處理采用了尾插法,新的鍵值對(duì)會(huì)添加到鏈表末尾而不是頭部,以減少鏈表的倒置。
#
HashMap和HashTable的區(qū)別及底層實(shí)現(xiàn)
HashMap和HashTable對(duì)比
HashMap和HashTable是Java中兩個(gè)常用的鍵值對(duì)存儲(chǔ)的類(lèi),它們之間有幾個(gè)主要的區(qū)別和底層實(shí)現(xiàn)方式:
-
線(xiàn)程安全性:
-
HashMap是非線(xiàn)程安全的,不保證在多線(xiàn)程環(huán)境下的并發(fā)操作的正確性。
-
HashTable是線(xiàn)程安全的,通過(guò)在關(guān)鍵方法上添加synchronized關(guān)鍵字來(lái)保證線(xiàn)程安全性。但這也導(dǎo)致了在多線(xiàn)程環(huán)境下的性能相對(duì)較低。
-
-
鍵值對(duì)的null值:
-
HashMap允許鍵和值都為null。即可以插入null鍵,也可以插入null值。
-
HashTable不允許鍵或者值為null,如果插入null鍵或者值會(huì)拋出NullPointerException。
-
-
初始容量和擴(kuò)容:
-
HashMap的初始容量默認(rèn)為16,加載因子默認(rèn)為0.75。當(dāng)HashMap的元素個(gè)數(shù)超過(guò)容量和加載因子的乘積時(shí),會(huì)進(jìn)行擴(kuò)容,擴(kuò)容為原來(lái)容量的兩倍。
-
HashTable的初始容量默認(rèn)為11,加載因子默認(rèn)為0.75。當(dāng)元素個(gè)數(shù)超過(guò)容量和加載因子的乘積時(shí),會(huì)進(jìn)行擴(kuò)容,擴(kuò)容為原來(lái)容量的兩倍再加1。
-
-
底層實(shí)現(xiàn):
-
HashMap底層使用數(shù)組和鏈表/紅黑樹(shù)的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)。當(dāng)鏈表長(zhǎng)度超過(guò)閾值(8)時(shí),鏈表會(huì)轉(zhuǎn)換為紅黑樹(shù),以提高查找效率。
-
HashTable底層使用數(shù)組和單向鏈表的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)。
-
總的來(lái)說(shuō),HashMap相對(duì)于HashTable來(lái)說(shuō)更常用,它在性能上表現(xiàn)更好,允許null鍵和null值,但不是線(xiàn)程安全的。HashTable適用于舊版本的Java或者需要在多線(xiàn)程環(huán)境下進(jìn)行操作時(shí),但需要注意它的性能相對(duì)較低。
6.HashMap鏈表插入節(jié)點(diǎn)的方式 在Java1.7中,插入鏈表節(jié)點(diǎn)使用頭插法。Java1.8中變成了尾插法
7.Java1.8的hash()中,將hash值高位(前16位)參與到取模的運(yùn)算中,使得計(jì)算結(jié)果的不確定性增強(qiáng),降低發(fā)生哈希碰撞的概率
image-20211018214936478
HashMap擴(kuò)容優(yōu)化:
擴(kuò)容以后,1.7對(duì)元素進(jìn)行rehash算法,計(jì)算原來(lái)每個(gè)元素在擴(kuò)容之后的哈希表中的位置,1.8借助2倍擴(kuò)容機(jī)制,元素不需要進(jìn)行重新計(jì)算位置
JDK 1.8 在擴(kuò)容時(shí)并沒(méi)有像 JDK 1.7 那樣,重新計(jì)算每個(gè)元素的哈希值,而是通過(guò)高位運(yùn)算(e.hash & oldCap)來(lái)確定元素是否需要移動(dòng),比如 key1 的信息如下:
使用 e.hash & oldCap 得到的結(jié)果,高一位為 0,當(dāng)結(jié)果為 0 時(shí)表示元素在擴(kuò)容時(shí)位置不會(huì)發(fā)生任何變化,而 key 2 信息如下
高一位為 1,當(dāng)結(jié)果為 1 時(shí),表示元素在擴(kuò)容時(shí)位置發(fā)生了變化,新的下標(biāo)位置等于原下標(biāo)位置 + 原數(shù)組長(zhǎng)度hashmap,**不必像1.7一樣全部重新計(jì)算位置**
為什么hashmap擴(kuò)容的時(shí)候是兩倍?
查看源代碼
在存入元素時(shí),放入元素位置有一個(gè) (n-1)&hash 的一個(gè)算法,和hash&(newCap-1),這里用到了一個(gè)&位運(yùn)算符
當(dāng)HashMap的容量是16時(shí),它的二進(jìn)制是10000,(n-1)的二進(jìn)制是01111,與hash值得計(jì)算結(jié)果如下
下面就來(lái)看一下HashMap的容量不是2的n次冪的情況,當(dāng)容量為10時(shí),二進(jìn)制為01010,(n-1)的二進(jìn)制是01001,向里面添加同樣的元素,結(jié)果為
可以看出,有三個(gè)不同的元素進(jìn)過(guò)&運(yùn)算得出了同樣的結(jié)果,嚴(yán)重的hash碰撞了
只有當(dāng)n的值是2的N次冪的時(shí)候,進(jìn)行&位運(yùn)算的時(shí)候,才可以只看后幾位,而不需要全部進(jìn)行計(jì)算
在翻倍擴(kuò)容的情況下,原來(lái)的N個(gè)元素將被分布到新數(shù)組的2N個(gè)位置上,這種分布方式可以有效地減少哈希沖突發(fā)生的可能性,提高了HashMap的查詢(xún)和插入性能。
hashmap線(xiàn)程安全的方式?
HashMap本身是非線(xiàn)程安全的,也就是說(shuō)在并發(fā)環(huán)境中同時(shí)讀寫(xiě)HashMap可能會(huì)導(dǎo)致數(shù)據(jù)不一致的問(wèn)題。如果在多線(xiàn)程環(huán)境中需要使用HashMap,可以使用以下幾種方式來(lái)確保線(xiàn)程安全性:
-
使用Collections工具類(lèi)的synchronizedMap方法,將HashMap包裝成一個(gè)線(xiàn)程安全的Map。示例代碼如下:
Map<Object, Object> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
這種方式會(huì)對(duì)整個(gè)Map進(jìn)行同步,保證每個(gè)操作的原子性和互斥性,但是會(huì)降低并發(fā)性能。
-
使用ConcurrentHashMap類(lèi),它是Java提供的線(xiàn)程安全的哈希表實(shí)現(xiàn)。ConcurrentHashMap采用了鎖分段技術(shù),在不同的段上實(shí)現(xiàn)了獨(dú)立的鎖,并發(fā)性能比使用Collections.synchronizedMap要好。示例代碼如下:
Map<Object, Object> concurrentHashMap = new ConcurrentHashMap<>();
ConcurrentHashMap允許多個(gè)線(xiàn)程同時(shí)讀取,且讀操作不需要加鎖。只有寫(xiě)操作需要加鎖,并且寫(xiě)操作只鎖定當(dāng)前操作的段,不會(huì)導(dǎo)致整個(gè)Map被鎖定。
-
使用并發(fā)工具類(lèi)來(lái)控制對(duì)HashMap的訪(fǎng)問(wèn),例如使用讀寫(xiě)鎖(ReentrantReadWriteLock)來(lái)保證讀寫(xiě)操作的安全性。在讀多寫(xiě)少的場(chǎng)景下,讀取操作可以同時(shí)進(jìn)行,而寫(xiě)入操作會(huì)獨(dú)占鎖。示例代碼如下:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); Map<Object, Object> map = new HashMap<>(); ? // 寫(xiě)操作 lock.writeLock().lock(); try {// 更新或者添加操作map.put(key, value); } finally {lock.writeLock().unlock(); } ? // 讀操作 lock.readLock().lock(); try {// 讀取操作Object value = map.get(key); } finally {lock.readLock().unlock(); }
使用讀寫(xiě)鎖可以提高并發(fā)性能,因?yàn)樽x操作可以同時(shí)進(jìn)行,讀線(xiàn)程之間不會(huì)互斥。
請(qǐng)注意,在多線(xiàn)程環(huán)境中使用HashMap時(shí),僅僅通過(guò)加鎖來(lái)保證線(xiàn)程安全性可能不足以滿(mǎn)足高并發(fā)的需求,還需要根據(jù)具體的業(yè)務(wù)場(chǎng)景來(lái)選擇合適的方式。
說(shuō)一下 HashSet 的實(shí)現(xiàn)原理? - HashSet如何檢查重復(fù)?HashSet是如何保證數(shù)據(jù)不可重復(fù)的?
HashSet 是 Java 中的一種集合類(lèi),它基于哈希表實(shí)現(xiàn)。下面是 HashSet 的實(shí)現(xiàn)原理和它如何保證數(shù)據(jù)不可重復(fù)的方式:
1. **HashSet 的實(shí)現(xiàn)原理**:
? ?- HashSet 內(nèi)部是通過(guò) HashMap 來(lái)實(shí)現(xiàn)的,實(shí)際上 HashSet 只是對(duì) HashMap 中 key 集合的一種包裝。
? ?- 在 HashSet 內(nèi)部使用 HashMap 存儲(chǔ)元素,以元素作為 key,value 則為一個(gè)固定的對(duì)象(比如 `Object`)。
? ?- 當(dāng)向 HashSet 中添加元素時(shí),實(shí)際上是將元素作為 key 放入 HashMap 中,value 則為一個(gè)固定的對(duì)象。
? ?- HashSet 利用 HashMap 的 key 值不能重復(fù)的特性,保證元素不可重復(fù)。
2. **HashSet 如何檢查重復(fù)**:
? ?- 當(dāng)向 HashSet 中添加元素時(shí),首先會(huì)調(diào)用元素的 `hashCode()` 方法得到哈希碼,然后根據(jù)哈希碼計(jì)算出在數(shù)組中的位置。
? ?- 如果該位置上已經(jīng)存儲(chǔ)了元素(存在哈希沖突),則會(huì)調(diào)用元素的 `equals()` 方法來(lái)比較新元素和已有元素是否相等。
? ?- 如果新元素和已有元素相等(`equals()` 返回 true),則將新元素覆蓋原有元素;否則將新元素插入到數(shù)組中。
? ?- HashSet 通過(guò)哈希碼和 equals 方法來(lái)檢查重復(fù)元素,并確保數(shù)據(jù)不可重復(fù)。
通過(guò)利用哈希表的特性,HashSet 能夠?qū)崿F(xiàn)高效地檢查重復(fù)元素,并保證集合中不包含重復(fù)數(shù)據(jù)。在使用 HashSet 時(shí),需要保證集合中元素正確實(shí)現(xiàn)了 `hashCode()` 和 `equals()` 方法,以確保 HashSet 能夠正確地工作。
ArrayList和LinkedList有什么區(qū)別
ArrayList和LinkedList是Java中常用的兩種集合類(lèi),它們?cè)趯?shí)現(xiàn)上有以下區(qū)別:
-
數(shù)據(jù)結(jié)構(gòu):ArrayList是基于數(shù)組實(shí)現(xiàn)的動(dòng)態(tài)數(shù)組,而LinkedList是基于雙向鏈表實(shí)現(xiàn)的。
-
隨機(jī)訪(fǎng)問(wèn):ArrayList支持高效的隨機(jī)訪(fǎng)問(wèn),可以通過(guò)索引直接訪(fǎng)問(wèn)元素,時(shí)間復(fù)雜度為O(1)。而LinkedList需要從頭節(jié)點(diǎn)或尾節(jié)點(diǎn)開(kāi)始遍歷,時(shí)間復(fù)雜度為O(n)。
-
插入和刪除:LinkedList在插入和刪除元素時(shí),其時(shí)間復(fù)雜度是O(1),因?yàn)橹恍枰薷墓?jié)點(diǎn)的指針即可。而ArrayList在插入和刪除元素時(shí),需要移動(dòng)其他元素,時(shí)間復(fù)雜度為O(n)。
-
內(nèi)存占用:由于ArrayList是基于數(shù)組實(shí)現(xiàn)的,它需要分配一塊連續(xù)的內(nèi)存空間來(lái)存儲(chǔ)元素。而LinkedList需要額外的空間來(lái)存儲(chǔ)節(jié)點(diǎn)之間的指針關(guān)系。因此,如果需要存儲(chǔ)大量的元素,ArrayList的內(nèi)存占用通常比LinkedList更小。
根據(jù)上述區(qū)別,可以得出一些適用場(chǎng)景:
-
當(dāng)需要高效的隨機(jī)訪(fǎng)問(wèn)和修改元素時(shí),使用ArrayList更合適。
-
當(dāng)需要頻繁執(zhí)行插入和刪除操作,而對(duì)隨機(jī)訪(fǎng)問(wèn)性能要求較低時(shí),使用LinkedList更合適。
-
LinkedList可以作為棧和隊(duì)列使用
需要根據(jù)具體的場(chǎng)景和需求來(lái)選擇使用ArrayList還是LinkedList。在實(shí)際開(kāi)發(fā)中,可以根據(jù)數(shù)據(jù)訪(fǎng)問(wèn)和操作的特點(diǎn)選擇最適合的集合類(lèi)。
ArrayList擴(kuò)容
每個(gè)ArrayList實(shí)例都有一個(gè)容量,該容量是指來(lái)存儲(chǔ)列表元素的數(shù)組的大小,該容量至少等于列表數(shù)組的大小,隨著ArrayList的不斷添加元素,其容量也在自動(dòng)增長(zhǎng),自動(dòng)增長(zhǎng)會(huì)將原來(lái)數(shù)組的元素向新的數(shù)組進(jìn)行copy。如果提前預(yù)判數(shù)據(jù)量的大小,可在構(gòu)造ArrayList時(shí)指定其容量。
-
創(chuàng)建新數(shù)組:根據(jù)當(dāng)前數(shù)組的容量和擴(kuò)容策略(一般是當(dāng)前容量的1.5倍或2倍),創(chuàng)建一個(gè)新的數(shù)組。
-
復(fù)制元素:將當(dāng)前數(shù)組中的元素逐個(gè)復(fù)制到新數(shù)組中。
-
更新引用:將ArrayList內(nèi)部的引用指向新數(shù)組,以便后續(xù)的操作使用新數(shù)組。
沒(méi)有指定初始容量時(shí),初始數(shù)組容量為10
4.垃圾回收:舊的數(shù)組因?yàn)闆](méi)有被引用,會(huì)由垃圾回收器進(jìn)行回收。
Array和ArrayList的區(qū)別
Array(數(shù)組)和ArrayList(數(shù)組列表)在以下幾個(gè)方面有區(qū)別:
-
大小固定 vs 可變大小:
-
數(shù)組的大小是固定的,在創(chuàng)建時(shí)需要指定長(zhǎng)度,并且不能動(dòng)態(tài)地改變數(shù)組的大小。
-
ArrayList的大小是可變的,可以動(dòng)態(tài)地添加、刪除和修改元素,它會(huì)根據(jù)需要自動(dòng)增加或減少內(nèi)部存儲(chǔ)空間。
-
-
數(shù)據(jù)類(lèi)型:
-
數(shù)組可以存儲(chǔ)任意類(lèi)型的元素,包括基本數(shù)據(jù)類(lèi)型(如int、char等)和引用數(shù)據(jù)類(lèi)型(如對(duì)象、字符串等)。
-
ArrayList只能存儲(chǔ)引用數(shù)據(jù)類(lèi)型的元素,不能直接存儲(chǔ)基本數(shù)據(jù)類(lèi)型,需要使用對(duì)應(yīng)的包裝類(lèi)(如Integer、Character等)進(jìn)行包裝。
-
-
內(nèi)存分配和訪(fǎng)問(wèn):
-
數(shù)組在內(nèi)存中是連續(xù)分配的,可以通過(guò)索引直接訪(fǎng)問(wèn)元素,訪(fǎng)問(wèn)速度更快。
-
ArrayList內(nèi)部使用數(shù)組作為存儲(chǔ)結(jié)構(gòu),但是它還包含了額外的邏輯來(lái)支持動(dòng)態(tài)調(diào)整大小和其他操作。訪(fǎng)問(wèn)ArrayList中的元素需要通過(guò)方法調(diào)用。
-
-
功能和操作:
-
數(shù)組提供了一組基本操作,如讀取和修改元素,通過(guò)索引查找元素等。但數(shù)組沒(méi)有提供高級(jí)的集合操作,需要手動(dòng)編寫(xiě)代碼來(lái)實(shí)現(xiàn)例如過(guò)濾、映射等功能。
-
ArrayList實(shí)現(xiàn)了Java的List接口,提供了一組豐富的方法來(lái)操作其中的元素,如添加、刪除、查找、排序等,同時(shí)還支持集合操作(如集合交并補(bǔ)、過(guò)濾、映射等)。
-
總結(jié)起來(lái),數(shù)組適合在大小固定且需要高效訪(fǎng)問(wèn)的情況下使用,而ArrayList適用于需要?jiǎng)討B(tài)大小和更多操作的場(chǎng)景。如果頻繁進(jìn)行插入、刪除等操作,并且不需要直接訪(fǎng)問(wèn)元素的具體索引位置,使用ArrayList更加方便。
List和數(shù)組之間的轉(zhuǎn)換
在Java中,可以使用以下方法進(jìn)行List和數(shù)組之間的轉(zhuǎn)換:
-
List轉(zhuǎn)換為數(shù)組:
-
使用List的
toArray()
方法將List轉(zhuǎn)換為數(shù)組。示例代碼如下:List<String> list = new ArrayList<>(); // 添加元素到List list.add("Hello"); list.add("World"); ? // 轉(zhuǎn)換為數(shù)組 String[] array = list.toArray(new String[0]);
注意:在將List轉(zhuǎn)換為數(shù)組時(shí),需要提供一個(gè)指定類(lèi)型和大小的數(shù)組作為參數(shù)。如果指定的數(shù)組大小小于List的大小,則方法內(nèi)部會(huì)創(chuàng)建一個(gè)新的數(shù)組,并將List中的元素復(fù)制到新數(shù)組中。
-
-
數(shù)組轉(zhuǎn)換為L(zhǎng)ist:
-
使用Arrays類(lèi)的
asList()
方法將數(shù)組轉(zhuǎn)換為L(zhǎng)ist。注意,這種方式返回的是一個(gè)固定大小的List,不能進(jìn)行添加、刪除操作。示例代碼如下:String[] array = { "Hello", "World" }; ? // 轉(zhuǎn)換為L(zhǎng)ist List<String> list = Arrays.asList(array);
通過(guò)
asList()
得到的List是一個(gè)固定大小的List,對(duì)其進(jìn)行添加或刪除操作會(huì)拋出UnsupportedOperationException異常。 -
另一種方式是使用ArrayList的構(gòu)造方法,將數(shù)組中的元素逐個(gè)添加到ArrayList中。示例代碼如下:
String[] array = { "Hello", "World" }; ? // 轉(zhuǎn)換為L(zhǎng)ist List<String> list = new ArrayList<>(Arrays.asList(array));
這種方式得到的是一個(gè)可操作的ArrayList,可以對(duì)其進(jìn)行添加、刪除等操作。
-
需要注意的是,在進(jìn)行List和數(shù)組之間的轉(zhuǎn)換時(shí),數(shù)組中的數(shù)據(jù)類(lèi)型必須與List中的元素類(lèi)型一致。
數(shù)組類(lèi)型和集合
##
高并發(fā)中的集合有哪些問(wèn)題
第一代線(xiàn)程安全集合類(lèi)
Vector、Hashtable
是怎么保證線(xiàn)程安排的: 使用synchronized修飾方法*
缺點(diǎn):效率低下
第二代線(xiàn)程非安全集合類(lèi)
ArrayList、HashMap
線(xiàn)程不安全,但是性能好,用來(lái)替代Vector、Hashtable ???????????
使用ArrayList、HashMap,需要線(xiàn)程安全怎么辦呢?
使用 Collections.synchronizedList(list); Collections.synchronizedMap(m);
底層使用synchronized代碼塊鎖 雖然也是鎖住了所有的代碼,但是鎖在方法里邊,并所在方法外邊性能可以理解為稍有提高吧。畢竟進(jìn)方法本身就要分配資源的
第三代線(xiàn)程安全集合類(lèi)
在大量并發(fā)情況下如何提高集合的效率和安全呢?
java.util.concurrent.*
ConcurrentHashMap:
CopyOnWriteArrayList :
CopyOnWriteArraySet: 注意 不是CopyOnWriteHashSet*
底層大都采用Lock鎖(1.8的ConcurrentHashMap不使用Lock鎖),保證安全的同時(shí),性能也很高。
ConcurrentHashMap底層原理是什么?
1.7 數(shù)據(jù)結(jié)構(gòu): 內(nèi)部主要是一個(gè)Segment數(shù)組,而數(shù)組的每一項(xiàng)又是一個(gè)HashEntry數(shù)組,元素都存在HashEntry數(shù)組里。因?yàn)槊看捂i定的是Segment對(duì)象,也就是整個(gè)HashEntry數(shù)組,所以又叫分段鎖。
1.8 數(shù)據(jù)結(jié)構(gòu): 與HashMap一樣采用:數(shù)組+鏈表+紅黑樹(shù)
底層原理則是采用鎖鏈表或者紅黑樹(shù)頭結(jié)點(diǎn),相比于HashTable的方法鎖,力度更細(xì),是對(duì)數(shù)組(table)中的桶(鏈表或者紅黑樹(shù))的頭結(jié)點(diǎn)進(jìn)行鎖定,這樣鎖定,只會(huì)影響數(shù)組(table)當(dāng)前下標(biāo)的數(shù)據(jù),不會(huì)影響其他下標(biāo)節(jié)點(diǎn)的操作,可以提高讀寫(xiě)效率。 putVal執(zhí)行流程:
-
判斷存儲(chǔ)的key、value是否為空,若為空,則拋出異常
-
計(jì)算key的hash值,隨后死循環(huán)(該循環(huán)可以確保成功插入,當(dāng)滿(mǎn)足適當(dāng)條件時(shí),會(huì)主動(dòng)終止),判斷table表為空或者長(zhǎng)度為0,則初始化table表
-
根據(jù)hash值獲取table中該下標(biāo)對(duì)應(yīng)的節(jié)點(diǎn),如果該節(jié)點(diǎn)為空,則根據(jù)參數(shù)生成新的節(jié)點(diǎn),并以CAS的方式進(jìn)行更新,并終止死循環(huán)。
-
如果該節(jié)點(diǎn)的hash值是MOVED(-1),表示正在擴(kuò)容,則輔助對(duì)該節(jié)點(diǎn)進(jìn)行轉(zhuǎn)移。
-
對(duì)數(shù)組(table)中的節(jié)點(diǎn),即桶的頭結(jié)點(diǎn)進(jìn)行鎖定,如果該節(jié)點(diǎn)的hash大于等于0,表示此桶是鏈表,然后對(duì)該桶進(jìn)行遍歷(死循環(huán)),尋找鏈表中與put的key的hash值相等,并且key相等的元素,然后進(jìn)行值的替換,如果到鏈表尾部都沒(méi)有符合條件的,就新建一個(gè)node,然后插入到該桶的尾部,并終止該循環(huán)遍歷。
-
如果該節(jié)點(diǎn)的hash小于0,并且節(jié)點(diǎn)類(lèi)型是TreeBin,則走紅黑樹(shù)的插入方式。
-
判斷是否達(dá)到轉(zhuǎn)化紅黑樹(shù)的閾值,如果達(dá)到閾值,則鏈表轉(zhuǎn)化為紅黑樹(shù)。