《OnJava8》精讀(四) 接口與內部類

在這裏插入圖片描述

介紹


《On Java 8》是什麼?

它是《Thinking In Java》的作者Bruce Eckel基於Java8寫的新書。裏面包含了對Java深入的理解及思想維度的理念。可以比作Java界的“武學祕籍”。任何Java語言的使用者,甚至是非Java使用者但是對面向對象思想有興趣的程序員都該一讀的經典書籍。目前豆瓣評分9.5,是公認的編程經典。

爲什麼要寫這個系列的精讀博文?

由於書籍讀起來時間久,過程漫長,因此產生了寫本精讀系列的最初想法。除此之外,由於中文版是譯版,讀起來還是有較大的生硬感(這種差異並非譯者的翻譯問題,類似英文無法譯出唐詩的原因),這導致我們理解作者意圖需要一點推敲。再加上原書的內容很長,只第一章就多達一萬多字(不含代碼),讀起來就需要大量時間。

所以,如果現在有一個人能替我們先仔細讀一遍,篩選出其中的精華,讓我們可以在地鐵上或者路上不用花太多時間就可以瞭解這邊經典書籍的思想那就最好不過了。於是這個系列誕生了。

一些建議

推薦讀本書的英文版原著。此外,也可以參考本書的中文譯版。我在寫這個系列的時候,會盡量的保證以“陳述”的方式表達原著的內容,也會寫出自己的部分觀點,但是這種觀點會保持理性並儘量少而精。本系列中對於原著的內容會以引用的方式體現。
最重要的一點,大家可以通過博客平臺的評論功能多加交流,這也是學習的一個重要環節。

第十章 接口


本章總字數:11000
關鍵詞:

  • 抽象類和方法
  • 創建接口
  • 接口適配

抽象類和方法

抽象方法只有聲明沒有方法體。如下:

abstract void f();

包含有抽象方法的類叫做——抽象類。如果一個類裏含有抽象方法(無論是一個或者多個),該類必須聲明爲抽象。比如:

abstract class Basic {
    abstract void unimplemented();
}

如果創建一個繼承抽象類的新類併爲之創建對象,那麼就必須爲基類的所有抽象方法提供方法定義。如果不這麼做(可以選擇不做),新類仍然是一個抽象類,編譯器會強制我們爲新類加上 abstract 關鍵字。

如果你寫了一個類,這個類裏沒有任何抽象方法,但是你對類聲明爲 abstract,那這個類依然無法被創建。有時候這種做法可以防止你未完成的類被別人調用:)

創建接口

使用interface 來創建接口。

// interfaces/PureInterface.java
// Interface only looked like this before Java 8
public interface PureInterface {
    int m1(); 
    void m2();
    double m3();
}

我們可以認爲,接口是一種特殊的抽象類——所有的方法都是抽象的。所以很明顯,接口也不能直接原來創建對象。

有意思的是,在Java8中,接口可以使用default 關鍵詞。它可以用來標記一個默認的方法。

// interfaces/InterfaceWithDefault.java
interface InterfaceWithDefault {
    void firstMethod();
    void secondMethod();

    default void newMethod() {
        System.out.println("newMethod");
    }
}
...

InterfaceWithDefault i = new Implementation2();
i.firstMethod();
i.secondMethod();
i.newMethod();

這些方法都可以被成功調用。

Java 過去是一種嚴格要求單繼承的語言:只能繼承自一個類(或抽象類),但可以實現任意多個接口。多年後的現在,Java 通過默認方法具有了某種多繼承的特性。結合帶有默認方法的接口意味着結合了多個基類中的行爲。

抽象類和接口

如果是在Java8之前,接口與抽象類的差異還很明顯,但是在Java8加入了default 方法後,兩者的選擇產生了一些疑惑,以下是他們的區別:
在這裏插入圖片描述
不僅類可以繼承,接口也可以。使用繼承的方式,我們可以拓展接口。

interface Monster {
    void menace();
}
interface DangerousMonster extends Monster {
    void destroy();
}
class DragonZilla implements DangerousMonster {
    @Override
    public void menace() {}

    @Override
    public void destroy() {}
}

有沒有想過這種可能,一個類繼承自A、B、C三種接口。這些接口中都有一個名叫f的方法,且參數類型不同,那會怎樣?
事實上,編譯器會報錯。因爲繼承的接口命名相同而出現了混淆。

當打算組合接口時,在不同的接口中使用相同的方法名通常會造成代碼可讀性的混亂,儘量避免這種情況。

接口適配

接口最強大的一點在於,可以規範某一系列類的標準。只要繼承並實現這個接口,那一定就是符合我的接口標準的類。

利用這點,我們可以在實際項目中規定某些場景必須使用指定接口實現的類型。

打個比方,Animal 接口下有eat、sleep等方法。而Cat、Dog實現了該接口。我們現在要做一個動物園的模塊。在動物園中出現的動物必須是Animal 接口的實現類,如果你傳入非動物類,那是不被允許的。這樣就規範了對象模型。

可以說:“只要對象遵循接口,就可以調用方法” ,這使得方法更加靈活,通用,並更具可複用性。

接口字段

在接口中是可以定義字段的。接口中的字段都是static 和 final的。

import java.util.*;

public interface RandVals {
    Random RAND = new Random(47);
    int RANDOM_INT = RAND.nextInt(10);
    long RANDOM_LONG = 10;
}	

第十一章 內部類


本章總字數:13000

關鍵詞:

  • 內部類的概念
  • 創建內部類
  • .this 和 .new
  • 內部類作用域
  • 什麼時候需要內部類
  • 內部類的一些特點

什麼是內部類

作者在本章一開始就簡明扼要的說出了定義——一個定義在另一個類中的類,叫作內部類。

內部類有自己的特殊性,它位於一個類的內部,所以他的訪問級別是特殊的,同時它與“組合”的概念又不相同。值得一提,“ Java 8 的 Lambda 表達式和方法引用減少了編寫內部類的需求”。

創建內部類

創建一個內部類很簡單:

// innerclasses/Parcel1.java
// Creating inner classes
public class Parcel1 {
    class Contents {
        private int i = 11;
        public int value() { return i; }
    }
    class Destination {
        private String label;
        Destination(String whereTo) {
            label = whereTo;
        }
        String readLabel() { return label; }
    }
    // Using inner classes looks just like
    // using any other class, within Parcel1:
    public void ship(String dest) {
        Contents c = new Contents();
        Destination d = new Destination(dest);
        System.out.println(d.readLabel());
    }
    public static void main(String[] args) {
        Parcel1 p = new Parcel1();
        p.ship("Tasmania");
    }
}

結果:

Tasmania

當我們在 ship() 方法裏面使用內部類的時候,與使用普通類沒什麼不同。在這裏,明顯的區別只是內部類的名字是嵌套在 Parcel1 裏面的。

一個內部類擁有其所有外部對象的訪問權。說到底這與我們之前瞭解到的引用有關。

當某個外圍類的對象創建了一個內部類對象時,此內部類對象必定會祕密地捕獲一個指向那個外圍類對象的引用。然後,在你訪問此外圍類的成員時,就是用那個引用來選擇外圍類的成員。

.this 和 .new

.this用來供內部類調用外部類的對象。而.new則是在外部使用,目的是告訴外部類初始化指定的內部類。

看示例:

// innerclasses/DotThis.java
// Accessing the outer-class object
public class DotThis {
    void f() { System.out.println("DotThis.f()"); }

    public class Inner {
        public DotThis outer() {
            return DotThis.this;
            // A plain "this" would be Inner's "this"
        }
    }

    public Inner inner() { return new Inner(); }

    public static void main(String[] args) {
        DotThis dt = new DotThis();
        DotThis.Inner dti = dt.inner();
        dti.outer().f();
        
        DotThis dn = new DotThis();
        DotThis.Inner dni = dn.new Inner();
    }
}

內部類的作用域

內部類可以被用在幾乎任何一個地方。甚至在方法內部:

// innerclasses/Parcel5.java
// Nesting a class within a method
public class Parcel5 {
    public Destination destination(String s) {
        final class PDestination implements Destination {
            private String label;

            private PDestination(String whereTo) {
                label = whereTo;
            }

            @Override
            public String readLabel() { return label; }
        }
        return new PDestination(s);
    }

    public static void main(String[] args) {
        Parcel5 p = new Parcel5();
        Destination d = p.destination("Tasmania");
    }
}

除此之外,內部類還可以被寫在任何一個作用域內,如 If語句中。

        if (b) {
            class TrackingSlip {
                private String id;
                TrackingSlip(String s) {
                    id = s;
                }
                String getSlip() {
                    return id;
                }
            }
            TrackingSlip ts = new TrackingSlip("slip");
            String s = ts.getSlip();
        }

什麼時候需要內部類

原著的話是“爲什麼需要內部類”。但是我們更加實際,我們更想討論什麼時候才需要用到它。
從作者處得出的答案來看,主要有以下幾點:

  1. 閉包(即實現某一系列功能的對象)
  2. 實現多重繼承
  3. 一些應用程序框架中會使用

我們見到最多的情景是閉包。也就是在代碼塊中需要臨時性的使用某一種對象功能時創建它。但是Java8 裏出現了Lambda 後,這種情況正在被替代。

之後就是多重繼承了。因爲Java中不允許有多個基類,有一些場景中需要實現某種多重繼承功能就有可能需要內部類。至於第3點已經不再重要(被主要用於用戶圖形界面中)。

控制框架是一類特殊的應用程序框架,它用來解決響應事件的需求。主要用來響應事件的系統被稱作事件驅動系統。應用程序設計中常見的問題之一是圖形用戶接口(GUI),它幾乎完全是事件驅動的系統。

內部類的一些特點

內部類可以繼承、覆蓋嗎?

由於內部類的特殊性,一般很少去這麼幹。在這裏我們也只是做了解。
首先,內部類是可以被繼承和覆蓋的。但是實現的方式比較特殊(主要是構造器部分)。

我們知道在Java中一個類產生一個.class文件,那內部類又如何呢?
事實上,內部類也會生成。只不過命名方式有些特別:

這些類文件的命名有嚴格的規則:外圍類的名字,加上“$",再加上內部類的名字。

例如:

Counter.class
LocalInnerClass$1.class
LocalInnerClass$LocalCounter.class
LocalInnerClass.class

總結

如果說前幾章的內容都是OOP語言的共通基礎。那這兩章內容就是有Java特色的編程了。像接口的default方法和內部類實現的多重繼承,這些都比前幾章要多花時間才能理解。強烈推薦大家收藏本系列。後面的章節也會在之後一一呈現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章