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