寫在最前:本筆記全程參考《Java核心技術卷I》,添加了一些個人的思考和整理
介面
1、介面的概念
- 介面用來描述類應該做什么,而不指定他們應該怎么做,
- 介面不是類,而是對希望符合這個介面的類的一組需求,不可以實體化(new)一個介面
- 一個類可以實作零或多個介面
- 介面的所有方法都是public抽象方法,所以可以省略關鍵字
public abstract(例外:默認方法)
?
讓類實作介面通常需要下面兩個步驟:
- 使用關鍵字
implements實作介面:public class Employee implements Comparable {} - 重寫介面中的所有方法,此時要顯式宣告方法為public
?
宣告一個介面的關鍵字是:interface,它用來取代class的位置:public interface 介面名 {}
?
2、小插曲:Comparable介面和compareTo方法
2.1 概述
Comparable介面中的compareTo方法將回傳一個整數,如果兩個數不相等,則回傳一個正值或者有一個負值(這取決于兩個數哪個大,如果呼叫方小于引數,則回傳負值)如果相等,則回傳0.
需要注意:整數的范圍不宜過大,避免造成溢位(減去一個負數時會變成加法運算)如果確定引數是非負整數,或者兩者的絕對值都不超過(Integer,MAX_VALUE-1)/2,就不會出現問題,否則,可以呼叫Ingeter.compare方法
compareTo方法中的相減技巧不適用于浮點型別,因為兩者的差值在回傳int型別時四舍五入可能會變成0. 可以借助Double.compare方法實作
?
2.2 compareTo方法與equals方法
Comparable介面的檔案建議compareTo方法應該與equals方法兼容,也就是說,當x.equals(y)時,x.compareTo(y)就應該回傳0. JavaAPI中絕大多數都遵從了這個建議,只有BigDecimal例外:
x = new DigDecimal("1.0");
y = new DigDecimal("1.00");
x.equals(y); // false
x.compareTo(y); // true 之所以如此可能是由于沒有明確的方法來確定這兩個數字哪一個更大
?
2.3 compareTo方法的對稱性以及繼承中的問題
如果反轉compareTo方法的呼叫者和引數時,回傳的結果也應該翻轉(但是具體的值不一定),
和equals方法一樣,在繼承中會遇到對稱性問題:
如果Employee實作了Comparable<Employee>,而不是Comparable<manager>,那么其子類Manager要覆寫compareTo,就必須做好比較經理和員工的準備,而絕對不能僅僅將員工強轉為經理(因為這么做會違反“反對稱原則”:當翻轉呼叫者和引數時會出現強轉例外!)
public Manager extends Employee {
public int compareTo(Employee other) {
Manager otherManager = (Manager) other; // 不要這么寫
// ...
}
}
?
解決方式:
-
如果不同子類中比較有不同含義,就應該將屬于不同類的物件之間的比較視為非法,并在每個compareTo方法開始時進行
getClass檢測:if (this.getClass() != other.getClass()) throw new ClassCastException(); -
如果存在能夠比較子類物件的通用演算法,那么可以在超類中提供一個
compareTo方法,在一開始使用instanceof檢測子類,并將方法宣告為final,保證子類不串改語意,
?
3、介面的特點
-
介面不是類,不可以實體化一個介面,參考一個實作了這個介面的類物件,
-
可以使用
instanceof檢查一個物件是否實作了某個介面:if (obj instanceof Comparable) -
介面中不能包含實體欄位,但可以包含常量,且可以省略
public static final:public interface Powered extend Moveable { double SPEED_LIMIT = 95; // public static final的常量 } -
任何實作了介面的類都自動地繼承了常量,而不必采用類名(應該說是介面名)呼叫
-
一個類可以有多個介面,但是只能繼承于一個父類,多介面算是java實作多繼承的方式
-
一個類除了實作介面之外,還可以去繼承其他類
-
多個父介面存在同名的抽象方法不會影響程式
-
介面可以定義默認方法,
?
4、介面的作用
-
可以使專案分層,所有的層都面向介面開發,開發效率提高了(例子:回呼模式)
可以理解為燈泡和燈座,兩個不同的工廠做出來的不同的產品可以耦合
-
介面可以使代碼和代碼之間的耦合度降低,就像燈泡和燈座,變得“可插拔”
?
4.1 介面與回呼
回呼(callback)是一種程式設計模式,其可以指定某個特定事件發生時應該采取的操作,
例如Swing下的Timer類,可以完成定時任務,在構造定時器時,需要設定一個時間間隔,然后告訴定時器要定期做什么事,
在很多程式設計語言中,可以提供一個函式名來指定要做的事,而在java中采用的是面對物件的方法:我們可以傳入某個類的物件,然后讓定時器定期呼叫這個物件的某個方法,怎么確保傳入的物件肯定有這個方法呢?只要這個物件的類實作了某個介面,就可以保證它一定實作了這個介面的某個方法,
?
5、介面與抽象類
5.1 介面存在的意義
之所以要出現介面,而不是讓抽象類完成介面的作業,是因為Java的設計者選擇了不支持多重繼承,其主要原因是多重繼承會讓語言變得非常復雜,或會降低效率,而介面可以提供多重繼承的大多數好處,且可以避免多重繼承帶來的復雜性和低效性,
?
5.2 兩者的區別和聯系
5.2.1 區別
- 介面的設計目的,是對類的行為進行約束;而抽象類的設計目的,是代碼復用,
- 介面是對行為的抽象,而抽象類是對根源(類別)的抽象(
人應該用抽象類,而能游泳應該是介面), - 一個類只能繼承一個直接父類,這個父類可以是具體的類也可是抽象類;但是一個類可以實作多個介面,
- 介面里面只能對方法進行宣告,抽象類既可以對方法進行宣告也可以對方法進行實作,(但是介面可以實作默認方法)
?
5.2.2 聯系
- 抽象類中的抽象方法不能預設的public
- 介面其實是一個特殊的抽象類,特殊在介面是完全抽象的
- 一個非抽象的類實作介面,需要將介面中所有的的方法“實作/重寫/覆寫”
- 抽象級別(從高到低):介面>抽象類>實作類,
5.2.3 共同點
- 都是上層的抽象層,
- 都不能被實體化
- 都能包含抽象的方法,這些抽象的方法用于描述類具備的功能,但是不比提供具體的實作,
?
6、介面的靜態方法和私有方法
Java8開始允許即可中添加靜態方法,雖然這沒什么毛病,但是有違介面作為酬謝規范的初衷,
通常我們會將靜態方法放在工具類中,這些工具了是介面的伴隨類,(如標準庫中的Collection/Collections或Path/Paths,但是在Java11中,Path介面實作了與工具類等價的方法,如此看來,Paths伴隨類就不是必要的了)
在Java9中,介面可以定義private的方法,私有方法可以是靜態方法或實體方法,
?
7、介面的默認方法
可以為介面的方法提供一個默認實作,使用default修飾符標記:
public interface Comparable<T> {
default int compareTo(T other) {
// ...
}
}
對于Comparable介面來說,這可能沒什么用,因為每一個子類都會覆寫這個默認實作
實作介面會繼承默認方法
?
7.1 介面演化
默認方法的一個重要用途是“介面演化”,如果對一個介面添加一個新的非默認方法,那么可能會導致以前實作了該介面的類不能編譯(因為沒有重寫介面的新方法)
?
7.2 默認方法沖突
如果現在介面中定義一個默認方法,又在父類或另一個介面中定義同名且相同引數的方法,則會產生沖突,但是并不是錯誤,Java解決方法沖突的規則如下:
- 父類優先,如果父類提供了一個具體方法,那么同名且擁有相同引數的默認方法會被忽略,但不會報錯,
- 如果兩個介面的默認方法沖突,則子類必須覆寫這個方法來解決沖突,需要注意的是,即使兩個介面中只有一個介面實作了默認方法,另一個只定義了同名的抽象方法,子類也不會繼承前者的默認方法,必須重寫方法,
?
8、Comparator介面
我們無法修改java默認的比較規則(無法修改某個類的compareTo方法的實作)
有些時候我們想要自定義比較標準,而不采用默認的比較方式,此時就需要使用比較器(Comparator)
比較器是實作了Comparator介面的類的實體,
當我們想要按照長度比較字串時,可以定義如下的實作Comparator<String>的類:
class LengthComparator implements Comparator<String> {
public int compare(String first, String second) {
return first.length() - second.length();
}
}
利用lambda運算式可以更簡潔的使用Comparator介面
?
9、物件克隆與Cloneable介面
克隆是一種深復制(deep copy),克隆后的物件是一個新物件,新物件與原物件處于不同的記憶體區域,同時其成員欄位也會被深復制,這樣,新物件和原物件才沒有任何瓜葛,
?
9.1 標記介面
Cloneable是一個標記介面,它不包含任何方法,唯一的作用是用于在型別檢查中通過instanceof檢查
?
9.2 Object的clone()方法與protected
這一部分我寫了個博客:protected訪問權限解釋
9.2.1 Object的clone()方法:
Object類中的clone方法宣告為protected,原始碼如下:
protected native Object clone() throws CloneNotSupportedException;
java中的native關鍵字表示這個方法是個本地方法,而且native修飾的方法執行效率比非native修飾的高,
?
9.2.2 protected訪問權限:
protected訪問權限解釋:
?protected訪問權限允許欄位或方法被同一個包下的類訪問,
?
此外,對于子類:private限制欄位或方法只能在類內部使用,而protected則允許欄位或方法能在子類內部使用,但是不允許通過子類的實體在子類外部使用,
?
9.2.3 Object的clone()方法與protected的愛恨情仇
如下方:
Object obj1 = obj.clone();會出錯,因為此處不是在Object類內部使用,而是在CloneTest類;- 也正因此,
CloneTest cloneTest1 = (CloneTest) cloneTest.clone();沒有問題, - 而
MyObject myObj1 = myObj.clone();報錯也是同樣的道理,如果在MyObject中實作方法,在方法內部呼叫this.clone(),則編譯不會報錯,不過clone()方法要求類實作Cloneable介面,沒有介面會出現CloneNotSupportedException例外
class MyObject {
public void test() throws CloneNotSupportedException {
// 類內部中呼叫,編譯沒有問題,但是:
// 因為沒有實作Cloneable介面,運行時會出現CloneNotSupportedException例外
Object clone = this.clone();
}
}
public class CloneTest { // Object類的子類
public static void main(String[] args) throws CloneNotSupportedException {
Object obj = new Object();
// 錯誤:'clone()' has protected access in 'java.lang.Object'
Object obj1 = obj.clone();
MyObject myObj = new MyObject();
// 錯誤:'clone()' has protected access in 'java.lang.Object'
MyObject myObj1 = myObj.clone();
CloneTest cloneTest = new CloneTest();
// 沒有問題:
CloneTest cloneTest1 = (CloneTest) cloneTest.clone();
}
}
?
9.3 使類能夠呼叫clone()方法
在MyObject類中重寫clone()方法,覆寫掉繼承自父類的clone()方法,則編譯通過,不再有因為protected引起的不可見問題
class MyObject implements Cloneable{ // 定義一個空類,Object類的子類
public void test() throws CloneNotSupportedException {
// 子類自身中呼叫,沒有問題
Object clone = this.clone();
}
// 注意要將權限改成public,否則不在同個包下且非子類的類中是無法呼叫protected方法的
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
MyObject myObj = new MyObject();
// 沒有問題
myObj.test();
// 沒有問題
MyObject myObj1 = (MyObject) myObj.clone();
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/265906.html
標籤:java
