Java編程思想學習錄(連載之:內部類)


Thinking in java系列博文目錄:java

注: 本文首發於 My 公衆號 CodeSheep ,可 長按掃描 下面的 當心心 來訂閱 ↓ ↓ ↓編程

CodeSheep · 程序羊


內部類基本概念

  • 可將一個類的定義置於另外一個類定義的內部
  • 內部類容許將邏輯相關的類組織在一塊兒,並控制位於內部的類的可見性
  • 甚至可將內部類定義於一個方法或者任意做用域內!
  • 固然,內部類 ≠ 組合
  • 內部類擁有其外圍類 全部元素的 訪問權
  • 更有甚,嵌套多層的內部類能透明地訪問全部它所嵌入的外圍類的全部成員

一個典型的例子:利用 Java內部類 實現的 迭代器模式bash

// 接口
-------------------------------------------------------------
public 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;
  } 

  // 迭代器類:實現了 Selector接口的 內部類
  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(); // 獲取iterator!
    while( !selector.end() ) {
      print( selector.current() + " " );
      selector.next();
    }
  }
}
// 輸出
-------------------------------------------------------------
0 1 2 3 4 5 6 7 8 9
複製代碼

.this 與 .new 的使用場景

.this用於在內部類中生成對其外部類對象的引用之時,舉例:ide

public class DotThis {
  
  void f() { print("DotThis.f()"); }
  
  public class Inner { // 內部類
    public DotThis outer() { // 返回外部類對象的引用
      return DotThis.this; // 若直接返回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.f()
  }
}
複製代碼

.new用於直接建立內部類的對象之時,距離:函數

public class DotNew {
  public class Inner { } // 空內部類
  public static void main( String[] args ) {
    DotNew dn = new DotNew();
    DotNew.Inner dni = dn.new Inner(); //注意此處必須使用外部類的對象,而不能直接 DotNew.Inner dni = new DotNew.Inner()
  }
}
複製代碼

嵌套類(static類型的內部類)

嵌套類是無需依賴其外部類的對象的。非static內部類經過一個特殊的this連接到其外圍類的對象,而static類型的內部類無此this引用。學習

接口與內部類有着頗有趣的關係: 放到接口中的任何類自動都是public且static,即接口中的任何類都是嵌套類,咱們甚至能夠在接口的內部類中去實現其外圍接口,舉例:ui

public interface ClassInInterface {
  void howdy();
  class Test implements ClassInInterface { // 類Test默認static,因此是嵌套類
    public void howdy() {
      print("Howdy!");
    }
    public static void main( String[] args ) {
      new Test().howdy(); 
    }
  }
}
複製代碼

方法做用域 內的內部類

能夠稱這類爲 局部內部類this

方法中定義的內部類只能在方法內被使用,方法以外不可訪問,舉例:spa

public class Parcel {  // parcel是「包裹」之意
  
  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 ); // 只有在方法中才能訪問內部類PDestination
  }

  public static void main( String[] args ) {
    Parcel p = new Parcel();
    Destination d = p.destination( "Hello" );
    ...
  }
}
複製代碼

更進一步,可在任意做用域內定義內部類,舉例:code

public class Parcel {
  
  private void internalTracking( boolean b ) {
    
    if( b ) { // 局部做用域中定義了內部類,做用域以外不可訪問!
      class TrackingSlip {
        private String id;
        TrackingSlip( String s ) { id = s; }
        String getSlip() { return id; }
      }
    }

  }
  
  public void track() { interTracking( true ); }

  public static void main( String[] args ) {
    Parcel p = new Parcel();
    p.track();
  }
}
複製代碼

匿名內部類

直觀上看,這種內部類沒有「名字」,舉例:

public class Parcel {
  
  public Contents contents() {
    return new Contents() { // 此即匿名內部類!!!
      private int i = 11;
      public int value() { return i; }
    }; // !!!注意這裏必需要加分號!!!
  }

  public static void main( String[] args ) {
    Parcel p = new Parcel();
    Contents c = p.contents();
  }
}
複製代碼

若想將外部的參數傳到匿名內部類中(典型的如將外部參數用於對匿名內部類中的定義字段進行初始化時)使用的話,該參數必須final,舉例:

public class Parcel {
  
  public Destination destination( final String s ) { // final必須!
    return new Destination() {
      private String label = s;
      public String readLabel() { return label; }
    }; // 分號必須!
  }

  public static void mian( String[] args ) {
    Parcel p = new Parcel();
    Destination d = p.destination("Hello");
  }
}
複製代碼

匿名內部類中不可能有命名的顯式構造器,此時只能使用實例初始化的方式來模仿,舉例(固然下面這個例子還反映了匿名內部類如何參與繼承):

// 基類
---------------------------------------------
abstact class Base() {
  public Base( int i ) {
    print( "Base ctor, i = " + i );
  }
  public abstract void f();
}

//主類(其中包含了繼承上面Base的派生匿名內部類!)
----------------------------------------------
public class AnonymousConstructor {
  
  public static Base getBase( int i ) { // 該處參數無需final,由於並未在下面的內部類中直接使用!
    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();
  }
}

// 輸出
------------------------------------------
Base ctor, i = 47 // 先基類
Inside instance initializer // 再打印派生類
In anonymous f()
複製代碼

匿名內部類 + 工廠模式 = 更加簡潔易懂:

// Service接口
---------------------------------------------------
interface Service {
  void method1();
  void method2();
}
// ServiceFactory接口
---------------------------------------------------
interface ServiceFactory {
  Service getService();
}
// Service接口的實現
---------------------------------------------------
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 ) {
    serviceComsumer( Implementation1.factory );
    serviceComsumer( Implementation2.factory );
  }
}
複製代碼

總結:爲何須要內部類

內部類能夠獨立地繼承自一個接口或者類而無需關注其外圍類的實現,這使得擴展類或者接口更加靈活,控制的粒度也能夠更細!

注意Java中還有一個細節:雖然Java中一個接口能夠繼承多個接口,可是一個類是不能繼承多個類的!要想完成該特性,此時除了使用內部類來「擴充多重繼承機制」,你可能別無選擇,舉例:

class D { }               // 普通類
abstract class E { }      // 抽象類

class Z extend D {    // 外圍類顯式地完成一部分繼承
  E makeE() {
    return new E() { }; // 內部類隱式地完成一部分繼承
  }
}

public class MultiImplementation {
  static void takesD( D d ) { }
  static void takesE( E e ) { }
  public static void main( String[] args ) {
    Z z = new Z();
    takesD( z );
    takesE( z.makeE() );
  }
}
複製代碼
相關文章
相關標籤/搜索