原文:JavaGuide
2.1 Java 基礎
1. 面向對象和面向過程的區別
- 面向過程:面向過程的性能比面向對象的高。因為累調用時需要實例化,開銷比較大,比較消耗資源。但面向過程沒有想象對象易維護、易復用、易擴展。
- 面向對象:面向對象易維護、易復用、易擴展。因為面向對象有封裝、繼承、多態的特性。
注意: Java 性能差的主要原因不是因為它是面向對象語言,而是因為Java是半編譯語言,最終執行的代碼并不是CPU直接執行的二進制。
2. Java 語言有哪些特點
- 1.簡單易學
- 2.面向對象(封裝、繼承、多態)
- 3.平臺無關性(Java虛擬機)
- 4.可靠性
- 5.安全性
- 6.支持多線程(C++需要調用操作系統的多線程功能)
- 7.支持網絡編程并且很方便。
- 8.編譯與解釋并存
3. JVM、JDK、JRE
JVM
Java虛擬機(JVM)是運行Java字節碼的虛擬機。JVM 有針對不同系統的特定實現(Windows、Linux、MacOS),目的是使用相同的字節碼,他們都會給出相同的結果。
Java字節碼
在Java中,JVM可以理解的代碼就叫做字節碼(擴展名為.class的文件),它不面向任何特定的處理器,只面向虛擬機。
Java 語言通過字節碼的方式,在一定程度上解決了傳統解釋型語言執行效率低的問題,同時又保留了解釋型語言可移植的特點。所以Java程序運行時比較高效,并且無序重新編譯便可在不同操作系統的計算機上運行。
Java 代碼編譯:
Java字節碼 == JVM ==> 二進制機器碼時,JVM類加載器首先加載字節碼文件,然后通過解釋器逐行解釋執行。這種方式的執行速度相對比較慢。而且有些方法和代碼塊是經常需要被調用的(熱點代碼),所以后面引進了JIT編譯器。JIT屬于運行時編譯,JIT完成一次編譯后,會將字節碼對應的機器碼保存下來,下次可以直接使用。
Java字節碼和不同系統的JVM實現是Java語言“Write Once,Run Anywhere”的關鍵所在。
JDK 和 JRE
JRE:Java運行時環境。它是運行已編譯Java 程序所需的所有內容的集合。包括Java虛擬機(JVM)、Java類庫、Java命令和其他一些基礎構件。但是它不能用于創建新程序。
JDK:Java Development Kit,它是功能齊全的Java SDK。他包含Java運行環境(JRE)、編譯器(javac)和工具(javadoc和jdb)。JDK能夠創建和編譯程序。
4. Oracle JDK 和 OpenJDK的對比
對于Java 7, 這兩個沒有關鍵不同的地方。
OpenJDK 是基于Sum捐贈的HotSpot源代碼。
- Open JDK 是一個參考模型并且是完全開源的,而Oracle JDK是 OpenJDK的一個實現,并不是完全開源的。
- Oracle JDK 比 Open JDK 更穩定。Open JDK 和 Oracle JDK 的代碼幾乎相同,但 Oracle JDK 有更多的類和一些bug修復,并且Oracle JDK 經過了徹底的測試。
- 在響應性和JVM性能方面,Oracle JDK 相比 Open JDK 提供了更好的性能。
5. Java 和 C++ 的區別
- 1.都是面向對象的語言,都支持封裝、繼承和多態。
- Java不提供指針來直接訪問內存,程序內存更加安全。
- Java的類是單繼承的,C++支持多繼承。不過雖然Java的類不支持多繼承,但接口可以多繼承。
- 4.C語言中,字符串或字符數組的最后有一個結束符
'\0'
,Java中沒有結束符的概念。
6.Java程序的主類
一個程序可以有多個類,但是只能有一個類是主類,包含main()
方法。
7、Java 應用程序 VS Java 小程序
- Java 應用程序:從主線程main()方法啟動
- Java 小程序: 沒喲main方法,主要是嵌在瀏覽器上面運行(調用 init()或者run()來啟動)
8. 字符型常量 VS 字符串常量
字符型常量 | 字符串常量 | |
---|---|---|
形式 | 單引號表示的一個字符,例如 'k'
|
雙引號引起的若干個字符,例如"linyk3"
|
含義 | 相當于一個整型值,可以參加表達式運算 | 代表一個地址,表示該字符串在內存中存放的位置 |
內存大小 | Java中char占2個字節 | 若干個字節 |
注意:Java中每一種基本類型所占的存儲空間是不變的:
9. 構造器Constructor 能否被重寫override?
Constructor 不能被override(重寫),但是可以被overload(重載),所以你看到一個類中有多個構造函數的情況。
10.重寫和重載的區別
- 重載:發生在同一個類中。方法名相同,參數類型、參數個數、參數順序不同。 方法的返回值和訪問修飾符可以不同。
- 重寫:發生在子類中。子類對父類的允許訪問的方法的實現過程進行重新編寫。方法名、參數列表必須相同,返回值范圍小于等于父類,拋出的異常范圍小于等于父類,訪問修飾符大于等于父類。(方法提供的行為改變,而方法的外貌并沒有改變)
11.Java 面向對象編程的三大特性: 封裝、繼承、多態
封裝
封裝把一個對象的屬性私有化,同時提供一些可以被外界訪問的屬性的方法。
繼承
繼承是使用已存在的類的定義作為基礎建立新類的技術,新類的定義可以增加新的數據或新的功能,也可以使用父類的可繼承的所有功能。通過繼承我們可以非常方便的復用以前的代碼。
- 1.子類擁有父類對象所有的屬性和方法(包括私有屬性和私有方法),但是父類中的私有屬性和方法子類是無法訪問的,只是擁有。
- 2.子類可以擁有自己屬性和方法,即子類可以對父類進行擴展。
- 3.子類可以用自己的方式實現父類的方法。
多態
多態指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時并不確定,而是在程序運行期間才確定。即一個引用變量到底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在程序運行期間才能決定。
在Java 中有兩種形式可以實現多態:
- 1.繼承:多個子類對同一個方法的重寫。
- 2.接口:實現接口并覆蓋接口中的同一方法。
12.String、StringBuffer 和 StringBuilder
String :
Java 9 之前: private final char[] value
Java 9 之后: private final byte[] value
StringBuilder 和 StringBuffer 都繼承自 AbstractStringBuilder 類,char[] value
,沒有使用 final
關鍵字。
每次對String類型進行改變的時候,都會生成一個新的String對象,然后將變量指向新的String對象。StringBuffer 每次都會對自己進行操作,而不是生成新的對象。
特性 | String | StringBuffer | StringBuilder |
---|---|---|---|
可變性 | 不可變 | 可變 | 可變 |
線程安全型 | 不可變常量,線程安全 | 同步鎖,線程安全 | 非線程安全 |
性能 | 性能慢 | 性能快 | 比StringBuffer性能提升10%-15% |
總結:
- 1.操作少量數據:適用String
- 2.單線程操作大量數據:適用StringBuilder
- 3.多線程操作大量數據:適用StringBuffer;
13.自動裝箱和拆箱
- 裝箱:基本類型 => 包裝類型
- 拆箱:包裝類型 => 基本類型
14.靜態方法內調用一個非靜態成員是非法的
靜態方法可以通過類調用,不必通過生成對象來進行調用。
而非靜態成員需要通過對象來調用。
15.Java定義一個無參構造方法的作用
Java 程序在執行子類的構造方法之前,如果沒有用 super()
來調用父類特定的構造方法,則會調用父類中無參構造方法,因此,如果父類中只定義了有參數的構造方法,而在子類的構造方法中沒有用super() 來調用父類中特定的構造方法,則編譯時間發生錯誤:Java程序在父類中找不到無參構造方法可供執行。
解決方案就是在父類中加上一個不做事且沒有參數的構造方法。
16. 略過
17. 接口和抽象類
- 1.接口的方法默認是 public的,所有方法在接口中不能有實現(Java 8 開始接口方法可以有默認實現),而抽象類可以有非抽象的方法。
- 2.接口中除了static、final 變量,不能有其他變量,而抽象類中則不一定。
- 3.一個類中可以實現多個接口,但只能實現一個抽象類。接口自己本身可以通過 extends 關鍵字擴展多個接口。
- 4.接口方法默認修飾符是public,抽象方法可以有public、protected和default這些修飾符。(抽象類就是為了被重寫,所以不能使用private 關鍵字修飾)
- 5.設計層面來說,抽象是對類的抽象,是一種模板設計,而接口是對行為的抽象,是一種行為的規范。
備注:
1.JDK8中,接口也可以定義靜態方法,可以直接用接口名調用。實現類和實現是不可以調用的。 如果直接實現兩個接口,接口中定義了一樣的默認方法,則必須重寫,不然會報錯。
2.JDK9中,接口被允許定義私有化方法。
Java 接口在JDK7 => JDK9 的變化:
- 1.JDK7或之前的版本,接口里面只能有常量變量和抽象方法。這些接口方法必須由實現類來實現。
- 2.JDK8 接口允許有默認方法和靜態方法功能
- JDK9 在接口中引入了私有方法和私有靜態方法。
18.成員變量 VS 局部變量
成員變量: 屬于類,可以被public、private、static等修飾符所修飾。
局部變量: 方法中定義的變量或方法的參數,不能被訪問控制修飾符及static修飾。
但是成員變量和局部變量都能被final所修飾。
成員變量如果是static 修飾,則屬于類的變量,如果沒有使用staitc修飾,則屬于實例對象的。對象是存在于堆內存,局部變量存在于棧內存。
成員對象是對象的一部分,隨對象的創建而存在,而局部變量隨著方法的調用而自動消失。
如果成員變量沒有被賦初始值,則會自動以類型的默認值賦值(如果是final,則必須被顯式的賦值)。局部變量不會自動賦值。
19.創建對象
使用 new
運算符來創建對象實例。對象實例存儲在堆內存中,對象引用存儲在棧內存中,對象引用指向對象實例。
20.返回值
方法的返回值:指我們獲取都的某個方法中的代碼執行后產生的結果
返回值的作用:接收結果,并用于其他操作
21. 構造方法
類的構造方法:作用是完成對類對象的初始化工作。
如果一個類么有聲明構造方法,也可以正確執行。因為一個類即使沒有聲明構造方法,也會有默認的無參構造方法。
22. 構造方法的特性
- 1.函數名與類名相同
- 2.沒有返回值,也不能用void聲明構造函數
- 3.生成類的對象時自動執行,無需調用。
23.靜態方法 VS 實例方法
- 可以通過類或對象來調用靜態方法:
類名.方法名
,對象名.方法名
- 調用實例方法是能通過
對象名.方法名
,需要創建對象后才能調用。
靜態方法在訪問本類的成員時,只能訪問靜態成員,而不允許訪問實例成員變量和實例方法。實例方法無此限制。
24.對象的相等 VS 引用的相等
對象相等:內存中存放的內容是否相等
引用相等:指向的內存地址是否相等
25.子類構造方法里調用父類無參構造方法
在調用子類構造方法之前會調用父類無參構造方法,目的是為了幫助子類做初始化工作。
26. == VS equals
- == : 判斷兩個對象是不是相等。(基本數據類型比較的是值,引用類型比較的是內存地址)
- equals: 判斷兩個對象是否相等。Object.equals() 默認是等價于 "==",比較兩個對象的內存地址。一般會覆蓋 equals 方法來比較兩個對象的內容。
public class test {
public static void main(String[] args) {
String a = new String("ab"); // a 為一個引用
String b = new String("ab"); // b 為另一個引用,內容一樣,內存地址不一樣
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 從常量池中查找
System.out.println(a == b); // false
System.out.println(aa == bb); // true
System.out.println(a.equals(b)); // true
System.out.println(42 == 42.0); // true
}
}
String 的equals 方法是被重寫過的,比較的是兩個對象的內容。
當創建String類型的對象時,虛擬機在常量池中查找有沒有已經存在的值, 如果有就直接賦值給當前引用, 沒有就重新創建一個String對象。
27 hashCode 與 equals
- hashCode() : 是在Object 對象中的函數,可以獲取哈希碼,也稱散列碼,是一個int整數。在哈希表中用來確定索引的位置,在其他地方沒有作用。
hashCode() 默認的行為是對堆上的對象產生獨特的值。如果沒有重寫hashCode(), 則該class的兩個對象無論如何都不會相等。
28.Java中只有值傳遞
29.線程、進程、程序
程序:含有指令和數據的文件,程序是靜態的代碼。
進程:是系統運行的基本單位,是操作系統分配CPU時間、內存空間、文件和輸入輸出設備使用權的單位。
線程:線程是進程劃分為更小的運行單位,一個進程可以包含1個或多個線程。
30. 線程基本狀態
Java 線程在運行的生命周期有6種不同的狀態:
狀態名稱 | 說明 |
---|---|
new | 初始狀態,線程被構建,但是還沒有調用start()方法 |
runnable | 運行狀態,Java線程將就緒和運行時籠統的成為運行中 |
blocked | 阻塞狀態,線程阻塞于鎖 |
waiting | 等待狀態,線程進入等待狀態,需要等待其他線程做出一些特定動作(通知或中斷) |
time_waiting | 超時等待狀態,相比于wating,它可以在指定時間內自行返回 |
terminated | 終止狀態,表示當前線程已經執行完畢 |
31.final 關鍵字
- 1.final 修飾變量:不可變。如果是基本類型,則其數值在初始化之后不能更改,如果是引用類型,則在初始化之后不能指向另一個對象。
- 2.final 修飾方法:一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率,因為早期的Java會將final轉化為內嵌調用。類中所有private的方法都隱式地指定為final。
- 3.final 修飾類: 表示這個類不能被繼承,final類中的所有成員方法都會被隱式的指定為final方法。
32. Java 異常
Java中,所有的異常都有一個共同的祖先:java.lang.Throwable
類。它有兩個重要的子類Exception(異常)
和 Error(錯誤)
。
- Error(錯誤):是程序無法處理的錯誤,表示運行應用程序中較嚴重的問題。
- Exception(異常):程序本身可以處理的異常。
異常處理:
- try:用來捕獲異常,其后可接0個或多個catch塊,如果沒有catch塊,則必須接一個finally塊。
- catch: 用來處理try 捕獲到的異常。
- finally:無論是否捕獲或處理異常,finally的語句都會被執行。當在try 塊或 catch 塊遇到 return語句時,finally 語句塊將在方法返回之前被執行。
finally 塊不會被執行的情況:
- 1.在finally語句塊的第一行發生了異常。
- 2.已經執行了 System.exit(int) 退出程序。
- 3.程序所在的程序死亡。
- 4.關閉CPU。
注意:當try語句和finally語句都有return語句時,在方法返回之前,finally語句的內容將被執行,并且finally語句的返回值會覆蓋原來的返回值:
public static int f(int value) {
try {
return value * value;
} finally {
if(value == 2) {
return 0;
}
}
}
如果調用f(2), 返回值將是0.
33.Java 序列化
當一個類實現了serializable接口,如果沒有顯式的定義 serialVersionUID,Java 序列化機制會根據編譯的Class自動生成一個serialVersionUID,用作序列化版本比對。如果Class文件沒有發生變化,就算編譯多次, serialVersionUID 也不會變化。
如果我們不想通過編譯來強制劃分軟件版本,集實現序列化接口的實體能夠兼容先前的版本,就需要顯式的定義序列化版本UID,這樣即使Class 文件變化了,只要serialVersionUID的值不變,就可以進行正確的序列化和反序列化。
private static final long serialVersionUID = 1L
對于不想進行序列化的變量,可以使用 transient
關鍵字修飾。
transient
:阻止實例中用此關鍵字修飾的變量序列化。當反序列化時,被修飾的變量不會被持久化和恢復。
只能修飾變量,不能修飾類和方法。
34. 獲取鍵盤輸入的兩種方法
方法1. 通過Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
方法2. 通過BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
35.Java中IO流
Java中IO流分為幾種?
- 按流的流向分,可以分為輸入流和輸出流;
- 按操作單元分,可以分為字節流和字符流;
- 按流的角色分,可以分為節點流和處理流。
Java IO流共涉及40多個類,都是從如下4個抽象類基類中派生出來的:
- InputStream/ Reader:所有輸入流的基類,前者是字節輸入流,后者是字符輸入流。
- OutputStream/ Writer:所有輸出流的基類,前者是字節輸出流,后者是字符輸出流。
既然有了字節流,為什么還要有字符流
不管是文件讀寫還是網絡發送和接收,信息的最小存儲單元都是字節。
字符流是由Java虛擬機將字節轉換得到的,但是轉換過程非常耗時,并且很容易因為編碼類型出現編碼問題,所有干脆提供直接操作字符的接口,方便平時對字符進行流操作。
如果是音頻文件、圖片等媒體文件,建議用字節流;
如果涉及到字符,建議用字符流。
BIO,NIO,AIO有什么區別
BIO(Blocking I/O):同步阻塞I/O模式,數據的讀取寫入必須阻塞在一個線程內等待其完成。適用于活動連接數不是特別高(小于單機1000)的情況,可以讓每一個連接專注于自己的I/O并且編程模型簡單,也不用考慮系統的過載、限流等問題。線程池本身就是一個天然漏斗,可以緩沖一些系統處理不了的連接或請求。但是當面對十萬甚至百萬級連接時,就不適用了,需要一種更高效的I/O處理模型來應對更高的并發量。
-
NIO(New I/O): 同步非阻塞I/O模式,NIO的N可以理解為 Non-blocking, 不單純是New。它支持面向緩沖的,基于通道的I/O操作方法。 在Java1.4引入NIO框架,對應 java.nio 包,提供了Channel,Selector, Buffer等抽象。NIO 提供了與傳統BIO模型中 Socket 和 ServerSocket 相對應的 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實現。 兩種通道都支持阻塞(性能和可靠性不好)和非阻塞(性能和可靠性好)兩種模式。
- 低負載、低并發 => 采用BIO模式
- 高負載、高并發 => 采用NIO非阻塞模式
AIO(Asynchronous I/O): AIO異步非阻塞IO模型。是基于事件和回調機制實現的,也即是應操作之后會直接返回,不會堵塞在那里,當后臺處理完成,操作系統會通知相應的線程進行后續的操作。AIO也就是NIO2,Java7中引入NIO的改進版NIO2。
36. 常見關鍵字總結:final/static/this/super
final 關鍵字
final 關鍵字主要用在3個地方:變量、方法、類。
- 1.final修飾變量:如果是基本類型,則其數值在初始化之后便不能修改;如果是引用類型,則在初始化之后不能再指向另一個對象。
- 2.final修飾方法:第一個原因是把方法鎖定,以防任何繼承的類修改它的含義。第二個原因是效率,早期Java會把final 方法轉為內嵌調用,現在的Java已經不采用了內嵌優化了。類中所有的private 方法都隱式的指定為final。
- 3.final修飾類:表明這個類已經不能被繼承。final類中的所有成員方法都會被隱式的指定為final方法。
static 關鍵字
static 關鍵字主要有以下四種使用場景:
- 1.修飾成員變量和成員方法:被static修飾的成員屬于類,不屬于單個對象,成員被所有對象共享,并且可以通過類名直接調用。靜態變量存放在Java內存區域的方法區。調用格式:
類名.靜態變量名
或類名.靜態方法名()
- 2.靜態代碼塊:靜態代碼塊定義在類的方法外,不管創建多少對象,靜態代碼塊只執行一次。靜態代碼塊在非靜態代碼塊之前執行。(靜態代碼塊 => 非靜態代碼塊 => 構造方法)
- 3.靜態內部類:static修飾類的話只能修飾內部類,靜態內部類和非靜態內部類之間一個最大的區別就是:非靜態內部類在編譯完成之后會隱含的保存著一個引用,指向創建它的外圍類。但是靜態內部類沒有這個引用,這也意味著1.靜態內部類的創建不需要依賴外圍類的創建;2.它不能使用任何外圍類的非static成員變量和方法。
// 靜態內部類實現單例模式
public class Singleton {
// 聲明為 private 避免調用默認構造方法創建對象
private Singleton() {}
// 聲明為private,表明靜態內部類只能在Singleton類中訪問
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
- 4.靜態導包:Java1.5之后的新特性,可以用來導入類中的靜態資源。格式為
import static
這兩個關鍵字連用可以指定導入某個類中的指定靜態資源,并且不需要使用類名調用類中的靜態成員,可以直接使用類中靜態成員變量和方法。
this 關鍵字
this 關鍵字用于引用類的當前實例,例如
class Manager {
Employees[] employees;
void manageEmployees() {
int totalEmp = this.employees.length;
System.out.println("Total employees: " + totalEmp)'
this.report();
}
void report() { }
}
上面的例子中,this關鍵字用于兩個地方:
- this.employees.length: 訪問類Manager的當前實例的變量。
- this.report(): 調用Manager的當前實例的方法。
this 關鍵字在這里也是可以省略的。
super 關鍵字
super 關鍵字用于從子類訪問父類的變量和方法,例如:
public class SupperClass {
protected int number;
protected showNumber() {
System.out.println("number = " + number);
}
}
public class SubClass extends SupperClass {
void bar() {
super.number = 10;
super.showNumber();
}
}
上面的例子中,SubClass 訪問父類的成員變量number 并調用父類的showNumber() 方法。
注意:在構造方法中使用super() 調用父類中的其他構造方法時,該語句必須處于構造器的首行,否則編譯器會報錯。另外,this調用本類中其他構造方法時,也要放在首行。
this 和 super 不能用在static 方法中。
37.Collections 工具類和Arrays 工具類常見方法總結
Collections 工具類常用方法
- 1.排序
- 2.查找
- 3.同步控制(不推薦,需要線程安全的集合類型時請考慮使用JUC包下的并發集合)
排序操作
void reverse(List list); // 反轉
void shuffle(List list); // 隨機排序
void sort(List list); // 按照自然排序的升序
void sort(List list, Comparator c); // 定制排序,由Comparator控制排序邏輯
void swap(List list, int i, int j); // 交換兩個索引位置的元素
void swap(List list, int distance); // 旋轉,當distance為整數,將list后distance個元素整體移到前面,當distance為負數,將list的前distance個元素整體移到后面。
查找、替換操作:
int binarySearch(List list, Object key); // 二分查找,返回索引,注意list要求是有序的
int max(Collection coll); // 根據元素的自然順序,返回最大的元素
int max(Collection coll, Comparator c); // 根據定制順序,返回最大的元素
void fill(List list, Object obj); //用指定元素obj替換list中所有元素
void frequency(Collection c, Object o) ; // 統計元素c出現的次數
void indexOfSubList(List list, List target); // 統計target在list中第一次出現的索引,找不到就返回-1. 類比于 int lastIndexOfSubList(List list, List target)
boolean replaceAll(List list, Object oldVal, Object newVal); // 用新元素替換舊元素
同步控制
Collections 提供了多個synchronizedXxx()
方法,該方法可以將指定集合包裝成線程同步的集合,從而解決多線程并發訪問集合時的線程安全問題。不過最好不要使用,英文效率非常低,建議使用JUC包下的并發集合。
HashSet、TreeSet、ArrayList、LinkedList、HashMap、TreeMap 都是線程不安全的。
synchronizedCollection(Collection<T> c); // 返回線程安全的collection
synchronizedList(List<T> list); // 返回線程安全的list
synchronizedMap(Map<K,V> map); // 返回線程安全的map
synchronizedSet(Set<T> set); // 返回線程安全的set;
Collections 還可以設置不可變集合:
-
emptyXxx()
: 返回一個空的、不可變的集合對象。 -
singletonXxx()
:返回一個只包含指定對象的不可變集合。 -
unmodifiableXxx()
:返回指定集合對象的不可變視圖。
// Collection.emptyXxx(); 創建一個空的、不可改變的Xxx對象
List<Object> list = Collections.emptyList();
Set<Object> set = Collections.emptySet();
Map<Object, Object> map = Collections.emptyMap();
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(1);
arrayList.add(3);
arrayList.add(3);
HashSet<Integer> integers1 = new HashSet<>();
integers1.add(1);
integers1.add(3);
integers1.add(2);
//Collections.emptyXXX();創建一個空的、不可改變的XXX對象
List<Object> list = Collections.emptyList(); // []
Set<Object> objects = Collections.emptySet(); // []
Map<Object, Object> objectObjectMap = Collections.emptyMap(); // {}
//Collections.singletonXXX();
List<ArrayList<Integer>> arrayLists = Collections.singletonList(arrayList); // [[1,3, 3]]
Set<ArrayList<Integer>> singleton = Collections.singleton(arrayList); // [[1,3]]
Map<String, String> nihao = Collections.singletonMap("1", "你好"); //{1=你好}
//unmodifiableXXX();創建普通XXX對象對應的不可變版本
List<Integer> integers = Collections.unmodifiableList(arrayList); // [[1,3, 3]]
Set<Integer> integers2 = Collections.unmodifiableSet(integers1); //[1, 2, 3]
//添加出現異常:java.lang.UnsupportedOperationException
// list.add(1);
// arrayLists.add(arrayList);
// integers.add(1);
Arrays 類的常見操作
- 1.排序
sort()
和parallelSort()
int a[] = { 1, 3, 2, 7, 6, 5, 4, 9 };
Arrays.sort(a); // sort(int[] a)方法按照數字順序排列指定的數組。
Arrays.sort(a, 2, 6); // sort(int[] a,int fromIndex,int toIndex)按升序排列數組的指定范圍
Arrays.parallelSort(c); // parallelSort(int[] a) 按照數字順序排列指定的數組(并行的)。同sort方法一樣也有按范圍的排序
char d[] = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
Arrays.parallelSort(d); // parallelSort給字符數組排序,sort也可以
String[] strs = { "abcdehg", "abcdefg", "abcdeag" };
Arrays.sort(strs); // parallelSort給字符串排序,sort也可以
System.out.println(Arrays.toString(strs));//[abcdeag, abcdefg, abcdehg]
- 2.查找
binarySearch()
char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
Arrays.sort(e); // 排序后再進行二分查找,否則找不到
int s = Arrays.binarySearch(e, 'c');
System.out.println("字符c在數組的位置:" + s);
- 3.比較
equals()
char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
char[] f = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
Arrays.equals(e, f) // true
- 4.填充
fill()
int[] g = { 1, 2, 3, 3, 3, 3, 6, 6, 6 };
Arrays.fill(g, 3); //333333333
int[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, };
Arrays.fill(h, 0, 2, 9); // 993333666 數組中指定范圍元素重新分配值
- 5.轉列表
asList()
List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
- 6.轉字符串
toString()
char[] k = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' };
System.out.println(Arrays.toString(k));// [a, f, b, c, e, A, C, B]
- 7.復制
copyOf()
int[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, };
int i[] = Arrays.copyOf(h, 6); //123333 copyOf 方法實現數組復制,h為數組,6為復制的長度
// copyOfRange將指定數組的指定范圍復制到新數組中
int j[] = Arrays.copyOfRange(h, 6, 11); //結果66600(h數組只有9個元素這里是從索引6到索引11復制所以不足的就為0)
38.深拷貝 VS 淺拷貝
- 深拷貝:基本類型進行值傳遞,對引用類型,創建一個新的對象,并復制其內容。
-
淺拷貝:基本類型進行值傳遞,對引用類型,進行引用傳遞的拷貝。
image.png
39. 補充: 抽象類 VS 接口
1 抽象方法
抽象方法是一種頁數的方法,它只有聲明,沒有具體的實現。
abstract void fun()
2 抽象類
如果一個類含有抽象方法,就稱這個類是抽象類。抽象類在類前用 abstract 關鍵字修飾。
[public] abstract class ClassName {
abstract void fun();
}
抽象類就是為了繼承而存在的。
包含抽象方法的類成為抽象類,但抽象類中也可以有非抽象方法。抽象類和普通方法一樣,可以擁有成員變量和普通的成員方法。
抽象類和普通類的區別:
- 抽象類必須為public 或 protected,默認是public(為了繼承)
- 抽象類不能用來實例化,也就是不能用來創建對象。
- 3.如果一個類繼承了抽象類,則子類必須實現父類的抽象方法。
除了這3點,其他和普通類沒有區別。
3. 接口
接口泛指提供給別人調用的方法或函數,接口是對行為的抽象。
[public] interface InterfaceName {
}
接口中可以含有變量和方法,但是接口中的變量和方法會被隱式的指定
- 接口變量:
public static final
- 接口方法:
public abstract
接口中不能有具體的實現,接口中的方法都必須是抽象方法。
所以,可以把接口認為是一種極度抽象的類型。
4. 抽象類和接口的區別
- 1.抽象類是對一種事物的抽象,是對類的整體(屬性+行為)的抽象
- 2.接口是對行為的抽象。
類比:飛機和鳥是不同的事物,但有一個共性,都會飛行。
所以可以將飛機設計為一個類Airplane,將鳥設計為一個類Bird。但是不能將飛行也設計為類。可以將飛行設計為一個接口Fly,里面包含了飛行的方法fly().
不同類型的飛機和鳥直接繼承Airplane 和 Bird類即可,然后根據需要去實現Fly 接口。
也就是說,一個類繼承了抽象類,則子類必須是抽象類的種類。而接口實現表示的是有沒有,具不具備這種行為。(能飛,則可以實現Fly接口,不能飛則不行)
設計層面,抽象類是作為很多子類的父類,是一種模板設計。
接口是一種行為規范,是一種輻射式設計。
對于抽象類,如果添加了新的方法,子類可以不進行變更。
如果接口進行了變更,則實現這個接口的類都必須進行相應的改動。