函數(shù)派發(fā)方式
能夠在編譯期確定執(zhí)行方法的方式叫做靜態(tài)分派 Static dispatch
蒜危,無法在編譯期確定蹦渣,只能在運行時去確定執(zhí)行方法的分派方式叫做動態(tài)分派 Dynamic dispatch
内狸。
靜態(tài)分派(Static dispatch)
Static dispatch
更快夷恍,CPU
直接拿到函數(shù)地址并進行調(diào)用蚓峦,而且靜態(tài)分派可以進行內(nèi)聯(lián)等進一步的優(yōu)化卵迂,使得執(zhí)行更快速察蹲,性能更高请垛。
使用 Static dispatch
代替 Dynamic dispatch
提升性能
我們知道Static dispatch
快于Dynamic dispatch
,如何在開發(fā)中去盡可能使用Static dispatch
洽议。
-
inheritance constraints
繼承約束我們可以使用
final
關(guān)鍵字去修飾Class
宗收,以此生成的Final class
,使用Static dispatch
亚兄。 -
access control
訪問控制private
關(guān)鍵字修飾混稽,使得方法或?qū)傩灾粚Ξ?dāng)前類可見。編譯器會對方法進行Static dispatch
。
編譯器可以通過 whole module optimization
檢查繼承關(guān)系匈勋,對某些沒有標(biāo)記 final
的類通過計算礼旅,如果能在編譯期確定執(zhí)行的方法,則使用 Static dispatch
颓影。 Struct
默認使用 Static dispatch
各淀。
Swift 提供了更靈活的Struct
,用以在內(nèi)存诡挂、引用計數(shù)碎浇、方法分派等角度去進行性能的優(yōu)化,在正確的時機選擇正確的數(shù)據(jù)結(jié)構(gòu)璃俗,可以使我們的代碼性能更快更安全奴璃。
如果你正在面試,或者正準(zhǔn)備跳槽城豁,不妨看看我精心總結(jié)的面試資料:https://gitee.com/Mcci7/i-oser 來獲取一份詳細的大廠面試資料 為你的跳槽加薪多一份保障
你可能會問 Struct
如何實現(xiàn)多態(tài)呢?
答案是 protocol oriented programming
苟穆。
以上分析了影響性能的幾個標(biāo)準(zhǔn),那么不同的算法機制Class
唱星,Protocol Types
和 Generic code
雳旅,它們在這三方面的表現(xiàn)如何,Protocol Type
和 Generic code
分別是怎么實現(xiàn)的呢间聊?我們帶著這個問題看下去攒盈。
Protocol Type
這里我們會討論Protocol Type如何存儲和拷貝變量,以及方法分派是如何實現(xiàn)的哎榴。不通過繼承或者引用語義的多態(tài):
protocol Drawable {
func draw()
}
struct Point :Drawable {
var x, y:Double
func draw() { … }
}
struct Line :Drawable {
var x1, y1, x2, y2:Double
func draw() {
…
}
}
var drawables:[Drawable] //遵守了Drawable協(xié)議的類型集合型豁,可能是point或者line
for d in drawables {
d.draw()
}
復(fù)制代碼
因為 Point 和 Line 的尺寸不同,數(shù)組存儲數(shù)據(jù)實現(xiàn)一致性存儲尚蝌,使用了Existential Container
迎变。查找正確的執(zhí)行方法則使用了 Protoloc Witness Table
。
以上通過 Protocol Type
實現(xiàn)多態(tài)飘言,幾個類之間沒有繼承關(guān)系衣形,故不能按照慣例借助 V-Table
實現(xiàn)動態(tài)分派。但是對于 swift 來說姿鸿,class
類和 struct
結(jié)構(gòu)體的實現(xiàn)是不同的,而屬于結(jié)構(gòu)體的協(xié)議Protocol
谆吴,可以擁有屬性和實現(xiàn)方法,管理Protocol Type
方法分派的表就叫做Protocol Witness Table
般妙。
Protocol Witness Table
和 V-table
一樣,Protocol Witness Table
(簡稱 PWT
)內(nèi)存儲的是方法數(shù)組,里面包含了方法實現(xiàn)的指針地址,一般我們調(diào)用方法時,是通過獲取對象的內(nèi)存地址和方法的位移offset
去查找的.
Protocol Witness Table
是用于管理 Protocol Type
的方法調(diào)用的,在我們接觸 swift 性能優(yōu)化時,聽到另一個概念叫做 Value Witness Table
(簡稱 VWT
),這個又是做什么的呢?
Value Witness Table
[圖片上傳失敗...(image-1b0d39-1638867090452)]
用于管理任意值的初始化、拷貝相速、銷毀碟渺。即對 Protocol Type
的生命周期進行專項管理
對于每一個類型(Int或者自定義),都在metadata中存儲了一個VWT(用來管理當(dāng)前類型的值)
Value Witness Table
和 Protocol Witness Table
通過分工,去管理 Protocol Type
實例的內(nèi)存管理(初始化苫拍,拷貝芜繁,銷毀)和方法調(diào)用。
動態(tài)分派
但是對于多態(tài)的情況绒极,我們不能在編譯期確定最終的類型骏令,這里就用到了 Dynamic dispatch
動態(tài)分派。動態(tài)分派的實現(xiàn)是垄提,每種類型都會創(chuàng)建一張表榔袋,表內(nèi)是一個包含了方法指針的數(shù)組。動態(tài)分派更靈活铡俐,但是因為有查表和跳轉(zhuǎn)的操作凰兑,并且因為很多特點對于編譯器來說并不明確,所以相當(dāng)于 block
了編譯器的一些后期優(yōu)化审丘。所以速度慢于 Static dispatch
吏够。
函數(shù)表派發(fā)(Table dispatch)
編譯型語言中最常見的派發(fā)方式,既保證了動態(tài)性也兼顧了執(zhí)行效率滩报。
函數(shù)所在的類會維護一個“函數(shù)表”(虛函數(shù)表)锅知,存取了每個函數(shù)實現(xiàn)的指針。
每個類的 vtable 在編譯時就會被構(gòu)建脓钾,所以與靜態(tài)派發(fā)相比多出了兩個讀取的工作:
- 讀取該類的 vtable
- 讀取函數(shù)的指針
優(yōu)點:
- 查表是一種簡單售睹,易實現(xiàn),而且性能可預(yù)知的方式惭笑。
- 理論上說侣姆,函數(shù)表派發(fā)也是一種高效的方式。
缺點:
- 與靜態(tài)派發(fā)相比沉噩,從字節(jié)碼角度來看捺宗,多了兩次讀和一次跳轉(zhuǎn)。
- 與靜態(tài)派發(fā)相比川蒙,編譯器對某些含有副作用的函數(shù)無法優(yōu)化笔咽。
- Swift 類擴展里面的函數(shù)無法動態(tài)加入該類的函數(shù)表中,只能使用靜態(tài)派發(fā)的方式旋恼。
舉個例子(只是一個示例):
class A {
func method1() {}
}
class B: A {
func method2() {}
}
class C: B {
override func method2() {}
func method3() {}
}
復(fù)制代碼
復(fù)制代碼
offset | 0xA00 | A | 0xB00 | B | 0xC00 | C |
---|---|---|---|---|---|---|
0 | 0x121 | A.method1 | 0x121 | A.method1 | 0x121 | A.method1 |
1 | 0x222 | B.method2 | 0x322 | C.method2 | ||
2 | 0x323 | C.method3 |
let obj = C()
obj.method2()
復(fù)制代碼
當(dāng)method2
被調(diào)用時泛鸟,會經(jīng)歷下面的幾個過程:
- 讀取對象
0xC00
的函數(shù)表 - 讀取函數(shù)指針的索引,
method2
的地址為0x322
- 跳轉(zhuǎn)執(zhí)行
0x322
消息派發(fā)(Message dispatch)
熟悉 OC
的人都知道康聂,OC
采用了運行時機制使用 obj_msgSend
發(fā)送消息贰健,runtime
非常的靈活,我們不僅可以對方法調(diào)用采用 swizzling
恬汁,對于對象也可以通過 isa-swizzling
來擴展功能伶椿,應(yīng)用場景有我們常用的 hook
和大家熟知的 KVO
。
大家在使用 Swift 進行開發(fā)時都會問,Swift 是否可以使用OC的運行時和消息轉(zhuǎn)發(fā)機制呢脊另?答案是可以导狡。
Swift 可以通過關(guān)鍵字 dynamic
對方法進行標(biāo)記,這樣就會告訴編譯器偎痛,此方法使用的是 OC
的運行時機制旱捧。
id returnValue = [obj messageName:param];
// 底層代碼
id returnValue = objc_msgSend(obj, @selector(messageName:), param);
復(fù)制代碼
復(fù)制代碼
優(yōu)點:
- 動態(tài)性高
- Method Swizzling
- isa Swizzling
- ...
缺點:
- 執(zhí)行效率是三種派發(fā)方式中最低的
所幸的是 objc_msgSend 會將匹配的結(jié)果緩存到一個映射表中,每個類都有這樣一塊緩存踩麦。若是之后發(fā)送相同的消息枚赡,執(zhí)行速率會很快。
總結(jié)來說靖榕,Swift 通過 dynamic
關(guān)鍵字的擴展后标锄,一共包含三種方法分派方式:Static dispatch
,Table dispatch
和 Message dispatch
茁计。下表為不同的數(shù)據(jù)結(jié)構(gòu)在不同情況下采取的分派方式:
類型 | 靜態(tài)派發(fā) | 函數(shù)表派發(fā) | 消息派發(fā) |
---|---|---|---|
值類型 | 所有方法 | / | / |
協(xié)議 | extension | 主體創(chuàng)建 | / |
類 | extension/final/static | 主體創(chuàng)建 | @objc + dynamic |
NSObject子類 | extension/final/static | 主體創(chuàng)建 | @objc + dynamic |
如果在開發(fā)過程中料皇,錯誤的混合了這幾種分派方式,就可能出現(xiàn) Bug
星压,以下我們對這些 Bug
進行分析:
此情況是在子類的 extension
中重載父類方法時践剂,出現(xiàn)和預(yù)期不同的行為。
class Base:NSObject {
var directProperty:String { return "This is Base" }
var indirectProperty:String { return directProperty }
}
class Sub:Base { }
extension Sub {
override var directProperty:String { return "This is Sub" }
}
復(fù)制代碼
執(zhí)行以下代碼娜膘,直接調(diào)用沒有問題:
Base().directProperty // “This is Base”
Sub().directProperty // “This is Sub”
復(fù)制代碼
間接調(diào)用結(jié)果和預(yù)期不同:
Base().indirectProperty // “This is Base”
Sub().indirectProperty // expected "this is Sub"逊脯,but is “This is Base” <- Unexpected!
復(fù)制代碼
在 Base.directProperty
前添加 dynamic
關(guān)鍵字就可以獲得 "this is Sub" 的結(jié)果。Swift 在 extension 文檔 中說明竣贪,不能在 extension
中重載已經(jīng)存在的方法军洼。
“Extensions can add new functionality to a type, but they cannot override existing functionality.”
會出現(xiàn)報錯:Cannot override a non-dynamic class declaration from an extension
。
[圖片上傳失敗...(image-c05dcd-1638867090451)]
出現(xiàn)這個問題的原因是演怎,NSObject
的 extension
是使用的 Message dispatch
匕争,而 Initial Declaration
使用的是 Table dispath
(查看上圖)。extension
重載的方法添加在了 Message dispatch
內(nèi)爷耀,沒有修改虛函數(shù)表甘桑,虛函數(shù)表內(nèi)還是父類的方法,故會執(zhí)行父類方法歹叮。想在 extension
重載方法跑杭,需要標(biāo)明dynamic
來使用 Message dispatch
。
協(xié)議的擴展內(nèi)實現(xiàn)的方法咆耿,無法被遵守類的子類重載:
protocol Greetable {
func sayHi()
}
extension Greetable {
func sayHi() {
print("Hello")
}
}
func greetings(greeter:Greetable) {
greeter.sayHi()
}
復(fù)制代碼
現(xiàn)在定義一個遵守了協(xié)議的類 Person
德谅。遵守協(xié)議類的子類 LoudPerson
:
class Person:Greetable {
}
class LoudPerson:Person {
func sayHi() {
print("sub")
}
}
復(fù)制代碼
執(zhí)行下面代碼結(jié)果為:
var sub:LoudPerson = LoudPerson()
sub.sayHi() //sub
復(fù)制代碼
不符合預(yù)期的代碼:
var sub:Person = LoudPerson()
sub.sayHi() //HellO <-使用了protocol的默認實現(xiàn)
復(fù)制代碼
注意,在子類。LoudPerson
中沒有出現(xiàn)override
關(guān)鍵字萨螺≌觯可以理解為LoudPerson
并沒有成功注冊Greetable
在Witness table
的方法宅荤。所以對于聲明為Person
實際為LoudPerson
的實例,會在編譯器通過Person
去查找浸策,Person
沒有實現(xiàn)協(xié)議方法,則不產(chǎn)生Witness table
惹盼,sayHi
方法是直接調(diào)用的庸汗。解決辦法是在base類
內(nèi)實現(xiàn)協(xié)議方法,無需實現(xiàn)也要提供默認方法手报◎遣眨或者將基類標(biāo)記為final
來避免繼承
這里的確沒有編譯驗證掩蛤,感謝 MemoryReload 的實踐驗證枉昏,自己編譯了源碼之后糾正一下,這里的直接調(diào)用的確不是上述的原因:
- 在子類
LoudPerson
中實現(xiàn)了sayHi
方法揍鸟,所以會直接調(diào)用LoudPerson
的sayHi
方法兄裂。 - 對于聲明為
Person
實際為LoudPerson
的實例,會在編譯器通過Person
去查找阳藻,聲明在協(xié)議中的函數(shù)是使用函數(shù)表 vtable 派發(fā)的晰奖,從下面編譯的 SIL 代碼中也可以看到,Person
的函數(shù)表 vtable 中沒有sayHi
方法腥泥,實際上匾南,Person
類的sayHi
方法只是在接口擴展中進行了定義,沒有最終的類型中實現(xiàn)蛔外。在使用時蛆楞,因為Person
類只是一個符合Greetable
接口的實例,編譯器對sayHi
唯一能確定的只是在接口擴展中有一個默認實現(xiàn)豹爹,因此在調(diào)用時尊流,無法確定安全,也就不會去進行動態(tài)派發(fā)崖技,而是轉(zhuǎn)而編譯期間就確定的默認實現(xiàn)逻住。
import Foundation
protocol Greetable {
func sayHi()
}
extension Greetable {
func sayHi()
}
func greetings(greeter: Greetable)
class Person : Greetable {
@objc deinit
init()
}
@_inheritsConvenienceInitializers class LoudPerson : Person {
func sayHi()
override init()
@objc deinit
}
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
...
} // end sil function 'main'
// Greetable.sayHi()
sil hidden @$s16FunctionDispatch9GreetablePAAE5sayHiyyF : $@convention(method) <Self where Self : Greetable> (@in_guaranteed Self) -> () {
// %0 "self" // user: %1
bb0(%0 : $*Self):
debug_value_addr %0 : $*Self, let, name "self", argno 1 // id: %1
%2 = integer_literal $Builtin.Word, 1 // user: %4
// function_ref _allocateUninitializedArray<A>(_:)
%3 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %4
%4 = apply %3<Any>(%2) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %6, %5
%5 = tuple_extract %4 : $(Array<Any>, Builtin.RawPointer), 0 // user: %17
%6 = tuple_extract %4 : $(Array<Any>, Builtin.RawPointer), 1 // user: %7
%7 = pointer_to_address %6 : $Builtin.RawPointer to [strict] $*Any // user: %14
%8 = string_literal utf8 "Hello" // user: %13
%9 = integer_literal $Builtin.Word, 5 // user: %13
%10 = integer_literal $Builtin.Int1, -1 // user: %13
%11 = metatype $@thin String.Type // user: %13
...
} // end sil function '$s16FunctionDispatch9GreetablePAAE5sayHiyyF'
// protocol witness for Greetable.sayHi() in conformance Person
sil private [transparent] [thunk] @$s16FunctionDispatch6PersonCAA9GreetableA2aDP5sayHiyyFTW : $@convention(witness_method: Greetable) <τ_0_0 where τ_0_0 : Person> (@in_guaranteed τ_0_0) -> () {
// %0 // user: %2
bb0(%0 : $*τ_0_0):
// function_ref Greetable.sayHi()
%1 = function_ref @$s16FunctionDispatch9GreetablePAAE5sayHiyyF : $@convention(method) <τ_0_0 where τ_0_0 : Greetable> (@in_guaranteed τ_0_0) -> () // user: %2
%2 = apply %1<τ_0_0>(%0) : $@convention(method) <τ_0_0 where τ_0_0 : Greetable> (@in_guaranteed τ_0_0) -> ()
%3 = tuple () // user: %4
return %3 : $() // id: %4
} // end sil function '$s16FunctionDispatch6PersonCAA9GreetableA2aDP5sayHiyyFTW'
// LoudPerson.sayHi()
sil hidden @$s16FunctionDispatch10LoudPersonC5sayHiyyF : $@convention(method) (@guaranteed LoudPerson) -> () {
sil_vtable Person {
#Person.init!allocator: (Person.Type) -> () -> Person : @$s16FunctionDispatch6PersonCACycfC // Person.__allocating_init()
#Person.deinit!deallocator: @$s16FunctionDispatch6PersonCfD // Person.__deallocating_deinit
}
sil_vtable LoudPerson {
#Person.init!allocator: (Person.Type) -> () -> Person : @$s16FunctionDispatch10LoudPersonCACycfC [override] // LoudPerson.__allocating_init()
#LoudPerson.sayHi: (LoudPerson) -> () -> () : @$s16FunctionDispatch10LoudPersonC5sayHiyyF // LoudPerson.sayHi()
#LoudPerson.deinit!deallocator: @$s16FunctionDispatch10LoudPersonCfD // LoudPerson.__deallocating_deinit
}
sil_witness_table hidden Person: Greetable module FunctionDispatch {
method #Greetable.sayHi: <Self where Self : Greetable> (Self) -> () -> () : @$s16FunctionDispatch6PersonCAA9GreetableA2aDP5sayHiyyFTW // protocol witness for Greetable.sayHi() in conformance Person
}
復(fù)制代碼
進一步通過示例去理解:
// Defined protocol瞎访。
protocol A {
func a() -> Int
}
extension A {
func a() -> Int {
return 0
}
}
// A class doesn't have implement of the function。
class B:A {}
class C:B {
func a() -> Int {
return 1
}
}
// A class has implement of the function吁恍。
class D:A {
func a() -> Int {
return 1
}
}
class E:D {
override func a() -> Int {
return 2
}
}
// Failure cases播演。
B().a() // 0
C().a() // 1
(C() as A).a() // 0 # We thought return 1伴奥。
// Success cases拾徙。
D().a() // 1
(D() as A).a() // 1
E().a() // 2
(E() as A).a() // 2
復(fù)制代碼
如果對上述代碼的執(zhí)行結(jié)果理解的不到位的話,還可以借助喵神 PROTOCOL EXTENSION 里面的例子理解一下:
現(xiàn)在我們可以對一個已有的 protocol 進行擴展暂衡,而擴展中實現(xiàn)的方法將作為實現(xiàn)擴展的類型的默認實現(xiàn)崖瞭。也就是說,假設(shè)我們有下面的 protocol 聲明唧领,以及一個對該接口的擴展:
protocol MyProtocol {
func method()
}
extension MyProtocol {
func method() {
print("Called")
}
}
復(fù)制代碼
在具體的實現(xiàn)這個接口的類型中疹吃,即使我們什么都不寫西雀,也可以編譯通過。進行調(diào)用的話腔呜,會直接使用 extension 中的實現(xiàn):
struct MyStruct: MyProtocol {
}
MyStruct().method()
// 輸出:
// Called in extension
復(fù)制代碼
當(dāng)然再悼,如果我們需要在類型中進行其他實現(xiàn)的話冲九,可以像以前那樣在具體類型中添加這個方法:
struct MyStruct: MyProtocol {
func method() {
print("Called in struct")
}
}
MyStruct().method()
// 輸出:
// Called in struct
復(fù)制代碼
也就是說,protocol extension 為 protocol 中定義的方法提供了一個默認的實現(xiàn)丑孩。有了這個特性以后温学,之前被放在全局環(huán)境中的接受 CollectionType
的 map
方法甚疟,就可以被移動到 CollectionType
的接口擴展中去了:
extension CollectionType {
public func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
//...
}
復(fù)制代碼
在日常開發(fā)中,另一個可以用到 protocol extension 的地方是 optional 的接口方法揽祥。通過提供 protocol 的 extension盔然,我們?yōu)?protocol 提供了默認實現(xiàn)涂召,這相當(dāng)于變相將 protocol 中的方法設(shè)定為了 optional。關(guān)于這個炎码,我們在可選接口和接口擴展一節(jié)中已經(jīng)講述過潦闲,就不再重復(fù)了迫皱。
對于 protocol extension 來說,有一種會非常讓人迷惑的情況和敬,就是在接口的擴展中實現(xiàn)了接口里沒有定義的方法時的情況戏阅。舉個例子饲握,比如我們定義了這樣的一個接口和它的一個擴展:
protocol A1 {
func method1() -> String
}
struct B1: A1 {
func method1() -> String {
return "hello"
}
}
復(fù)制代碼
在使用的時候蚕键,無論我們將實例的類型為 A1 還是 B1锣光,因為實現(xiàn)只有一個铝耻,所以沒有任何疑問瓢捉,調(diào)用方法時的輸出都是 “hello”:
let b1 = B1() // b1 is B1
b1.method1()
// hello
let a1: A1 = B1()
// a1 is A1
a1.method1()
// hello
復(fù)制代碼
但是如果在接口里只定義了一個方法,而在接口擴展中實現(xiàn)了額外的方法的話搂漠,事情就變得有趣起來了某弦“凶常考慮下面這組接口和它的擴展:
protocol A2 {
func method1() -> String
}
extension A2 {
func method1() -> String {
return "hi"
}
func method2() -> String {
return "hi"
}
}
復(fù)制代碼
擴展中除了實現(xiàn)接口定義的 method1
之外,還定義了一個接口中不存在的方法 method2
拣度。我們嘗試來實現(xiàn)這個接口:
struct B2: A2 {
func method1() -> String {
return "hello"
}
func method2() -> String {
return "hello"
}
}
復(fù)制代碼
B2
中實現(xiàn)了 method1
和 method2
蜡娶。接下來映穗,我們嘗試初始化一個 B2
對象,然后對這兩個方法進行調(diào)用:
let b2 = B2()
b2.method1() // hello
b2.method2() // hello
復(fù)制代碼
結(jié)果在我們的意料之中宿接,雖然在 protocol extension 中已經(jīng)實現(xiàn)了這兩個方法睦霎,但是它們只是默認的實現(xiàn)走诞,我們在具體實現(xiàn)接口的類型中可以對默認實現(xiàn)進行覆蓋蚣旱,這非常合理戴陡。但是如果我們稍作改變沟涨,在上面的代碼后面繼續(xù)添加:
let a2 = b2 as A2
a2.method1() // hello
a2.method2() // hi
復(fù)制代碼
a2
和 b2
是同一個對象裹赴,只不過我們通過 as
告訴編譯器我們在這里需要的類型是 A2
。但是這時候在這個同樣的對象上調(diào)用同樣的方法調(diào)用卻得到了不同的結(jié)果延都,發(fā)生了什么晰房?
我們可以看到酵颁,對 a2
調(diào)用 method2
實際上是接口擴展中的方法被調(diào)用了月帝,而不是 a2
實例中的方法被調(diào)用嚷辅。我們不妨這樣來理解:對于 method1
,因為它在 protocol
中被定義了扁位,因此對于一個被聲明為遵守接口的類型的實例 (也就是對于 a2
) 來說域仇,可以確定實例必然實現(xiàn)了 method1
寺擂,我們可以放心大膽地用動態(tài)派發(fā)的方式使用最終的實現(xiàn) (不論它是在類型中的具體實現(xiàn)怔软,還是在接口擴展中的默認實現(xiàn));但是對于 method2
來說括改,我們只是在接口擴展中進行了定義家坎,沒有任何規(guī)定說它必須在最終的類型中被實現(xiàn)。在使用時憔涉,因為 a2
只是一個符合 A2
接口的實例析苫,編譯器對 method2
唯一能確定的只是在接口擴展中有一個默認實現(xiàn)衩侥,因此在調(diào)用時,無法確定安全跪但,也就不會去進行動態(tài)派發(fā)峦萎,而是轉(zhuǎn)而編譯期間就確定的默認實現(xiàn)爱榔。
也許在這個例子中你會覺得無所謂,因為實際中估計并不會有人將一個已知類型實例轉(zhuǎn)回接口類型筛欢。但是要考慮到如果你的一些泛型 API 中有類似的直接拿到一個接口類型的結(jié)果的時候唇聘,調(diào)用它的擴展方法時就需要特別小心了:一般來說迟郎,如果有這樣的需求的話宪肖,我們可以考慮將這個接口類型再轉(zhuǎn)回實際的類型,然后進行調(diào)用夫凸。
整理一下相關(guān)的規(guī)則的話:
-
如果類型推斷得到的是實際的類型
- 那么類型中的實現(xiàn)將被調(diào)用阱持;如果類型中沒有實現(xiàn)的話,那么接口擴展中的默認實現(xiàn)將被使用
-
如果類型推斷得到的是接口蒜绽,而不是實際類型
并且方法在接口中進行了定義桶现,那么類型中的實現(xiàn)將被調(diào)用骡和;如果類型中沒有實現(xiàn),那么接口擴展中的默認實現(xiàn)被使用
否則 (也就是方法沒有在接口中定義)钮科,擴展中的默認實現(xiàn)將被調(diào)用
參考: