四虎影视库国产精品一区-四虎影视库-四虎影视久久久免费-四虎影视久久久-四虎影视久久-四虎影视精品永久免费网站

TEL:15195455103

Java培訓(xùn) > Java知識 >

java4個技巧:從繼承和覆蓋,到終的類和方法

作者:南京java培訓(xùn)????來源:南京????發(fā)布時間:2020-02-03 09:53????瀏覽量:93

   本文探索了四種技術(shù),它們可以在綁定時使用,并將其引入到代碼庫中,以提高開發(fā)的易用性和可讀性。并非所有這些技術(shù)都適用于所有情況,甚至大多數(shù)情況。例如,可能只是有一些方法,只會讓自己協(xié)變返回類型或一些泛型類適合使用區(qū)間的泛型類型的模式,而其他人,如終方法和類和try-with-resources塊,將提高可讀性和清潔度的大多數(shù)種代碼基底的意圖。無論哪種情況,重要的是不僅要知道這些技術(shù)的存在,還要知道何時明智地應(yīng)用它們。

1. 協(xié)變返回類型

  即使是介紹性的Java操作手冊也會包含關(guān)于繼承、接口、抽象類和方法重寫的內(nèi)容,但是即使是高級的文本也很少會在重寫方法時探索更復(fù)雜的可能性。例如,下面的代碼片段即使是初級的Java開發(fā)人員也不會感到驚訝:

  public interface Animal { public String makeNoise();}public class Dog implements Animal { @Override public String makeNoise() { return "Woof"; }}public class Cat implements Animal { @Override public String makeNoise() { return "Meow"; }}

  這是多態(tài)性的基本概念:可以根據(jù)對象的接口(Animal::makeNoise)調(diào)用方法,但是方法調(diào)用的實際行為取決于實現(xiàn)類型(Dog::makeNoise)。例如,下面方法的輸出會根據(jù)是否將Dog對象或Cat對象傳遞給該方法而改變:

  public class Talker { public static void talk(Animal animal) { System.out.println(animal.makeNoise()); }}Talker.talk(new Dog()); //輸出:低音Talker.talk(new Cat()); //輸出:喵

  雖然這是許多Java應(yīng)用程序中常用的一種技術(shù),但是在重寫方法時可以采取一個不太為人所知的操作:更改返回類型。雖然這看起來是一種覆蓋方法的開放方式,但是對于被覆蓋方法的返回類型有一些嚴(yán)格的限制。

  如果方法聲明d 1返回類型為R 1覆蓋或隱藏另一個方法d的聲明 2返回類型為R 2,那么d 1是否可以用返回類型替換d 2,或者出現(xiàn)編譯時錯誤。

  返回類型替換表(同上,第240頁)定義為

  如果R1是空的,那么R2無效

  如果R1那么原始類型是R嗎2等于R1

  如果R1是一種引用類型,則下列其中之一為真:R1適用于d的類型參數(shù)2是R的子類型嗎2.R1可以轉(zhuǎn)換為R的子類型嗎2通過無節(jié)制的轉(zhuǎn)換d1沒有與d相同的簽名嗎2和R1R = |2|

  可以說有趣的案例是規(guī)則3.a。和3. b。:重寫方法時,可以將返回類型的子類型聲明為被重寫的返回類型。例如:

  public interface CustomCloneable { public Object customClone();}public class Vehicle implements CustomCloneable { private final String model; public Vehicle(String model) { this.model = model; } @Override public Vehicle customClone() { return new Vehicle(this.model); } public String getModel() { return this.model; }}Vehicle originalVehicle = new Vehicle("Corvette");Vehicle clonedVehicle = originalVehicle.customClone();System.out.println(clonedVehicle.getModel());

  雖然clone()的原始返回類型是Object,但是我們能夠在克隆的車輛上調(diào)用getModel()(不需要顯式的強制轉(zhuǎn)換),因為我們已經(jīng)將Vehicle::clone的返回類型重寫為Vehicle。這消除了對混亂類型強制轉(zhuǎn)換的需要,我們知道我們要尋找的返回類型是一個載體,即使它被聲明為一個對象(這相當(dāng)于基于先驗信息的安全類型強制轉(zhuǎn)換,但嚴(yán)格來說是不安全的):

  Vehicle clonedVehicle = (Vehicle) originalVehicle.customClone();

  注意,我們?nèi)匀豢梢詫④囕v類型聲明為對象,而返回類型將恢復(fù)為對象的原始類型:

  Object clonedVehicle = originalVehicle.customClone();System.out.println(clonedVehicle.getModel()); //錯誤:getModel不是一個對象方法

  注意,對于泛型參數(shù)不能重載返回類型,但是對于泛型類可以重載。例如,如果基類或接口方法返回一個列表 ,則可以將子類的返回類型重寫為ArrayList ,但不能將其重寫為List 。

2. 區(qū)間的泛型類型

  創(chuàng)建泛型類是創(chuàng)建一組以類似方式與組合對象交互的類的佳方法。例如,一個列表 只是存儲和檢索類型為T的對象,而不了解它所包含元素的性質(zhì)。在某些情況下,我們希望約束泛型類型參數(shù)(T)使其具有特定的特征。例如,給定以下接口

  public interface Writer { public void write(); }

  我們可能想創(chuàng)建一個特定的作家集合如下與復(fù)合模式:

  public class WriterComposite implements Writer { private final List writers; public WriterComposite(List writers) { this.writers = writer; } @Override public void write() { for (Writer writer: this.writers) { writer.write(); } }}

  我們現(xiàn)在可以遍歷一個Writer樹,不知道我們遇到的特定Writer是一個獨立的Writer(一個葉子)還是一個Writer集合(一個組合)。如果我們還想讓我們的組合作為讀者和作者的組合呢?例如,如果我們有以下接口

  public interface Reader { public void read(); }

  如何將WriterComposite修改為ReaderWriterComposite?一種技術(shù)是創(chuàng)建一個新的接口ReaderWriter,將Reader和Writer接口融合在一起:

  public interface ReaderWriter extends Reader, Writer {}

  然后我們可以修改現(xiàn)有的WriterComposite如下:

  public class ReaderWriterComposite implements ReaderWriter { private final List readerWriters; public WriterComposite(List readerWriters) { this.readerWriters = readerWriters; } @Override public void write() { for (Writer writer: this.readerWriters) { writer.write(); } } @Override public void read() { for (Reader reader: this.readerWriters) { reader.read(); } }}

  雖然這確實實現(xiàn)了我們的目標(biāo),但是我們在代碼中創(chuàng)建了膨脹:我們創(chuàng)建了一個接口,其惟一目的是將兩個現(xiàn)有接口合并在一起。隨著接口越來越多,我們可以開始看到膨脹的組合爆炸。例如,如果我們創(chuàng)建一個新的修飾符接口,我們現(xiàn)在需要創(chuàng)建ReaderModifier、WriterModifier和ReaderWriter接口。注意,這些接口沒有添加任何功能:它們只是合并現(xiàn)有的接口。

  為了消除這種膨脹,我們需要能夠指定ReaderWriterComposite接受泛型類型參數(shù)(當(dāng)且僅當(dāng)它們既是讀寫器又是寫器時)。交叉的泛型類型允許我們這樣做。為了指定泛型類型參數(shù)必須實現(xiàn)讀寫接口,我們在泛型類型約束之間使用&操作符:

  public class ReaderWriterComposite implements Reader, Writer { private final List readerWriters; public WriterComposite(List readerWriters) { this.readerWriters = readerWriters; } @Override public void write() { for (Writer writer: this.readerWriters) { writer.write(); } } @Override public void read() { for (Reader reader: this.readerWriters) { reader.read(); } }}

  在不擴展繼承樹的情況下,我們現(xiàn)在可以約束泛型類型參數(shù)來實現(xiàn)多個接口。注意,如果其中一個接口是抽象類或具體類,則可以指定相同的約束。例如,如果我們將Writer接口更改為類似下面的抽象類。

  public abstract class Writer { public abstract void write();}

  我們?nèi)匀豢梢约s束我們的泛型類型參數(shù)是讀者和作家,但是作者(因為它是一個抽象類,而不是一個接口)必須首先指定(也請注意,我們現(xiàn)在ReaderWriterComposite擴展了寫信人抽象類并實現(xiàn)了接口,而不是實現(xiàn)兩個):

  public class ReaderWriterComposite extends Writer implements Reader { //與前面一樣的類}

  還需要注意的是,這種交互的泛型類型可以用于兩個以上的接口(或一個抽象類和多個接口)。例如,如果我們想要我們的組合也包括修飾符接口,我們可以寫我們的類定義如下:

  public class ReaderWriterComposite implements Reader, Writer, Modifier { private final List things; public ReaderWriterComposite(List things) { this.things = things; } @Override public void write() { for (Writer writer: this.things) { writer.write(); } } @Override public void read() { for (Reader reader: this.things) { reader.read(); } } @Override public void modify() { for (Modifier modifier: this.things) { modifier.modify(); } }}

  盡管執(zhí)行上述操作是合法的,但這可能是代碼氣味的一種標(biāo)志(作為讀取器、寫入器和修飾符的對象可能是更具體的東西,比如文件)。

北大青鳥軟件學(xué)校

3.Auto-Closeable類

  創(chuàng)建資源類是一種常見的實踐,但是維護資源的完整性可能是一個具有挑戰(zhàn)性的前景,特別是在涉及異常處理時。例如,假設(shè)我們創(chuàng)建了一個資源類resource,并希望對該資源執(zhí)行一個可能拋出異常的操作(實例化過程也可能拋出異常):

  public class Resource { public Resource() throws Exception { System.out.println("Created resource"); } public void someAction() throws Exception { System.out.println("Performed some action"); } public void close() { System.out.println("Closed resource"); }}

  無論是哪種情況(如果拋出或不拋出異常),我們都希望關(guān)閉資源以確保沒有資源泄漏。正常的過程是將我們的close()方法封裝在finally塊中,確保無論發(fā)生什么,我們的資源在封閉的執(zhí)行范圍完成之前是關(guān)閉的:

  Resource resource = null;try { resource = new Resource(); resource.someAction();} catch (Exception e) { System.out.println("Exception caught");}finally { resource.close();}

  通過簡單的檢查,有很多樣板代碼會降低對資源對象執(zhí)行someAction()的可讀性。為了糾正這種情況,Java 7引入了try-with-resources語句,通過該語句可以在try語句中創(chuàng)建資源,并在保留try執(zhí)行范圍之前自動關(guān)閉資源。要使類能夠使用try-with-resources,它必須實現(xiàn)AutoCloseable接口:

  public class Resource implements AutoCloseable { public Resource() throws Exception { System.out.println("Created resource"); } public void someAction() throws Exception { System.out.println("Performed some action"); } @Override public void close() { System.out.println("Closed resource"); }}

  我們的資源類現(xiàn)在實現(xiàn)了AutoCloseable接口,我們可以清理我們的代碼,以確保我們的資源是關(guān)閉之前離開的嘗試執(zhí)行范圍:

  try (Resource resource = new Resource()) { resource.someAction();} catch (Exception e) { System.out.println("Exception caught");}

  與不使用資源進行嘗試的技術(shù)相比,此過程要少得多,并且維護了相同的安全性(在完成try執(zhí)行范圍后,資源總是關(guān)閉的)。如果執(zhí)行上述try-with-resources語句,則得到以下輸出:

  Created resourcePerformed some actionClosed resource

  為了演示這種使用資源的嘗試技術(shù)的安全性,我們可以更改someAction()方法來拋出一個異常:

  public class Resource implements AutoCloseable { public Resource() throws Exception { System.out.println("Created resource"); } public void someAction() throws Exception { System.out.println("Performed some action"); throw new Exception(); } @Override public void close() { System.out.println("Closed resource"); }}

  如果我們再次運行try-with-resources語句,我們將得到以下輸出:

  Created resourcePerformed some actionClosed resourceException caught

  注意,即使在執(zhí)行someAction()方法時拋出了一個異常,我們的資源還是關(guān)閉了,然后捕獲了異常。這確保在離開try執(zhí)行范圍之前,我們的資源被保證是關(guān)閉的。同樣重要的是,資源可以實現(xiàn)close - able接口,并且仍然使用try-with-resources語句。實現(xiàn)AutoCloseable接口和Closeable接口之間的區(qū)別在于close()方法簽名拋出的異常類型:exception和IOException。在我們的例子中,我們只是更改了close()方法的簽名,以避免拋出異常。

4. 后的類和方法

  在幾乎所有的情況下,我們創(chuàng)建的類都可以由另一個開發(fā)人員擴展并定制以滿足該開發(fā)人員的需求(我們可以擴展自己的類),即使我們并沒有打算要擴展我們的類。雖然這對于大多數(shù)情況已經(jīng)足夠了,但是有時我們可能不希望覆蓋某個方法,或者更一般地說,擴展某個類。例如,如果我們創(chuàng)建一個文件類,封裝了文件系統(tǒng)上的文件的閱讀和寫作,我們可能不希望任何子類覆蓋讀(int字節(jié))和寫(字符串?dāng)?shù)據(jù))方法(這些方法中的邏輯是否改變,它可能導(dǎo)致文件系統(tǒng)會損壞)。在這種情況下,我們將不可擴展的方法標(biāo)記為final:

  public class File { public final String read(int bytes) { //對文件系統(tǒng)執(zhí)行讀操作 return "Some read data"; } public final void write(String data) { //執(zhí)行對文件系統(tǒng)的寫操作 }}

  現(xiàn)在,如果另一個類希望覆蓋讀或?qū)懛椒ǎ瑒t會拋出編譯錯誤:無法覆蓋文件中的終方法。我們不僅記錄了不應(yīng)該重寫我們的方法,而且編譯器還確保了在編譯時強制執(zhí)行這個意圖。

  將這個想法擴展到整個類,有時我們可能不希望我們創(chuàng)建的類被擴展。這不僅使類的每個方法都不可擴展,而且還確保了類的任何子類型都不會被創(chuàng)建。例如,如果我們正在創(chuàng)建一個使用密鑰生成器的安全框架,我們可能不希望任何外部開發(fā)人員擴展我們的密鑰生成器并覆蓋生成算法(自定義功能可能在密碼方面較差并危及系統(tǒng)):

  public final class KeyGenerator { private final String seed; public KeyGenerator(String seed) { this.seed = seed; } public CryptographicKey generate() { //…做一些加密工作來生成密鑰… }}

  通過使我們的KeyGenerator類成為final,編譯器將確保沒有類可以擴展我們的類并將自己作為有效的密鑰生成器傳遞給我們的框架。雖然簡單地將generate()方法標(biāo)記為final似乎就足夠了,但這并不能阻止開發(fā)人員創(chuàng)建自定義密鑰生成器并將其作為有效的生成器傳遞。由于我們的系統(tǒng)是面向安全的,所以好盡可能地不信任外部世界(如果我們提供了KeyGenerator類中的其他方法,聰明的開發(fā)人員可能會通過更改它們的功能來更改生成算法)。

  盡管這看起來是對開放/封閉原則的公然漠視(事實的確如此),但這樣做是有充分理由的。正如我們在上面的安全性示例中所看到的,很多時候,我們無法允許外部世界對我們的應(yīng)用程序做它想做的事情,我們必須在關(guān)于繼承的決策中非常慎重。像Josh Bolch這樣的作者甚至說,一個類應(yīng)該被有意地設(shè)計成可擴展的,或者應(yīng)該顯式地對擴展關(guān)閉(有效的Java)。盡管他故意夸大了這個想法(參見記錄繼承或不允許繼承),但他提出了一個很好的觀點:我們應(yīng)該仔細(xì)考慮哪些類應(yīng)該擴展,哪些方法可以重寫。

結(jié)論

  雖然我們編寫的大多數(shù)代碼只利用了Java的一小部分功能,但它足以解決我們遇到的大多數(shù)問題。有時候,我們需要更深入地研究語言,重新拾起那些被遺忘或未知的部分來解決特定的問題。其中一些技術(shù),如協(xié)變返回類型和交互式泛型類型,可以在一次性的情況下使用,而其他技術(shù),如自動關(guān)閉的資源和終方法和類,可以而且應(yīng)該更頻繁地使用,以生成更具可讀性和更精確的代碼。將這些技術(shù)與日常編程實踐相結(jié)合不僅有助于更好地理解我們的意圖,而且有助于更好地編寫更好的Java。


本文內(nèi)容、圖片由互聯(lián)網(wǎng)用戶自發(fā)貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請發(fā)送郵件至2353260942@qq.com 舉報,一經(jīng)查實,本站將立刻刪除。(如需投稿聯(lián)系管理員開通!)

? CopyRight njjava.com ???? 蘇ICP備14052071號

搶試聽名額

名額僅剩66名

教育改變生活

WE CHANGE LIVES