1. 程式人生 > >《Java程式設計思想》第10章 內部類

《Java程式設計思想》第10章 內部類

書中原始碼:https://github.com/yangxian1229/ThinkingInJava_SourceCode
可以將一個類的定義放在另一個類的定義內部,這就是內部類。
內部類與組合是完全不同的概念。

10.1 建立內部類

把類的定義置於外圍類的裡面。更典型的情況是,外部類有一個方法,該方法返回一個指向內部類的引用,就像在to()和contents()方法中看到的那樣。

//: innerclasses/Parcel2.java
// Returning a reference to an inner class.

public class Parcel2 {
  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) {
    Parcel2 p = new Parcel2();
    p.ship("Tasmania");
    Parcel2 q = new Parcel2();
    // Defining references to inner classes:
    Parcel2.Contents c = q.contents();//具體指明這個物件的型別
    Parcel2.Destination d = q.to("Borneo");
  }
} /* Output:
Tasmania
*///:~

如果想從外部類的非靜態方法之外的任意位置建立某個內部類的物件,那麼必須像在main()方法中那樣,具體地指明這個物件的型別:OuterClassName.InnerClassName。

10.2 連結到外部類

內部類擁有其外圍類的所有元素的訪問權。

//: innerclasses/Sequence.java
// Holds a sequence of Objects.

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

public class Sequence {
  private Object[] items;
  private int next = 0;
  public Sequence(int size) { items = new Object[size]; }
  public void add(Object x) {
    if(next < items.length)
      items[next++] = x;
  }
  private class SequenceSelector implements Selector {//內部類
    private int i = 0;
    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.print(selector.current() + " ");
      selector.next();
    }
  }
} /* Output:
0 1 2 3 4 5 6 7 8 9
*///:~

10.3 使用.this與.new

如果需要生成對外部類物件的引用,可以使用外部類後面緊跟圓點和this

//: innerclasses/DotThis.java
// Qualifying access to 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();
  }
} /* Output:
DotThis.f()
*///:~

有時你可能想要告知某些其他物件,去建立其某個內部類的物件。則必須在new表示式中提供對其外部類的引用,這是需要使用.new語法。

//: innerclasses/DotNew.java
// Creating an inner class directly using the .new syntax.

public class DotNew {
  public class Inner {}
  public static void main(String[] args) {
    DotNew dn = new DotNew();
    DotNew.Inner dni = dn.new Inner();//*************************
  }
} ///:~

要想直接建立內部類的物件,不能引用外部類的名字DotNew,而是必須使用外部類的物件來建立內部類物件。在擁有外部類物件之前是不可能建立內部類物件的。這是因為內部類物件會暗暗地連結到建立它的外部類物件上。但是,如果你建立的是巢狀類(靜態內部類),那麼它就不需要對外部類物件的引用。

10.4 內部類與向上轉型

當將內部類向上轉型為其基類,尤其是轉型為一個介面的時候,內部類就有了勇武之地。這個是因為此內部類——某個介面的實現——能夠完全不可見,並且不可用。所得到的只是指向基類或介面的引用,所以能夠很方便地實現隱藏。
//兩個介面

//: innerclasses/Destination.java
public interface Destination {
  String readLabel();
} ///:~
//: innerclasses/Contents.java
public interface Contents {
  int value();
} ///:~
//: innerclasses/TestParcel.java

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 Destination destination(String s) {//向上轉型
    return new PDestination(s);
  }
  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");//向上轉型
    // Illegal -- can't access private class:
    //! Parcel4.PContents pc = p.new PContents();
  }
} ///:~

10.5 在方法和作用域內的內部類

在方法的作用域內建立一個完整的類。這被稱為區域性內部類

//: innerclasses/Parcel5.java
// Nesting a class within a method.

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(s);
  }
  public static void main(String[] args) {
    Parcel5 p = new Parcel5();
    Destination d = p.destination("Tasmania");
  }
} ///:~

PDestination類是destination()方法的一部分,而不是Parcel5的一部分。所以,在destination()之外不能訪問PDestination
你可以在同一子目錄下的人以類中對某個內部類使用類識別符號PDestination,這並不會有命名衝突。
你可以在任意的作用域內嵌入一個內部類。在作用域之外,它是不可用的,除此之外,它與普通類一樣。

10.6 匿名內部類

//: innerclasses/Parcel7.java
// Returning an instance of an anonymous inner class.

public class Parcel7 {
  public Contents contents() {
    return new Contents() { // Insert a class definition************
      private int i = 11;
      public int value() { return i; }
    }; // Semicolon required in this case*************
  }
  public static void main(String[] args) {
    Parcel7 p = new Parcel7();
    Contents c = p.contents();
  }
} ///:~

建立一個繼承自Contents的匿名類的物件。通過new表示式返回的引用被自動向上轉型為對Contents的引用。上述匿名內部類的語法是下述形式的簡化形式:

//: innerclasses/Parcel7b.java
// Expanded version of Parcel7.java

public 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();
  }
} ///:~

在這個匿名內部類中,使用了預設的構造器來生成Contents。下面程式碼展示的是,如果你的基類需要一個有引數的構造器,應該怎麼辦:

//: innerclasses/Parcel8.java
// Calling the base-class constructor.

public class Parcel8 {
  public Wrapping wrapping(int x) {
    // Base constructor call:
    return new Wrapping(x) { // Pass constructor argument.
      public int value() {
        return super.value() * 47;
      }
    }; // Semicolon required
  }
  public static void main(String[] args) {
    Parcel8 p = new Parcel8();
    Wrapping w = p.wrapping(10);
  }
} ///:~

//: innerclasses/Wrapping.java
public class Wrapping {
  private int i;
  public Wrapping(int x) { i = x; }
  public int value() { return i; }
} ///:~

只需簡單地傳遞合適的引數給基類的構造器即可,這裡是將x傳進new Wrapping(x)。儘管Wrapping只是一個具有具體實現的普通類,但它還是被其匯出類當作公共“介面”來使用。
在匿名內部類末尾的分號,並不是用來標記此內部類結束的。實際上,它標記的是表示式的結束,只不過這個表示式正巧包含了匿名內部類罷了。因此,這與別的地方使用的分號是一致的。
在匿名類中定義欄位時,還能夠對其執行初始化操作:

//: innerclasses/Parcel9.java
// An anonymous inner class that performs
// initialization. A briefer version of Parcel5.java.

public class Parcel9 {
  // Argument must be final to use inside
  // anonymous inner class:
  public Destination destination(final String dest) {
    return new Destination() {
      private String label = dest;
      public String readLabel() { return label; }
    };
  }
  public static void main(String[] args) {
    Parcel9 p = new Parcel9();
    Destination d = p.destination("Tasmania");
  }
} ///:~

如果定義一個匿名內部類,並且希望它使用一個在其外部定義的物件,那麼編譯器會要求其引數引用是final的,就像你在destination()的引數中看到的那樣。
如果只是簡單地給一個欄位賦值,那麼此例中的方法是很好的。但是,如果想做一些類似構造器的行為,該怎麼辦呢?在匿名類中不可能有命名構造器(一位它根本就沒有名字),但通過例項初始化,就能夠達到為匿名內部類建立一個構造器的效果,就像這樣:

//: innerclasses/AnonymousConstructor.java
// Creating a constructor for an anonymous inner class.
import static net.mindview.util.Print.*;

abstract class Base {
  public Base(int i) {
    print("Base constructor, i = " + i);
  }
  public abstract void f();
}	

public class AnonymousConstructor {
  public static Base getBase(int i) {
    return new Base(i) {
      { print("Inside instance initializer"); }
      public void f() {
        print("In anonymous f()");
      }
    };
  }
  public static void main(String[] args) {
    Base base = getBase(47);
    base.f();
  }
} /* Output:
Base constructor, i = 47
Inside instance initializer
In anonymous f()
*///:~

在此例中,不要求變數i一定是final的。因為i被傳遞給匿名類的基類構造器,它並不會在匿名類內部被直接使用。

10.6.1 再訪工廠方法

//: innerclasses/Factories.java
import static net.mindview.util.Print.*;

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

interface ServiceFactory {
  Service getService();
}	

class Implementation1 implements Service {
  private Implementation1() {}
  public void method1() {print("Implementation1 method1");}
  public void method2() {print("Implementation1 method2");}
  public static ServiceFactory factory =
    new ServiceFactory() {
      public Service getService() {
        return new Implementation1();
      }
    };
}	

class Implementation2 implements Service {
  private Implementation2() {}
  public void method1() {print("Implementation2 method1");}
  public void method2() {print("Implementation2 method2");}
  public static ServiceFactory factory =
    new 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(Implementation1.factory);
    // Implementations are completely interchangeable:
    serviceConsumer(Implementation2.factory);
  }
} /* Output:
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
*///:~

現在用於Implementation1Implementation2的構造器都可以是private的,並且沒有任何必要去建立作為工廠的具名類。另外,你經常只需要單一的工廠物件,因此在本例中他被建立為Serviec實現中的一個static域。

10.7 巢狀類

如果不需要內部類物件與其外圍類物件之間有聯絡,那麼可以將內部類宣告為static。者通常稱為巢狀類。想要理解static應用於內部類時的含義,就必須記住,普通的內部類物件隱式地儲存了一個引用,指向建立它的外圍類物件。然而,當內部類是static的時候,就不是這樣了。巢狀類意味著:
1)要建立巢狀類的物件,並不需要其外圍類的物件。
2)不能從巢狀類的物件中訪問非靜態的外圍類物件。
巢狀類與普通的內部類還有一個區別。普通內部類的欄位與方法,只能放在類的外部層次上,所以普通的內部類不能有static資料和static欄位,也不能包含巢狀類。但是巢狀類可以包含所有這些東西。巢狀類沒有**.this這個特殊引用,這使得它類似於一個static**方法。

10.7.1 介面內部的類

你放到介面中的任何類都自動的是publicstatic的。

10.7.2 從多層巢狀類中訪問外部類的成員

一個內部類被巢狀多少層並不重要——它能透明的訪問所有它所嵌入的外圍類的所有成員。

10.8 為什麼需要內部類

每個內部類都能獨立地繼承自一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。
必須在一個類中以某種方式實現兩個介面。由於介面的靈活性,可以有兩種選擇:使用單一類,或者使用內部類。//: innerclasses/MultiInterfaces.java
如果擁有的是抽象的類或者具體的類,而不是介面,那就只能使用內部類才能實現多重繼承。//: innerclasses/MultiImplementation.java

10.8.1 閉包與回撥

閉包是一個可呼叫的物件,它記錄了一些資訊,這些資訊來自於建立它的作用域。

10.8.2 內部類與控制框架

(看完22章,再回來看)

10.9 內部類的繼承

//: innerclasses/InheritInner.java
// Inheriting an inner class.

class WithInner {
  class Inner {}
}

public class InheritInner extends WithInner.Inner {//。。。。
  //! InheritInner() {} // Won't compile
  InheritInner(WithInner wi) {
    wi.super();
  }
  public static void main(String[] args) {
    WithInner wi = new WithInner();
    InheritInner ii = new InheritInner(wi);
  }
} ///:~