Java 編程思想筆記:Learn 6

第9章 接口

接口和內(nèi)部類為我們提供了一種將接口與實現(xiàn)分離的更加結(jié)構(gòu)化的方法核偿。

9.1 抽象類和抽象方法

基類往往只是定義函數(shù)簽名诚欠,并沒有方法的具體實現(xiàn)。

因為基類是為了建立通用接口漾岳,不同的子類可以用不同的方法表示此接口轰绵。

那么,這種基類就被稱為抽象類尼荆。

在 Java 中提供一個叫做 “抽象方法” 的機制左腔,這種方法是不完整的方法,僅有聲明而沒有方法體捅儒。

如液样,abstract void f()

包含抽象方法的類叫做抽象類,如果一個類包含一個或多個抽象方法野芒,該類必須限定為抽象的蓄愁。

抽象類不能實例化,強行實例化會編譯出錯狞悲。

抽象類可以繼承,如果子類不對父類的抽象方法進行重寫妇斤,那么子類也是抽象類摇锋,如果子類對抽象類進行重寫丹拯,那么子類就是一般的類。

// 抽象類只需要包含抽象方法荸恕,抽象方法只有方法簽名乖酬;
// 抽象類也可以像普通類一樣,包含屬性和普通方法融求;
abstract class Instrument4{
    private int i;
    public abstract void play(String n);
    public String what() {return "Instrument";}
    public abstract void adjust();
}

// 實現(xiàn)抽象類的子類只需要重寫父類的抽象方法就好咬像,不必重寫所有方法
class Wind4 extends Instrument4{
    public void play(String n){
        System.out.println("Wind4 play");
    }
    public void adjust(){
        System.out.println("adjust");
    }
}

class Percussion extends Instrument4 {
    @Override
    public void play(String n) {
        System.out.println("Percussion play");
    }

    @Override
    public void adjust(){
        System.out.println("adjust");
    }
}


class Stringed extends Instrument4{
    @Override
    public void play(String n){
        System.out.println("Stringed play");
    }

    @Override
    public void adjust(){
        System.out.println("adjust");
    }
}

public class Music4 {
    private static void tune(Instrument4 i){
        String n = "111";
        i.play(n);
    }
    
    static void tuneAll(Instrument4[] e){
        for(Instrument4 i : e){
            tune(i);
        } 
    }
    
    public static void main(String[] args){
    // 使用基類作為參數(shù)類型,就可以調(diào)用所有子類生宛。
        Instrument4[] orchestra = {
                new Wind4(),
                new Percussion(),
                new Stringed()
        };
        tuneAll(orchestra);
    }
}

9.2 接口

較之于抽象類县昂,接口中只能定義方法簽名。所以說接口是完全的抽象類陷舅。

接口不僅提供了一個極度抽象的類倒彰,而且它允許人們通過創(chuàng)建一個能夠被向上轉(zhuǎn)型為多種基類的類型,來實現(xiàn)多繼承莱睁。
也就是說待讳,如果一個方法把接口作為參數(shù),相當于所有實現(xiàn)該接口的類都可以當作此方法的參數(shù)來運算仰剿,這就有點像動態(tài)語言中的多繼承创淡。

把上一節(jié)的例子修改為接口,

interface  Instrument4{
      void play(String n);
      void adjust();
}

class Wind4 implements Instrument4{
    @Override
    public void play(String n){
        System.out.println("Wind4 play");
    }

    @Override
    public void adjust(){
        System.out.println("adjust");
    }
}

class Percussion implements Instrument4 {
    @Override
    public void play(String n) {
        System.out.println("Percussion play");
    }

    @Override
    public void adjust(){
        System.out.println("adjust");
    }
}


class Stringed implements Instrument4{
    @Override
    public void play(String n){
        System.out.println("Stringed play");
    }

    @Override
    public void adjust(){
        System.out.println("adjust");
    }
}

public class Music4 {
    private static void tune(Instrument4 i){
        String n = "111";
        i.play(n);
    }

    static void tuneAll(Instrument4[] e){
        for(Instrument4 i : e){
            tune(i);
        }
    }

    public static void main(String[] args){
        Instrument4[] orchestra = {
                new Wind4(),
                new Percussion(),
                new Stringed()
        };
        tuneAll(orchestra);
    }
}

由此可見南吮,接口是徹底的抽象類辩昆,把 Instrument4 中的屬性和普通方法去掉,Instrument4 就變成一個接口旨袒。

9.3 完全解藕

9.3.1 . java 策略設計模式

利用繼承實現(xiàn)的策略模式汁针,Upcase / DownCase / Splitter 都是Processor的子類,在 Apply 中 Processor 是參數(shù)砚尽,通過傳遞 Processor 不同的子類施无,從而 Apply.process() 有了不同的行為。

策略模式就是通過傳遞不同的參數(shù)(實現(xiàn)接口的類)必孤,使得調(diào)用函數(shù)表現(xiàn)出不同的行為猾骡。

import java.util.Arrays;

class Processor{
    public String name(){
        return this.getClass().getName();
    }
    Object process(Object input){
        return input;
    }
}

class Upcase extends Processor{
    String process(Object input){
        return ((String) input).toUpperCase();
    }
}

class Downcase extends Processor{
    String process(Object input){
        return ((String) input).toLowerCase();
    }
}

class Splitter extends Processor{
    String process(Object input){
        return Arrays.toString(((String) input).split(" "));
    }
}

public class Apply {
    public static void process(Processor p, Object s){
        System.out.println("Using Processor " + p.name());
        System.out.println(p.process(s));
    }

    public static String s = "this is a good start";

    public static void main(String[] args){
        process(new Upcase(), s);
        process(new Downcase(), s);
        process(new Splitter(), s);
    }
}

9.3.2. java 適配器模式

class Processor2{
    public String name(){
        return this.getClass().getName();
    }
    Object process(Object input){
        return input;
    }
}

class Upcase2 extends Processor2{
    String process(Object input){
        return ((String) input).toUpperCase();
    }
}

class Downcase2 extends Processor2{
    String process(Object input){
        return ((String) input).toLowerCase();
    }
}

class Splitter2 extends Processor2{
    String process(Object input){
        return Arrays.toString(((String) input).split(" "));
    }
}

假如現(xiàn)在有一個其他的類 Process2,Apply.process() 也想調(diào)用它敷搪。但是這個類 Process2 不是自己的代碼兴想,或者我不知道源碼,不能修改赡勘。這時候就要:

  1. 修改Process 為一個接口嫂便,使得任何實現(xiàn)接口的類都能被 Apply.process() 調(diào)用。
  2. 做一個適配器闸与,使得 Process2 在不改動代碼的情況下毙替,能夠適配 Apply.process() 岸售。
interface Processor{
  String name();
  Object process(Object input);
}

適配器

public class Apply2Adapter implements Processor{

    private Processor2 processor2;

    Apply2Adapter(Processor2 processor2){
        this.processor2 = processor2;
    }

    @Override
    public String name(){
        return processor2.getClass().getName();
    }

    @Override
    public Object process(Object input){
        return processor2.process(input);
    }
}

9.4 Java 中的多重繼承

接口不僅僅只是一種更加純粹形式的抽象類,它的目標比這要高厂画。因為接口是根本沒有任何具體實現(xiàn)的——也就是說凸丸,沒有任何與接口相關的存儲; 因此,也就無法阻止多個接口的組合袱院。

一個類可以繼承多個接口屎慢,所有的接口名都置于 implements 關鍵字之后,用逗號將它們一一隔開忽洛∧寤荩可以繼承任意多個接口,并可以向上轉(zhuǎn)型為每個接口脐瑰,每一個接口都是一個獨立類型妖枚。

需要注意的是,當一個類又繼承又實現(xiàn)接口時苍在,必須將繼承類放在前面绝页,實現(xiàn)的接口放到后面。

package com.zzjack.rdsapi_demo.javathought;


interface CanFight{
    void fight();
}

interface CanSwim{
    void swim();
}

interface CanFly{
    void fly();
}

class ActionCharacter{
    public void fight(){}
}

class Hero extends ActionCharacter implements CanFight,
        CanSwim, CanFly{
    public void swim(){}
    public void fly(){}
}

public class Adventure {
    public static void t(CanFight x){
        x.fight();
    }
    
    public static void u(CanSwim s){
        s.swim();
    }
    
    public static void v(CanFly f){
        f.fly();
    }
    
    public static void w(ActionCharacter x){
        x.fight();
    }
    
    public static void main(String[] args){
        Hero h = new Hero();
        t(h);
        u(h);
        v(h);
        w(h);
    }
}

接口和抽象類的使用場景

  1. 為了能夠向上轉(zhuǎn)型為多個基類型寂恬,這樣就非常靈活续誉;
  2. 與使用抽象基類相同,防止客戶端程序員創(chuàng)建該類的對象初肉,并確保這僅僅是建立一個端口酷鸦。
    接口和抽象類的使用場景?
    如果要創(chuàng)建不帶任何方法定義和成員變量的積累牙咏,那么就該選擇成為接口而不是抽象類臼隔;

9.5 通過繼承來擴展接口

接口可以繼承,而且可以通過繼承在新接口中組合數(shù)個接口。

extends 這個關鍵詞,只能用于單一類钳降,但是可以引用多個基類接口佳谦,只需要用逗號一一隔開就好苔可。

interface Monster{
    void menace();
}

interface DangerousMonster extends Monster{
    void destory();
}

interface Lethal{
    void kill();
}

class DragonZilla implements DangerousMonster{
    public void menace(){}
    public void destory(){}
}

interface Vampire extends DangerousMonster, Lethal{
    void drinkBlood();
}

public class VeryBadVampire implements Vampire{
    public void menace(){}
    public void destory(){}
    public void kill(){}
    public void drinkBlood(){}
}

組合接口時命名沖突

interface I1 {
    void f();
}

interface I2{
    int f(int i);
}

interface I3{
    int f();
}

class C{
    public int f(){
        return 1;
    }
}

class C2 implements I1,I2{
    public void f(){}
    
    public int f(int i){
        return i;
    }
}

class C3 extends C implements I2{
    public int f(int i){
        return 1;
    }
}

class C4 extends C implements I3{
    public int f(){
        return 1;
    }
}


class C5 extends C implements I1{
    public void f(){} 
}

打算組合的接口中,如果組合的接口中有的方法簽名相同,就會造成錯誤。

9.6 適配接口

接口最吸引人的原因之一就是允許同一個接口具有多個不同的具體實現(xiàn)盛正。在簡單的情況中,它的體現(xiàn)形式通常是一個接受接口類型的方法屑埋,而該接口的實現(xiàn)和向該方法傳遞的對象則取決于方法的使用者豪筝。

因此,接口的一種常見用法就是前面提到的策略設計模式,此時你編寫一個執(zhí)行某些操作的方法壤蚜,而該方法將接受一個同樣是你指定的接口即寡。

public class RandomDoubles {
    private static Random rand = new Random(47);

    public double next(){
        return rand.nextDouble();
    }

    public static void main(String[] args){
        RandomDoubles randomDouble = new RandomDoubles();
        for(int i=0; i < 7; i++){
            System.out.print(randomDouble.next() + " ");
        }
    }
}

使用適配器模式徊哑,被適配的類可以通過繼承和實現(xiàn) Readable 接口來創(chuàng)建袜刷。通過使用 interface 關鍵字提供的偽多重繼承機制,可以生成既是 RanddomDouble 又是 Readable 的新類:

import java.nio.CharBuffer;
import java.util.Scanner;

public class AdaptedRandomDoubles extends RandomDoubles implements Readable{
    private int count;
    public AdaptedRandomDoubles(int count){
        this.count = count;
    }

    public int read(CharBuffer cb){
        if(count-- == 0){
            return -1;
        }
        String result = Double.toString(next()) + " ";
        cb.append(result);
        return result.length();
    }

    public static void main(String[] args){
        Scanner s = new Scanner(new AdaptedRandomDoubles(47));
        while (s.hasNextDouble()){
            System.out.print(s.nextDouble() + " ");
        }
    }
}

9.7 接口中的域

因為放入接口的任何域都自動是 static 和 final 的莺丑,所以接口就成為了一中很便捷的用來創(chuàng)建常量組的工具著蟹。在沒有枚舉類型之前梢莽,經(jīng)常會看到如下代碼:

public interface Months{
  int JANUARY=1;
  int FEBRUARY = 2;
  ...
}

在接口中定義的域不是 “空 final”,但是可以被非常量表達式初始化

public interface RandVals {
    Random RANDOM = new Random(47);
    int RANDOM_INT = RANDOM.nextInt(47);
    long RANDOM_LONG = RANDOM.nextLong() * 10;
    float RANDOM_FLOAT = RANDOM.nextLong() * 10;
    double RANDOM_DOUBLE = RANDOM.nextDouble() * 10;
}

在接口中涮雷,也可以定義一些常量洪鸭,這些常量會被初始化览爵。

9.8 嵌套接口

class A{
    interface B{
        void f();
    }

    public class BImp implements B{
        @Override
        public void f() {

        }
    }

    public class BImp2 implements B{
        @Override
        public void f(){}
    }

    public interface C{
        void f();
    }

    public class CImp implements C{
        public void f(){}
    }

    private interface D{
        void f();
    }

    private class DImp implements D{
        @Override
        public void f() {

        }
    }

    public class DImp2 implements D{
        @Override
        public void f(){

        }
    }

    public D getD(){
        return new DImp();
    }

    private D dRef;

    public void  reveive(D d){
        dRef = d;
        dRef.f();
    }
}

interface E{
    interface G{
        void f();
    }

    public interface H{
        void f();
    }

    void g();

    // can not be private within an interface
//    private interface i {}
}


public class NestingInterfaces {
    public class BImp implements A.B{
        public void f() {}
    }

    class CImp implements A.C{
        public void f() {}
    }

    // can not implement a private interface 
//    class DImp implements A.D {}
}

從此例可以看出,interface 可以有 public 和 包訪問 兩種權(quán)限俱济。

9.9 接口與工廠

接口是實現(xiàn)多種集成的途徑蛛碌,而生成遵循某個接口的對象的典型方法就是工廠方法設計模式左医。這與直接調(diào)用構(gòu)造器不同,我們在工廠對象上調(diào)用的是創(chuàng)建方法彤路,而該工廠對象將生成接口的某個實現(xiàn)的對象远豺。理論上,通過這種方式惊来,我們的代碼將完全與接口的實現(xiàn)分離,這就使得我們可以透明地將某個實現(xiàn)替換稱為另一個實現(xiàn)继准。下面的實例展示了工廠方法的結(jié)構(gòu):

interface Service{
    void method1();
    void method2();
}

interface ServiceFactory{
    Service getService();
}

class Implementation1 implements Service{
    Implementation1() {}

    @Override
    public void method1() {
        System.out.print("Implementation1 method1");
    }

    @Override
    public void method2(){
        System.out.print("Implementation1 method2");
    }
}

class Implementation1Factory implements ServiceFactory{
    public Service getService(){
        return new Implementation1();
    }
}

class Implementation2 implements Service{
    Implementation2() {}

    @Override
    public void method1() {
        System.out.print("Implementation2 method1");
    }

    @Override
    public void method2() {
        System.out.print("Implementation2 method2");
    }
}

class Implementation2Factory implements ServiceFactory{
    public Service getService(){
        return new Implementation2();
    }
}

public class Factories {
    public static void serviceConsumer(ServiceFactory fact){
        Service s = fact.getService();
        s.method1();
        s.method2();
    }

    public static void main(String[] args){
        serviceConsumer(new Implementation1Factory());
        serviceConsumer(new Implementation2Factory());
    }
}

工廠方法:
先定義一個關于調(diào)用方法接口 Service,然后定義一個生成該調(diào)用方法接口的接口 ServiceFactory 崔泵。

隨后定義類實現(xiàn) Service, Implementation1。再定義類實現(xiàn)ServiceFactory, Implementation1Factory.

按照需要還可以定義類實現(xiàn) Implementation2 管削、 Implementation2Factory倒脓; Implementation3 、 Implementation3Factory ...

最后定義一個工廠類 Factory含思,以 serviceFactory 作為參數(shù)崎弃,實例化 Service ,這樣通過最后的 工廠類 Factory, 就可以調(diào)用任何實現(xiàn) serviceFactory 接口的數(shù)據(jù)了含潘。

應用場景
為什么需要這種間接性呢饲做?一個常見的原因就是想創(chuàng)建框架遏弱。比如現(xiàn)在需要創(chuàng)建一個棋類游戲漱逸,在相同的棋盤上可以下西洋棋和國際象棋

interface Game{
  boolean move();
}

interface GameFactory{
  Game getGame();
}

class Checkers implements Game{
  private int moves = 0;
  private final static int MOVES = 3;

  @Override
  public boolean move(){
    System.out.println("Checker move " + moves);
    return  ++moves != MOVES;
  }
} 

class CheckerFactory implements GameFactory{
  @Override
  public Game getGame(){
    return new Checkers();
  }
}

class Chess implements Game{
  private int moves = 0;
  private final static int MOVES = 4;
  
  @Override
  public boolean move{
      System.out.println("Chess move " + moves);
      return ++moves != MOVES;
  }

class ChessFactory implements GameFactory{
    
    @Override
    public Game getGame(){
      return new Chess();
  }
}

public class Games(){
  public void static playGame(GameFactory gameFactory){
    Game game = gameFactory.getGame();
    while(game.move());
  }

  public void static main(String[] args){
    playGame(new CheckerFactory);
    playGame(new ChessFactory);  
}
}

checker 是棋盤的意思,chess 是國際象棋的意思饰抒。這段代碼先定義了接口Game 肮砾、 GameFactory, 國際象棋和西洋棋分別實現(xiàn)了這兩個接口。這樣它們都可以被 Game.playGame調(diào)用袋坑。

9.10 總結(jié)

“確定接口是理想選擇仗处,因此應該總是選擇接口而不是具體的類”。這其實是一種引誘吃环。當然范咨,對于創(chuàng)建類权旷,幾乎在任何時刻译柏,都可以替代為創(chuàng)建一個接口和一個工廠(這個工廠是生成該接口的)胯府。

許多人都掉進了這種誘惑的陷阱寒波,只要有可能就去創(chuàng)建接口和工廠猴娩。這種邏輯看起來好像是因為需要使用不同的具體實現(xiàn)议忽,因此總是應該添加這種抽象性栈幸。這實際上已經(jīng)變成了一種草率的設計優(yōu)化。

任何抽象性都應該是應真正的需求產(chǎn)生的帮辟。當必須時速址,你應該重構(gòu)接口而不是到處添加額外級別的間接性,并由此帶來額外的復雜度由驹。

恰當?shù)脑瓌t是應該是優(yōu)先選擇類而不是接口芍锚。從類開始,如果接口的必需性變得非常明確蔓榄,那么就進行重構(gòu)并炮。接口是一種重要的工具,但是他們不應該被濫用甥郑。(我覺得作者的意思是針對接口容易被濫用的情況)

內(nèi)部類

內(nèi)部類可以將一個類的定義放在另一個類的定義內(nèi)部逃魄,這就是內(nèi)部類。

內(nèi)部類是非常有用的特性澜搅,因為它允許你把一些邏輯相關的類組織在一起伍俘,并且控制內(nèi)部類的可視性。

但是內(nèi)部類與組合是完全不同的概念店展。

在最初养篓,內(nèi)部類看起來就像是一種代碼隱藏機制,將類置于其他類的內(nèi)部赂蕴。但是柳弄,你將了解到,內(nèi)部類遠不止于此概说,

它了解外圍類碧注,并能與之通信,而且你使用內(nèi)部類寫出的代碼更加優(yōu)雅而清晰糖赔,盡管并不總是如此萍丐。

10.1 創(chuàng)建內(nèi)部類

創(chuàng)建內(nèi)部類的方式——把類的定義置于外圍類的里面.

class A{
    class B{
        
    }
    class C{
    
    }
}

外部有一個方法,該方法返回一個指向內(nèi)部類的引用放典。
使用時逝变,必須具體指明這個對象的類型基茵,OuterClassName.InnerClassName

package com.zzjack.rdsapi_demo.javathought;

public class Parcel3 {
    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;
        }
    }

    public Destination to(String s){
        return new Destination(s);
    }

    public Contents contents(){
        return new Contents();
    }

    public void ship(String dest){
        Contents c = contents();
        Destination d = to(dest);
        System.out.println(d.readLabel());
    }

    public static void main(String[] args){
        Parcel3 p = new Parcel3();
        p.ship("Tasmanina");
        Parcel3 q = new Parcel3();
        // 具體指明這個對象的類型,OuterClassName.InnerClassName
        // 外部類的方法中使用內(nèi)部類
        Parcel3.Contents c = q.contents();
        Parcel3.Destination d = q.to("Borneo");
    }
}

10.2 鏈接到外部類

到目前為止壳影,內(nèi)部類似乎還只是一種名字隱藏和組織代碼的模塊拱层。除此之外,外部類還有其他用途宴咧。

當生成一個內(nèi)部類的對象時根灯,此對象與制造它的外圍對象之間就有了一種聯(lián)系,所以它能訪問外圍對象的所有成員掺栅,而不需要任何特殊條件烙肺。

此外,內(nèi)部類還擁有其外圍類的所有元素的訪問權(quán)氧卧。

interface Selector{
    boolean end();
    Object current();
    void next();
}

public class Sequence{
    private Object[] items;
    private int next = 0;
    
    public void add(Object x){
        if(next < items.length){
            items[next++] = x;
        }
    }
    
    private class SequenceSelector iimplements Selector{
        private int i = 0;
        
        // 私有類可以調(diào)用外部 item
        public boolean end(){
            return i == items.length;
        }
        
        public Object current(){
            return items[i];
        }
        
        public void next(){
            if(i < items.length){
                i++;
            }
        }
    }
    
    public Selector selector(){
        return new SequenceSelector();
    }
    
    public static void main(String[] args){
        Sequence sequence = new Sequence(10);
        for(int i = 0; i < 10; i++){
            sequence.add(Integer.toString(i));
        }
        Selector selector =  sequence.selector();
        while(!selector.end()){
            System.out.println(selector.current() + " ");
            selector.next();
        }   
    }
}

Sequence 類只是一個固定大小的 Object 的數(shù)組桃笙,以類的形式包裝起來了〖俪可以調(diào)用add() 在序列末增加新的 Object.

要獲取 Sequence 中的每一個對象怎栽,可以使 Selector 接口。這是 “迭代器” 設計模式的一個例子宿饱。

Selector 允許你檢查序列是不是到了末尾(end()), 訪問當前對象(current()),以及移動到序列中的下一個對象(next())脚祟。

Selector 是一個接口谬以,所以別的類可以按照它們自己的方式來實現(xiàn)這個接口,并且別的方法能以此接口為參數(shù)由桌,來生成更加通用的代碼为黎。

這里,SequenceSelector 是提供 Selector 功能的 private 類行您∶可以看到,在 main() 中創(chuàng)建了一個Sequence, 并且向其中添加了一個

String 對象娃循。然后通過調(diào)用 selector() 獲取一個 Selector,并用它在 Sequence 中移動和選擇每一個元素炕檩。

最初看到 SequenceSelector, 可能覺得它只不過是另外一個內(nèi)部類捌斧。注意方法 end()/ current()/ next() 都用到了 objects,這是

一個引用笛质,它并不是 SequenceSelector 的一部分妇押,而是外圍類中的一個 private 字段敲霍。然而內(nèi)部可以訪問其外圍類的方法和字段潭袱,就

像自己擁有它們似的屯换,這帶來了很大的方便。

所以內(nèi)部類自動擁有對其外圍類所有成員的訪問權(quán)晕窑,這是如何做到的杨赤?當某個外圍類的對象創(chuàng)建了一個內(nèi)部類對象時疾牲,此內(nèi)部類對象必定會秘密地

捕捉一個指向那個外圍類對象的引用阳柔。然后舌剂,在你訪問此外圍類的成員時暑椰,就是那個引用來選擇外圍類的成員一汽。幸運的是角虫,編譯器會幫你處理所有的

細節(jié)均驶,但你現(xiàn)在可以看到:內(nèi)部類的對象只能在與其外圍類的對象相關聯(lián)的情況下才能被創(chuàng)建(就像你現(xiàn)在看到的爬虱,在內(nèi)部類是非 static 類時)。

構(gòu)建內(nèi)部類對象時瞒滴,需要一個指向其外圍類對象的引用妓忍,如果編譯器訪問不到這個引用就會報錯虏两。

10.3 使用 .this 與 .new

如果你需要生成對外部類對象的引用世剖,可以使用外部類的名字后面緊跟圓點和 this.這樣產(chǎn)生的引用自動地具有正確的類型。

在內(nèi)部類引用外圍類旁瘫,需要使用 .this。

public class DotThis{
    void f(){
        System.out.println("DotThis.f()");
    }
    
    public class Inner{
        public DotThis outer(){
            return DotThis.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();
    }
}

有時候你需要告知某些其他對象酬凳,去創(chuàng)建其某個內(nèi)部類的對象惠况。要實現(xiàn)此目的粱年,你必須在 new 表達式

中提供對其他外部類對象的引用台诗,這是需要使用 .new 語法:

public class DotNew{
    public class Inner{}
    
    public static void main(String[] args){
        DotNew dn = new DotNew();
        // 外部類實例創(chuàng)建內(nèi)部類實例
        DotNew.Inner inner = dn.new Inner();
    }
}

要想直接創(chuàng)建內(nèi)部類的對象阻逮,不能去引用外部類的名字 DotNew,而是必須使用外部類的實例來創(chuàng)建

內(nèi)部類的實例事哭,就像上面程序看到的那樣,dn.new瓜富。這也解決了內(nèi)部類名字作用域的問題鳍咱。

在擁有外部類實例之前是不可能創(chuàng)建內(nèi)部類實例的。這是因為內(nèi)部類實例會暗暗地連接到創(chuàng)建它的

外部類實例上与柑。但是谤辜,如果你創(chuàng)建的是嵌套類(靜態(tài)內(nèi)部類)蓄坏,那么它不需要對外部類對象的引用。

10.4 內(nèi)部類與向上引用

當將內(nèi)部類向上轉(zhuǎn)型為其基類丑念,尤其是轉(zhuǎn)型為一個接口時涡戳,內(nèi)部類就有了用武之地。

(從實現(xiàn)了某個接口的對象脯倚,得到對此接口的引用渔彰,與向上轉(zhuǎn)型為這個對象的基類,實際上效果是一樣的推正。)

這是因此內(nèi)部類——某個接口的實現(xiàn)——能夠完全不可見恍涂,并且不可用,所得到的只是指向基類或接口的引用舔稀。所以能夠
很方便地隱藏細節(jié)乳丰。

接口一,Destination

public interface Destination{
    String readLabel();
}

接口二内贮,Contents

public interface Contents{
    int value();
}

內(nèi)部類产园,指向接口的引用

class Parcel4{
    private class PContents implements Contents{
        private int i = 11;
        public int value(){
            return i;
        }
    }
    
    protected class PDestination implements Destination{
        private String label;
        private PDestination(String whereTo){
            label = whereTo;
        }
        public String readLabel(){
            return label;
        }
    }
    
    public Contents contents(){
        return new PContents();
    }
}

public class TestParcel{
    public static void main(String[] args){
        Parcel4 p = new Parcel4();
        Contents c = p.contents();
        Destination d = p.destination(Tasmania");
    }
}

Parcel4 中增加了一些新東西:內(nèi)部類 PContents 是 private,所以除了 Parcel4,

沒有人能訪問它夜郁。PDestinantion 是 protected什燕,所以只有 Parcel4 及其子類,還有與

Parcel4 同一個包中的類(因為 protected 也給予了包訪問權(quán)) 能訪問 PDestination,其他類都不能

訪問 PDestination竞端。這意味著屎即,如果客戶端程序員想了解或訪問這些成員,那是要受到限制的事富。實際上技俐,

甚至不能向下轉(zhuǎn)型成 private 內(nèi)部類(或 protected 內(nèi)部類,除非是繼承自它的子類)统台,因此不能訪問其名字雕擂。

就像在 TestParcel 中看到的那樣。于是贱勃,private 內(nèi)部類給類的設計者提供了一種途徑井赌,通過這種方式可以完全阻止

任何依賴于類型的編碼,并且完全隱藏了實現(xiàn)的細節(jié)贵扰。

10.5 在方法和作用域內(nèi)的內(nèi)部類

如果所讀仇穗、寫的代碼包含了內(nèi)部類,那么它們都是平凡的內(nèi)部類戚绕,比較簡單纹坐。

還有一些比較復雜,比如可以在一個方法里面或在任意的作用域內(nèi)定義內(nèi)部類列肢。這么做的兩個理由:

  • 如前所示恰画,你實現(xiàn)了某類型的接口宾茂,于是可以創(chuàng)建并返回對其的引用。

  • 你要解決一個復雜的問題拴还,想創(chuàng)建一個類來輔助你的解決方案跨晴,但是又不希望這個類是公共可用的。

還有一些其他用途的內(nèi)部類:

  • 一個定義在方法中的類片林,這個被稱為局部內(nèi)部類端盆。

  • 一個定義在作用域內(nèi)的類,此作用域在方法的內(nèi)部费封。

  • 一個實現(xiàn)了接口的內(nèi)部類

  • 一個匿名類焕妙,它擴展了有非默認構(gòu)造器的類。

  • 一個匿名類弓摘,它執(zhí)行字段初始化

  • 一個匿名類焚鹊,它通過實例初始化實現(xiàn)構(gòu)造(匿名類不可能有構(gòu)造器)

public class Parcel5{
    
    public Destination destination(String s){
        class PDestination implements Destination{
            private String label;
            private PDestination(String whereTo){
                label = whereTO;
            }
            
            public String readLabel(){
                return label;
            }
        }
        return new pDestination();
    }
    
    public static void main(String[] args){
        Parcel5 p = new Parcel5();
        Destination d = p.destination("Tasmania");
    }
}

PDestination 類是 destination() 方法的一部分,而不是 Parcel5 的一部分韧献。

所以末患,在 destination() 之外不能訪問 PDestination。注意出現(xiàn)在 return 語句中的

向上轉(zhuǎn)型——返回的是 Destination 的引用锤窑,它是 PDestination 的基類璧针。當然,在

destination() 中定義了內(nèi)部類 PDestination, 并不意味著一旦 dest() 方法執(zhí)行完畢渊啰,

PDestination 就不可用了探橱。

你可以在同一個子目錄下的任意類中對某個內(nèi)部類使用類標識符 PDestination, 這并不會有

命名沖突。下面的例子展示了如何在任意的作用域內(nèi)嵌入一個內(nèi)部類:

public class Parcel6{
    private void internalTracking(boolean b){
        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();
        }
    }
    public void track(){
        internalTrakcing(true);
    }
    public static void main(String[] args){
        Parcel6 p = new Parcel6();
        p.track();
    }
}

TrackingSlip 類被嵌入在 if 語句的作用域內(nèi)绘证,這并不是說該類的創(chuàng)建是有條件的隧膏,它其實與別的類

一起編譯過了。然而嚷那,在定義 TrackingSlip 的作用域之外私植,它是不可用的,除此之外车酣,它與普通類一樣。

10.6 匿名內(nèi)部類

public class Parcel7{
    public Contents contents(){
        return new Contents(){
            private int i = 11;
            public int value(){
                return i;
            }
        };
    }
    
    public static void main(String[] args){
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
    }
}

contents() 方法將返回值的生成與表示這個返回值的定義結(jié)合在一起索绪。

另外這個類是匿名的湖员,它沒有名字。更糟的是瑞驱,看起來似乎是你想要創(chuàng)建一個 Contents 對象娘摔。

但是然后(在到達語句結(jié)束的分號之前)你卻說,“等一等唤反,我想在這里插入一個類的定義”

這種奇怪的語法指的是:“創(chuàng)建一個繼承自 Contents 的匿名類的對象凳寺⊙冀颍”通過 new 表達式返回的

引用被自動向上轉(zhuǎn)型為對 Contents 的引用。上述匿名內(nèi)部類的語法是下述形式的簡化表達:

class Parcel7b{
    class MyContents implements Contents{
        private int i = 11;
        public int value(){
            return i;
        }
    }

    public Contents contents(){
        return new MyContents();
    }

    public static void main(String[] args){
        Parcel7b p = new Parcel7b();
        Contents c = p.contents();
    }
}

如果匿名類的構(gòu)造器需要攜帶參數(shù), 直接傳遞參數(shù)就好了肠缨。

注意此時的 super

class Wrapping{
    private int i;

    public Wrapping(int x){
        i = x;
    }

    public int value(){
        return i;
    }
}


public class Parcel8 {
    public Wrapping wrapping(int x){
        return new Wrapping(x){
            public int value(){
                return super.value() * 47;
            }
        };
    }
    
    public static void main(String[] args){
        Parcel8 p = new Parcel8();
        Wrapping w = p.wrapping(10);
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逆趋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子晒奕,更是在濱河造成了極大的恐慌闻书,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脑慧,死亡現(xiàn)場離奇詭異魄眉,居然都是意外死亡,警方通過查閱死者的電腦和手機闷袒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門坑律,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人囊骤,你說我怎么就攤上這事晃择。” “怎么了淘捡?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵藕各,是天一觀的道長。 經(jīng)常有香客問我焦除,道長激况,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任膘魄,我火速辦了婚禮乌逐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘创葡。我一直安慰自己浙踢,他們只是感情好,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布灿渴。 她就那樣靜靜地躺著洛波,像睡著了一般。 火紅的嫁衣襯著肌膚如雪骚露。 梳的紋絲不亂的頭發(fā)上蹬挤,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音棘幸,去河邊找鬼焰扳。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的吨悍。 我是一名探鬼主播扫茅,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼育瓜!你這毒婦竟也來了葫隙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤爆雹,失蹤者是張志新(化名)和其女友劉穎停蕉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钙态,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡慧起,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了册倒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚓挤。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖驻子,靈堂內(nèi)的尸體忽然破棺而出灿意,到底是詐尸還是另有隱情,我是刑警寧澤崇呵,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布缤剧,位于F島的核電站,受9級特大地震影響域慷,放射性物質(zhì)發(fā)生泄漏荒辕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一犹褒、第九天 我趴在偏房一處隱蔽的房頂上張望抵窒。 院中可真熱鬧,春花似錦叠骑、人聲如沸李皇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掉房。三九已至,卻和暖如春慰丛,著一層夾襖步出監(jiān)牢的瞬間圃阳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工璧帝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人富寿。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓睬隶,卻偏偏與公主長得像锣夹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子苏潜,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容