Java作為面向對象的典型語言,相比於C++而言,對類的繼承和派生有着更簡潔的設計(比如單根繼承)。
在繼承派生的過程中,是符合Liskov替換原則(LSP)的。LSP總結起來,就一句話:
所有引用基類(父類)的地方必須能夠透明地使用其子類的對象。
LSP包含四層含義:
① 子類完全擁有父類的方法,且具體子類必須實現父類的抽象方法;
② 子類中可以增加自己的方法;
③ 當子類覆蓋或實現父類的方法時,方法的形參要比父類方法的更加寬松;
④ 當子類覆蓋或實現父類的方法時,方法的返回值要比父類方法的更加嚴格。
針對LSP四層含義的③④條,就引出了協變(Covariance)和逆變(Contravariance)的概念:
協變,簡言之,就是父類型到子類型,變得越來越具體,在Java中體現在返回值類型不變或更加具體(異常類型也是如此)等。
逆變,簡言之,就是父類型到子類型,變得越來越具體,但是方法的形參卻變得更加抽象或不變(注意:這里在Java中本質為方法重載,而不是覆蓋,當添加@Override標簽將會報錯!)
於是,針對上述的逆變,概念可以理解,但是Java中所謂的“方法形參變得更加寬泛”,實質是方法重載,似乎也就不能嚴格地稱為繼承關系下的“逆變”,思考之,似乎Java的繼承派生過程中,幾乎所有的操作,都是協變的,那么是不是就說明Java中並不存在逆變呢???
錯!Java中是存在逆變的!
這里就引出了Java中的泛型,這里舉個例子:
在Java中,Number類是Integer類的父類(super),如果某個方法的簽名是void method(List<Number> listNumber),那么按照協變的思想,是不是意味着為這個方法傳入List<Integer>類型參數也是可以的呢?
當然不... 很多博客在此都說“Java對於這樣的泛型是不支持協變的”,但我認為,事實是List<Integer>的實參,依舊是一個List類型的持有對象,因此對於List<Number>這個持有對象來說,二者持有的對象存在繼承派生的關系,但二者本身並不存在繼承派生的關系,因而也就無從談及協變(實質,這里二者的關系是“不變”,“不變”是針對於協變與逆變概念而言的)。
舉個例子來說明這個簡單的問題,一個父親和他的兒子都分別有一輛車,他們的車款型相同(當然,也可能不同,但總歸是車,即持有對象,這里為了針對上述二者持有對象均為List故意言之),盡管車上的父親和兒子存在着“繼承派生”的關系,但是這兩輛車並不存在繼承關系,所以二者之間並沒有“協變”的概念。
那么,總不能對於這樣的method,要為每種持有對象持有的對象類型分別重載實現method吧...於是,Java就提供了泛型的通配符(注意,這里才談到泛型),為了解決上述的method問題,可以這樣聲明method: void method(List<? extends Number> listNumber)。這樣,這里的形參就必須是一個持有對象,它持有的對象類型,必須是Number類或者是繼承自Number類的更具體的子類(如Integer類,Double類),此時,可以說這個方法依舊實現了“協變”,那么Java中的逆變是體現在哪兒的呢?
這里就引出了通配符后另一個關鍵字,super。
這樣聲明的方法:void method(List<? super Integer> listInteger),說明該持有對象持有的對象類型,必須是Integer或Integer的父類(超類super),於是,此時向方法中傳遞持有Number類的持有對象也是可以的,甚至,可以傳遞一個持有Object類型的持有對象。此處便是使得參數類型變得更加寬泛,因此此處體現的是“逆變”。
這也很好記:
? extends 對應 協變
? super 對應 逆變
(? 即為Java泛型的通配符)
綜上,Java是符合LSP的一門語言,對“協變”“逆變”的支持也是有具體實現以及道理的。理解好這些概念,可以讓編程中遇到的知識概念更加系統化,理解記憶也更高效。
至於前幾天,有同學在群里討論,Java中如果子類覆蓋了父類的方法,是否就不符合LSP了,如果說Java是嚴格按照LSP來設計的,那么這種情況是否就不能稱為覆蓋,而是重載...
當時被這個問題雷到了... 我理解的LSP應該是一種思想,是設計過程以及實現過程中開發者應該牢記並遵守的。如果按照LSP的總的規則,那么每個父類對象出現的地方,都可以用其具體子類對象來替換而不會發生錯誤。這個錯誤當然是保證語法編譯不會發生錯誤,而不是針對覆蓋方法導致的功能不同。所以...這個問題,實質上應該歸結於理解發生了偏頗...
本博客參考博客:
Java協變和逆變:https://blog.csdn.net/qiuchengjia/article/details/52910901
Java的逆變與協變:https://www.cnblogs.com/en-heng/p/5041124.html
from Steven Shen
編輯於2018.6.22
修改於2019.9.4
