文章目錄
1.訪問權限
三個顯示關鍵字設置類中的訪問權限:public private protected
如果沒有使用上述三個關鍵字設置權限,默認爲default訪問權限,則稱爲包訪問,只有同一個包下的其他類成員可以訪問。
2.組合和聚合
組合:整體刪除,部件一定刪除
聚合:整體刪除,部件不會刪除
3.“是一個”與“像是一個”
是一個----純粹替代:子類繼承父類,不添加新方法,is-a
像是一個----導出類添加新方法:子類繼承父類後增加新的方法,添加新的接口,is-like-a
14.25
95649.28
97522.78 97522.78
4.前期綁定,後期綁定
前期綁定:編譯器生成對特定函數名的調用,改調用會被解析爲將執行的代碼的絕對地址
後期綁定:程序運行到函數調用時才能確定代碼的地址,編譯器需要確保方法存在,並且對參數和返回值進行類型檢查,但不知要執行的確切代碼。(多態)
5.向上轉型
子類當做其父類來處理的過程叫向上轉型。
向下轉型:強制類型轉換(從父類到子類)
6.基本類型的存儲
new 出來的對象保存在堆內存中,不用new的變量直接存儲在棧內存中,更加高效。
7.finalize()
垃圾回收器只知道如何釋放new創建的對象的內存,不知道如何回收不是new分配的內存。
而在 Java 中,對象並非總是被垃圾回收,或者換句話說:
- 對象可能不被垃圾回收。
- 垃圾回收不等同於析構。
當垃圾回收器準備回收對象的內存時,會先調用其finalize()方法。
8.垃圾回收器如何工作
-
引用計數:每個對象含有一個引用計數器,計數爲0則清除對象,在出現循環引用時其計數都不爲0,會出現無法回收的情況,因此這種工作方式未被使用
-
自適應:
-
停止-複製:這需要先暫停程序的運行(不屬於後臺回收模式),然後將所有存活的對象從當前堆複製到另一個堆,沒有複製的就是需要被垃圾回收的。另外,當對象被複制到新堆時,它們是一個挨着一個緊湊排列,然後就可以按照前面描述的那樣簡單、直接地分配新空間了。
這種所謂的"複製回收器"效率低下主要因爲兩個原因。其一:得有兩個堆,然後在這兩個分離的堆之間來回折騰,得維護比實際需要多一倍的空間。某些 Java 虛擬機對此問題的處理方式是,按需從堆中分配幾塊較大的內存,複製動作發生在這些大塊內存之間。
其二在於複製本身。一旦程序進入穩定狀態之後,可能只會產生少量垃圾,甚至沒有垃圾。儘管如此,複製回收器仍然會將所有內存從一處複製到另一處,這很浪費。爲了避免這種狀況,一些 Java 虛擬機會進行檢查:要是沒有新垃圾產生,就會轉換到另一種模式(即"自適應")。這種模式稱爲標記-清掃(mark-and-sweep),Sun 公司早期版本的 Java 虛擬機一直使用這種技術。對一般用途而言,"標記-清掃"方式速度相當慢,但是當你知道程序只會產生少量垃圾甚至不產生垃圾時,它的速度就很快了。
-
“標記-清掃”:從棧和靜態存儲區出發,遍歷所有的引用,找出所有存活的對象。但是,每當找到一個存活對象,就給對象設一個標記,並不回收它。只有當標記過程完成後,清理動作纔開始。在清理過程中,沒有標記的對象將被釋放,不會發生任何複製動作。"標記-清掃"後剩下的堆空間是不連續的,垃圾回收器要是希望得到連續空間的話,就需要重新整理剩下的對象。
-
9.可變參數列表
public class NewVarArgs {
static void printArray(Object... args) {
for (Object obj: args) {
System.out.print(obj + " ");
}
System.out.println();
}
public static void main(String[] args) {
// Can take individual elements:
printArray(47, (float) 3.14, 11.11);
printArray(47, 3.14F, 11.11);
printArray("one", "two", "three");
printArray(new A(), new A(), new A());
// Or an array:
printArray((Object[]) new Integer[] {1, 2, 3, 4});
printArray(); // Empty list is OK
}
}
有了可變參數,你就再也不用顯式地編寫數組語法了,當你指定參數時,編譯器實際上會爲你填充數組。你獲取的仍然是一個數組
10.final
類中所有的 private 方法都隱式地指定爲 final。因爲不能訪問 private 方法,所以不能覆寫它。
當說一個類是 final (final 關鍵字在類定義之前),就意味着它不能被繼承。
Java 中除了 static 和 final 方法(private 方法也是隱式的 final)外,其他所有方法都是後期綁定。這意味着通常情況下,我們不需要判斷後期綁定是否會發生——它自動發生。
11.類初始化和加載
每個類的編譯代碼都存在於它自己獨立的文件中。該文件只有在使用程序代碼時纔會被加載。一般可以說“類的代碼在首次使用時加載“。這通常是指創建類的第一個對象,或者是訪問了類的 static 屬性或方法。構造器也是一個 static 方法儘管它的 static 關鍵字是隱式的。因此,準確地說,一個類當它任意一個 static 成員被訪問時,就會被加載。
首次使用時就是 static 初始化發生時。所有的 static 對象和 static 代碼塊在加載時按照文本的順序(在類中定義的順序)依次初始化。static 變量只被初始化一次。
初始化過程:
初始化一個類時,如果他有繼承基類,不論是否創建了基類的對象,基類都會被加載,以此類推,當加載完成後,開始執行根基類的static,以此類推。
必要的類加載完成後,開始創建對象。對象中的所有基本類型變量都被置爲默認值,對象引用被設爲 null —— 這是通過將對象內存設爲二進制零值一舉生成的。接着會調用基類的構造器。
基類構造器和派生類構造器一樣以相同的順序經歷相同的過程。當基類構造器完成後,實例變量按文本順序初始化。最終,構造器的剩餘部分被執行。
12.多態
只有普通的方法調用可以是多態的
如果一個方法是靜態(static)的,它的行爲就不具有多態性
13.接口
關鍵字 default 允許在接口中提供方法實現——在 Java 8 之前被禁止。
// interfaces/InterfaceWithDefault.java
interface InterfaceWithDefault {
void firstMethod();
void secondMethod();
default void newMethod() {
System.out.println("newMethod");
}
}
這樣可以不用實現newMethod方法而使用它。
允許在接口中添加靜態方法
特性 | 接口 | 抽象類 |
---|---|---|
組合 | 新類可以組合多個接口 | 只能繼承單一抽象類 |
狀態 | 不能包含屬性(除了靜態屬性,不支持對象狀態) | 可以包含屬性,非抽象方法可能引用這些屬性 |
默認方法 和 抽象方法 | 不需要在子類中實現默認方法。默認方法可以引用其他接口的方法 | 必須在子類中實現抽象方法 |
構造器 | 沒有構造器 | 可以有構造器 |
可見性 | 隱式 public | 可以是 protected 或友元 |
接口中的字段都自動是 static 和 final 的
接口可以嵌套在類或其他接口中,像非嵌套接口一樣,它們具有 public 或包訪問權限的可見性。
14.內部類
當生成一個內部類的對象時,此對象與製造它的外圍對象(enclosing object)之間就有了一種聯繫,所以它能訪問其外圍對象的所有成員,而不需要任何特殊條件。此外,內部類還擁有其外圍類的所有元素的訪問權。
.new
// innerclasses/DotNew.java
// Creating an inner class directly using .new syntax
public class DotNew {
public class Inner {}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
}
要想直接創建內部類的對象,你不能按照你想象的方式,去引用外部類的名字 DotNew,而是必須使用外部類的對象來創建該內部類對象
在擁有外部類對象之前是不可能創建內部類對象的。這是因爲內部類對象會暗暗地連接到建它的外部類對象上。但是,如果你創建的是嵌套類(靜態內部類),那麼它就不需要對外部類對象的引用。
匿名內部類
// innerclasses/Parcel7.java
// Returning an instance of an anonymous inner class
public class Parcel7 {
public Contents contents() {
return new Contents() { // Insert class definition
private int i = 11;
@Override
public int value() { return i; }
}; // Semicolon required
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
}
“創建一個繼承自 Contents 的匿名類的對象。”通過 new 表達式返回的引用被自動向上轉型爲對 Contents 的引用。
如果你的基類需要一個有參數的構造器:
// innerclasses/Parcel8.java
// Calling the base-class constructor
public class Parcel8 {
public Wrapping wrapping(int x) {
// Base constructor call:
return new Wrapping(x) { // [1]
@Override
public int value() {
return super.value() * 47;
}
}; // [2]
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Wrapping w = p.wrapping(10);
}
}
- [1] 將合適的參數傳遞給基類的構造器。
- [2] 在匿名內部類末尾的分號,並不是用來標記此內部類結束的。實際上,它標記的是表達式的結束,只不過這個表達式正巧包含了匿名內部類罷了。因此,這與別的地方使用的分號是一致的。
如果定義一個匿名內部類,並且希望它使用一個在其外部定義的對象,那麼編譯器會要求其參數引用是 final 的
15.嵌套類
如果不需要內部類對象與其外圍類對象之間有聯繫,那麼可以將內部類聲明爲 static,這通常稱爲嵌套類。想要理解 static 應用於內部類時的含義,就必須記住,普通的內部類對象隱式地保存了一個引用,指向創建它的外圍類對象。然而,當內部類是 static 的時,就不是這樣了。嵌套類意味着:
- 要創建嵌套類的對象,並不需要其外圍類的對象。
- 不能從嵌套類的對象中訪問非靜態的外圍類對象。
嵌套類與普通的內部類還有一個區別。普通內部類的字段與方法,只能放在類的外部層次上,所以普通的內部類不能有 static 數據和 static 字段,也不能包含嵌套類。但是嵌套類可以包含所有這些東西
嵌套類就沒有這個特殊的 this 引用,這使得它類似於一個 static 方法。
接口內部的類
嵌套類可以作爲接口的一部分。你放到接口中的任何類都自動地是 public 和 static 的。因爲類是 static 的,只是將嵌套類置於接口的命名空間內,這並不違反接口的規則。你甚至可以在內部類中實現其外圍接口,就像下面這樣:
// innerclasses/ClassInInterface.java
// {java ClassInInterface$Test}
public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface {
@Override
public void howdy() {
System.out.println("Howdy!");
}
public static void main(String[] args) {
new Test().howdy();
}
}
}
如果你想要創建某些公共代碼,使得它們可以被某個接口的所有不同實現所共用,那麼使用接口內部的嵌套類會顯得很方便。
一個內部類被嵌套多少層並不重要——它能透明地訪問所有它所嵌入的外圍類的所有成員
16.集合
ArrayList 和 LinkedList 都是 List 的類型,從輸出中可以看出,它們都按插入順序保存元素。兩者之間的區別不僅在於執行某些類型的操作時的性能,而且 LinkedList 包含的操作多於 ArrayList 。本章後面將對這些內容進行更全面的探討。
HashSet , TreeSet 和 LinkedHashSet 是 Set 的類型。從輸出中可以看到, Set 僅保存每個相同項中的一個,並且不同的 Set 實現存儲元素的方式也不同。 HashSet 使用相當複雜的方法存儲元素,這種技術是檢索元素的最快方法,因此,存儲順序看上去沒有什麼意義(通常只關心某事物是否是 Set 的成員,而存儲順序並不重要)。如果存儲順序很重要,則可以使用 TreeSet ,它將按比較結果的升序保存對象)或 LinkedHashSet ,它按照被添加的先後順序保存對象。
Map (也稱爲關聯數組)使用鍵來查找對象,就像一個簡單的數據庫。所關聯的對象稱爲值。 對於每個鍵, Map 只存儲一次。
鍵和值保存在 HashMap 中的順序不是插入順序,因爲 HashMap 實現使用了非常快速的算法來控制順序。 TreeMap 通過比較結果的升序來保存鍵, LinkedHashMap 在保持 HashMap 查找速度的同時按鍵的插入順序保存鍵。
List:
可以使用 contains()
方法確定對象是否在列表中。如果要刪除一個對象,可以將該對象的引用傳遞給 remove()
方法。同樣,如果有一個對象的引用,可以使用 indexOf()
在 List 中找到該對象所在位置的下標號
當確定元素是否是屬於某個 List ,尋找某個元素的索引,以及通過引用從 List 中刪除元素時,都會用到 equals()
方法(根類 Object 的一個方法)
對於 LinkedList ,在列表中間插入和刪除都是廉價操作(在本例中,除了對列表中間進行的真正的隨機訪問),但對於 ArrayList ,這可是代價高昂的操作。
迭代器
迭代器(也是一種設計模式)的概念實現了這種抽象。迭代器是一個對象,它在一個序列中移動並選擇該序列中的每個對象,而客戶端程序員不知道或不關心該序列的底層結構。另外,迭代器通常被稱爲輕量級對象(lightweight object):創建它的代價小。因此,經常可以看到一些對迭代器有些奇怪的約束。例如,Java 的 Iterator 只能單向移動。這個 Iterator 只能用來:
- 使用
iterator()
方法要求集合返回一個 Iterator。 Iterator 將準備好返回序列中的第一個元素。 - 使用
next()
方法獲得序列中的下一個元素。 - 使用
hasNext()
方法檢查序列中是否還有元素。 - 使用
remove()
方法將迭代器最近返回的那個元素刪除。
ListIterator 是一個更強大的 Iterator 子類型,它只能由各種 List 類生成。 Iterator 只能向前移動,而 ListIterator 可以雙向移動。它可以生成迭代器在列表中指向位置的後一個和前一個元素的索引,並且可以使用 set()
方法替換它訪問過的最近一個元素。可以通過調用 listIterator()
方法來生成指向 List 開頭處的 ListIterator ,還可以通過調用 listIterator(n)
創建一個一開始就指向列表索引號爲 n 的元素處的 ListIterator 。
17.Lambda表達式
Lambda 表達式是使用最小可能語法編寫的函數定義:
- Lambda 表達式產生函數,而不是類。 在 JVM(Java Virtual Machine,Java 虛擬機)上,一切都是一個類,因此在幕後執行各種操作使 Lambda 看起來像函數 —— 但作爲程序員,你可以高興地假裝它們“只是函數”。
- Lambda 語法儘可能少,這正是爲了使 Lambda 易於編寫和使用。
Lambda 表達式的基本語法是:
-
參數。
-
接着
->
,可視爲“產出”。 -
->
之後的內容都是方法體。-
[1] 當只用一個參數,可以不需要括號
()
。 然而,這是一個特例。 -
[2] 正常情況使用括號
()
包裹參數。 爲了保持一致性,也可以使用括號()
包裹單個參數,雖然這種情況並不常見。 -
[3] 如果沒有參數,則必須使用括號
()
表示空參數列表。 -
[4] 對於多個參數,將參數列表放在括號
()
中。到目前爲止,所有 Lambda 表達式方法體都是單行。 該表達式的結果自動成爲 Lambda 表達式的返回值,在此處使用 return 關鍵字是非法的。 這是 Lambda 表達式縮寫用於描述功能的語法的另一種方式。
- [5] 如果在 Lambda 表達式中確實需要多行,則必須將這些行放在花括號中。 在這種情況下,就需要使用 return。
-
Lambda 表達式通常比匿名內部類產生更易讀的代碼
// functional/LambdaExpressions.java
interface Description {
String brief();
}
interface Body {
String detailed(String head);
}
interface Multi {
String twoArg(String head, Double d);
}
public class LambdaExpressions {
static Body bod = h -> h + " No Parens!"; // [1]
static Body bod2 = (h) -> h + " More details"; // [2]
static Description desc = () -> "Short info"; // [3]
static Multi mult = (h, n) -> h + n; // [4]
static Description moreLines = () -> { // [5]
System.out.println("moreLines()");
return "from moreLines()";
};
public static void main(String[] args) {
System.out.println(bod.detailed("Oh!"));
System.out.println(bod2.detailed("Hi!"));
System.out.println(desc.brief());
System.out.println(mult.twoArg("Pi! ", 3.14159));
System.out.println(moreLines.brief());
}
}
interface IntCall {
int call(int arg);
}
// functional/RecursiveFactorial.java
遞歸
public class RecursiveFactorial {
static IntCall fact;
public static void main(String[] args) {
fact = n -> n == 0 ? 1 : n * fact.call(n - 1);
for(int i = 0; i <= 10; i++)
System.out.println(fact.call(i));
}
}
方法引用:
方法引用組成:類名或對象名,後面跟 ::
,然後跟方法名稱。
18.異常
多重捕獲
通過 Java 7 的多重捕獲機制,你可以使用“或”將不同類型的異常組合起來,只需要一行 catch 語句:
// exceptions/MultiCatch.java
public class MultiCatch {
void x() throws Except1, Except2, Except3, Except4 {}
void process() {}
void f() {
try {
x();
} catch(Except1 | Except2 | Except3 | Except4 e) {
process();
}
}
}複製ErrorOK!
或者以其他的組合方式:
// exceptions/MultiCatch2.java
public class MultiCatch2 {
void x() throws Except1, Except2, Except3, Except4 {}
void process1() {}
void process2() {}
void f() {
try {
x();
} catch(Except1 | Except2 e) {
process1();
} catch(Except3 | Except4 e) {
process2();
}
}
}
在return中使用finally
無論在try中的哪裏使用return,finally代碼塊都會執行。