欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于為何說JAVA中要慎重使用繼承詳解

 更新時(shí)間:2018年08月16日 08:58:47   作者:Decouple  
Java繼承是面向?qū)ο蟮淖铒@著的一個(gè)特征,然而下面這篇文章主要給大家介紹了關(guān)于為何說JAVA中要慎重使用繼承的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

這篇文章的主題并非鼓勵(lì)不使用繼承,而是僅從使用繼承帶來的問題出發(fā),討論繼承機(jī)制不太好的地方,從而在使用時(shí)慎重選擇,避開可能遇到的坑。

JAVA中使用到繼承就會有兩個(gè)無法回避的缺點(diǎn):

  • 打破了封裝性,子類依賴于超類的實(shí)現(xiàn)細(xì)節(jié),和超類耦合。
  • 超類更新后可能會導(dǎo)致錯(cuò)誤。

繼承打破了封裝性

關(guān)于這一點(diǎn),下面是一個(gè)詳細(xì)的例子(來源于Effective Java第16條)

public class MyHashSet<E> extends HashSet<E> {
 private int addCount = 0;

 public int getAddCount() {
  return addCount;
 }

 @Override
 public boolean add(E e) {
  addCount++;
  return super.add(e);
 }

 @Override
 public boolean addAll(Collection<? extends E> c) {
  addCount += c.size();
  return super.addAll(c);
 }
}

這里自定義了一個(gè)HashSet,重寫了兩個(gè)方法,它和超類唯一的區(qū)別是加入了一個(gè)計(jì)數(shù)器,用來統(tǒng)計(jì)添加過多少個(gè)元素。

寫一個(gè)測試來測試這個(gè)新增的功能是否工作:

public class MyHashSetTest {
 private MyHashSet<Integer> myHashSet = new MyHashSet<Integer>();

 @Test
 public void test() {
  myHashSet.addAll(Arrays.asList(1,2,3));
  
  System.out.println(myHashSet.getAddCount());
 }
}

運(yùn)行后會發(fā)現(xiàn),加入了3個(gè)元素之后,計(jì)數(shù)器輸出的值是6。

進(jìn)入到超類中的addAll()方法就會發(fā)現(xiàn)出錯(cuò)的原因:它內(nèi)部調(diào)用的是add()方法。所以在這個(gè)測試?yán)?,進(jìn)入子類的addAll()方法時(shí),數(shù)器加3,然后調(diào)用超類的addAll(),超類的addAll()又會調(diào)用子類的add()三次,這時(shí)計(jì)數(shù)器又會再加三。

問題的根源

將這種情況抽象一下,可以發(fā)現(xiàn)出錯(cuò)是因?yàn)槌惖目筛采w的方法存在自用性(即超類里可覆蓋的方法調(diào)用了別的可覆蓋的方法),這時(shí)候如果子類覆蓋了其中的一些方法,就可能導(dǎo)致錯(cuò)誤。

 

比如上圖這種情況,F(xiàn)ather類里有可覆蓋的方法A和方法B,并且A調(diào)用了B。子類Son重寫了方法B,這時(shí)候如果子類調(diào)用繼承來的方法A,那么方法A調(diào)用的就不再是Father.B(),而是子類中的方法Son.B()。如果程序的正確性依賴于Father.B()中的一些操作,而Son.B()重寫了這些操作,那么就很可能導(dǎo)致錯(cuò)誤產(chǎn)生。

關(guān)鍵在于,子類的寫法很可能從表面上看來沒有問題,但是卻會出錯(cuò),這就迫使開發(fā)者去了解超類的實(shí)現(xiàn)細(xì)節(jié),從而打破了面向?qū)ο蟮姆庋b性,因?yàn)榉庋b性是要求隱藏實(shí)現(xiàn)細(xì)節(jié)的。更危險(xiǎn)的是,錯(cuò)誤不一定能輕易地被測出來,如果開發(fā)者不了解超類的實(shí)現(xiàn)細(xì)節(jié)就進(jìn)行重寫,那么可能就埋下了隱患。

超類更新時(shí)可能產(chǎn)生錯(cuò)誤

這一點(diǎn)比較好理解,主要有以下幾種可能:

1、超類更改了已有方法的簽名。會導(dǎo)致編譯錯(cuò)誤。

2、超類新增了方法:

  • 和子類已有方法的簽名相同但返回類型不同,會導(dǎo)致編譯錯(cuò)誤。
  • 和子類的已有方法簽名相同,會導(dǎo)致子類無意中復(fù)寫,回到了第一種情況。
  • 和子類無沖突,但可能會影響程序的正確性。比如子類中元素加入集合必須要滿足特定條件,這時(shí)候如果超類加入了一個(gè)無需檢測就可以直接將元素插入的方法,程序的正確性就受到了威脅。

設(shè)計(jì)可繼承的類

設(shè)計(jì)可以用來繼承的類時(shí),應(yīng)該注意:

  • 對于存在自用性的可覆蓋方法,應(yīng)該用文檔精確描述調(diào)用細(xì)節(jié)。
  • 盡可能少的暴露受保護(hù)成員,否則會暴露太多實(shí)現(xiàn)細(xì)節(jié)。
  • 構(gòu)造器不應(yīng)該調(diào)用任何可覆蓋的方法。

詳細(xì)解釋下第三點(diǎn)。它實(shí)際上和 繼承打破了封裝性 里討論的問題很相似,假設(shè)有以下代碼:

public class Father {
 public Father() {
  someMethod();
 }

 public void someMethod() {
 }
}
public class Son extends Father {
 private Date date;

 public Son() {
  this.date = new Date();
 }

 @Override
 public void someMethod() {
  System.out.println("Time = " + date.getTime());
 }
}

上述代碼在運(yùn)行測試時(shí)就會拋出NullPointerException :

public class SonTest {
 private Son  son = new Son();

 @Test
 public void test() {
  son.someMethod();
 }
}

因?yàn)槌惖臉?gòu)造函數(shù)會在子類的構(gòu)造函數(shù)之前先運(yùn)行,這里超類的構(gòu)造函數(shù)對someMethod()有依賴,同時(shí)someMethod()被重寫,所以超類的構(gòu)造函數(shù)里調(diào)用到的將是Son.someMethod(),而這時(shí)候子類還沒被初始化,于是在運(yùn)行到date.getTime()時(shí)便拋出了空指針異常。

因此,如果在超類的構(gòu)造函數(shù)里對可覆蓋的方法有依賴,那么在繼承時(shí)就可能會出錯(cuò)。

結(jié)論

繼承有很多優(yōu)點(diǎn),但使用繼承時(shí)應(yīng)該慎重并多加考慮。同樣用來實(shí)現(xiàn)代碼復(fù)用的還有復(fù)合,如果使用繼承和復(fù)合皆可(這是前提),那么應(yīng)該優(yōu)先使用復(fù)合,因?yàn)閺?fù)合可以保持超類對實(shí)現(xiàn)細(xì)節(jié)的屏蔽,上述關(guān)于繼承的缺點(diǎn)都可以用復(fù)合來避免。這也是所謂的復(fù)合優(yōu)先于繼承。

如果使用繼承,那么應(yīng)該留意重寫超類中存在自用性的可覆蓋方法可能會出錯(cuò),即使不進(jìn)行重寫,超類更新時(shí)也可能會引入錯(cuò)誤。同時(shí)也應(yīng)該精心設(shè)計(jì)超類,對任何相互調(diào)用的可覆蓋方法提供詳細(xì)文檔。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

  • 詳解Spring Data操作Redis數(shù)據(jù)庫

    詳解Spring Data操作Redis數(shù)據(jù)庫

    Redis是一種NOSQL數(shù)據(jù)庫,Key-Value形式對數(shù)據(jù)進(jìn)行存儲,其中數(shù)據(jù)可以以內(nèi)存形式存在,也可以持久化到文件系統(tǒng)。Spring data對Redis進(jìn)行了很好的封裝,用起來也是十分的得心應(yīng)手,接下來通過本文給大家分享Spring Data操作Redis數(shù)據(jù)庫,需要的朋友參考下
    2017-03-03
  • 詳解Spring中@Component和@Configuration的區(qū)別

    詳解Spring中@Component和@Configuration的區(qū)別

    一直有同學(xué)搞不清Spring中@Component和@Configuration這兩個(gè)注解有什么區(qū)別,所以這篇文章小編就給大家簡單介紹一下@Component和@Configuration的區(qū)別,需要的朋友可以參考下
    2023-07-07
  • 關(guān)于HashMap源碼解讀

    關(guān)于HashMap源碼解讀

    HashMap是基于哈希表的Map接口實(shí)現(xiàn),主要用于存儲鍵值對,它通過數(shù)組、鏈表和紅黑樹來實(shí)現(xiàn),解決了哈希沖突問題,Java?8中,HashMap對數(shù)據(jù)結(jié)構(gòu)進(jìn)行了優(yōu)化,引入紅黑樹來提高查找效率,此外,HashMap是非線程安全的,適用于單線程環(huán)境
    2024-09-09
  • java 類加載與自定義類加載器詳解

    java 類加載與自定義類加載器詳解

    本文主要介紹了java 類加載與自定義類加載器。具有一定的參考價(jià)值,下面跟著小編一起來看下吧
    2017-01-01
  • 詳解java實(shí)踐SPI機(jī)制及淺析源碼

    詳解java實(shí)踐SPI機(jī)制及淺析源碼

    這篇文章主要介紹了詳解java實(shí)踐SPI機(jī)制及淺析源碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 淺談java線程中生產(chǎn)者與消費(fèi)者的問題

    淺談java線程中生產(chǎn)者與消費(fèi)者的問題

    下面小編就為大家?guī)硪黄獪\談java線程中生產(chǎn)者與消費(fèi)者的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-07-07
  • Java Map集合詳解與演示

    Java Map集合詳解與演示

    Map用于保存具有映射關(guān)系的數(shù)據(jù),Map集合里保存著兩組值,一組用于保存Map的ley,另一組保存著Map的value,可以理解為Map中的元素是兩個(gè)對象,一個(gè)對象作為鍵,一個(gè)對象作為值。鍵不可以重復(fù),但是值可以重復(fù)
    2021-11-11
  • Java實(shí)現(xiàn)Dijkstra算法的示例代碼

    Java實(shí)現(xiàn)Dijkstra算法的示例代碼

    Dijkstra(迪杰斯特拉)算法是典型的單源最短路徑算法,用于計(jì)算一個(gè)節(jié)點(diǎn)到其他所有節(jié)點(diǎn)的最短路徑。本文主要介紹了實(shí)現(xiàn)這一算法的Java代碼,需要的可以參考一下
    2022-07-07
  • Java中的守護(hù)線程問題

    Java中的守護(hù)線程問題

    這篇文章主要介紹了Java中的守護(hù)線程問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • SpringBoot中定義Bean的方法總結(jié)

    SpringBoot中定義Bean的方法總結(jié)

    在Spring Boot應(yīng)用程序中,定義Bean是非常常見的操作,它是構(gòu)建應(yīng)用程序的基礎(chǔ),pring Boot提供了多種方式來定義Bean,每種方式都有其適用的場景和優(yōu)勢,本文將介紹Spring Boot中定義Bean的幾種常見方式,需要的朋友可以參考下
    2023-12-12

最新評論