Java編程思想: 內部類

內部類的基礎知識 

內部類的定義java

將一個類Inner的定義放在另外一個類Outer的定義內部.  則Inner的具體類型爲Outer.Inner閉包

若是要引用Inner類型, 咱們須要Outer.Inner, 即在類層次上, Inner是寄生於Outer的, 任何關於Inner的操做(如構造, 調用其方法)都須要經過Outer的實例對象生成一個Inner的對象(這樣才能跟Outer.Inner類型關聯起來)來進行操做.ide

public class Outer {
	public String s;
	Outer(String s) {
		this.s = s;
	}
	class Inner {
		public void show() {
			System.out.println(s);
			System.out.println("Inner show");
		}
	}
	public Inner inner() {
		return new Inner();
	}
	public static void main(String[] args) {
		Outer o = new Outer("???");
		Outer.Inner i = o.inner();
		i.show();
		System.out.println(o.s);
		
		// ERROR
//		Outer.Inner i1 = new Outer.Inner();

		Outer.Inner i1 = o.new Inner();
		i1.show();
	}
}

就像是任何類方法內部可使用this來調用此類的全部成員同樣. 內部類也一樣使用隱式的"this指針"來訪問外部類的全部成員,而不須要任何特殊條件(相似動態語言如Python,JavaScript的閉包原理).測試

.this與.new優化

若是要生成對外部類的引用, 須要.thisthis

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();
  }
}

這裏不能使用return DotThis. 是由於DotThis是一個類, 而DotThis.this是一個引用對象,指的是當前內部類所引用的外部對象.spa

若是要建立內部類的對象, 則須要.new. 在擁有外部類對象以前不可能建立內部類對象的,這是由於內部類對象會暗暗的鏈接到建立它的外部類對象上.設計

public class DotNew {
  public class Inner {
    public void show() {
      System.out.println("Inner show");
    }
  }
  public static void main(String[] args) {
    DotNew dn = new DotNew();
    DotNew.Inner dni = dn.new Inner();
    dni.show();
  }
}

 

內部類與向上轉型指針

內部類的一個用途在於: 實現一個接口. 這樣內部類可向上轉型爲一個接口的對象:code

interface A {
  String toString();
}
public class C {
  private class B implements A {
    public String toString() {
      return getClass().getName();
    }
  }
  public static void main(String[] args) {
    C c = new C();
    C.B b = c.new B();
    System.out.println(b);
  }
}

這種設計符合組合思想,接口的實現類成爲具體類的內部對象,從而很好的隱藏其實現細節.

 

內部類的使用

定義在方法中的類/定義在做用域中的類

定義在方法或做用域中的類, 主要爲了解決如下狀況: 方法/做用域的邏輯過於複雜, 咱們須要建立一個類來輔助解決, 但又不但願這個類是公共可用的.

如下筆記主要基於下例幾點:

1. 一個定義在方法中的類.

2. 一個定義在做用域內的類,此做用域在方法的內部.

3. 一個實現了接口的匿名類.

4. 一個匿名類,它擴展了有非默認構造器的類.

5. 一個匿名類,它執行字段初始化.

6. 一個匿名類,它經過實例初始化實現構造.

定義在方法中的類

interface A {
  String toString();
}
public class C {
  public A show() {
    class B implements A {
      public String toString() {
        return getClass().getName();
      }
    }
    return new B();
  }
  public static void main(String[] args) {
    C c = new C();
    // C$1B
    System.out.println(c.show());
  }
}

定義在做用域內中的內部類

interface A {
  String toString();
}
public class C {
  public String show(boolean b) {
    if (b) {
      class B implements A {
        public String toString() {
          return getClass().getName();
        }
      }
      A a = new B();
      return a.toString();
    } else {
      return "error";
    }
  }
  public static void main(String[] args) {
    C c = new C();
    System.out.println(c.show(true));
    System.out.println(c.show(false));
  }
}

 

匿名內部類

interface Contents {
  int value();
}
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();
    System.out.println(c.value());
  }
}

對於匿名內部類的語法解析以下: new表明新建一個對象, 調用的是Contents()構造器, 其後增長的是類的實際定義.

備註: 這跟動態語言, 如Python/JavaScript的閉包同樣.

上述的匿名內部類語法就是下述形式的簡化形式:

interface Contents {
	int value();
}
public class Parcel7 {
	class MyCOntents implements Contents {
		private int i = 11;
		@Override
		public int value() {
			return i;
		}
	}

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

	public static void main(String[] args) {
		Parcel7 p = new Parcel7();
		Contents c = p.contents();
		System.out.println(c.value());
	}
}

 

若是匿名內部類使用了外部的參數, 那麼其參數必須命名爲final禁止被修改. 而在匿名類中一樣可使用實例初始化(即下例代碼中大括號{}部分)來達到相似構造器的效果:

interface Contents {
  int value();
}
public class Parcel7 {
  public Contents contents(final int value) {
    return new Contents() {
      private int i;
      {
        i = value;
      }
      public int value() { return i; }
    };
  }
  public static void main(String[] args) {
    Parcel7 p = new Parcel7();
    Contents c = p.contents(42);
    System.out.println(c.value());
  }
}

使用匿名內部類能夠優化其工廠方法:

interface Service {
  void method1();
  void method2();
}
interface ServiceFactory {
  Service getService();
}
class Implementation1 implements Service {
  private Implementation1() {}
  public void method1() {
    System.out.println("Implementation1 method1.");
  }
  public void method2() {
    System.out.println("Implementation1 method2.");
  }
  public static ServiceFactory factory =
    new ServiceFactory() {
      @Override
      public Service getService() {
        return new Implementation1();
      }
    };
}
class Implementation2 implements Service {
  private Implementation2() {}
  public void method1() {
    System.out.println("Implementation2 method1.");
  }
  public void method2() {
    System.out.println("Implementation2 method2.");
  }
  public static ServiceFactory factory =
    new ServiceFactory() {
      @Override
      public Service getService() {
        return new Implementation2();
      }
    };
}
public class Factories {
  public static void serviceConsume(ServiceFactory fact) {
    Service s = fact.getService();
    s.method1();
    s.method2();
  }
  public static void main(String[] args) {
    serviceConsume(Implementation1.factory);
    serviceConsume(Implementation2.factory);
  }
}

 

嵌套類

使用static聲明的內部類爲嵌套類, 及它跟外圍類的實例對象並無任何的關聯.

當內部類爲static時,意味着:

1. 要建立嵌套類的對象, 並不須要其外圍類的對象.

2. 不能從嵌套類的對象中訪問非靜態的外圍類對象.

interface A {
  String toString();
}
public class C {
  public static class B implements A {
    public String toString() {
      return getClass().getName();
    }
  }
  public static void main(String[] args) {
    A a = new C.B();
    System.out.println(a);
  }
}

假設咱們須要在接口中編寫通用的公用代碼, 用於不一樣接口實現的類所公用, 那麼在接口中內嵌類是很是好的方法:

interface A{
  void howdy();
  class Test implements A{
    public void howdy() {
      System.out.println("Howdy");
    }
    public static void main(String[] args) {
      new Test().howdy();
    }
  }
}

public class ClassInInterface implements A{
  public void howdy() {}
  public static void main(String[] args) {
    A.Test t = new A.Test();
    t.howdy();
  }
}

咱們一般在類中編寫main來測試這個類. 若是嫌麻煩咱們可使用嵌套類來實現測試代碼:

public class TestBed {
  public void f() {
    System.out.println("f()");
  }
  public static class Tester {
    public static void main(String[] args) {
      TestBed t = new TestBed();
      t.f();
    }
  }
}

執行: java TestBed$Tester便可測試.

 

爲何要使用內部類

主要緣由在於: 每一個內部類都能獨立的繼承自一個(接口的)實現, 因此不管外圍類是否已經繼承了某個(接口的)實現, 對於內部類都沒有影響.

針對多重繼承, 接口只解決了部分問題, 內部類使之獲得完善.

考慮如下場景: 即必須在一個類中以某種方式實現兩個接口. 這時候, 咱們有兩個選擇: 要麼使用單一類(所有implements兩個接口), 要麼使用內部類:

interface A {
	void show();
}
interface B {
	void func();
}

class X implements A, B {
	public void show() {
		System.out.println("X show");
	}
	public void func() {
		System.out.println("X func");
	}
}

class Y implements A {
	public void show() {
		System.out.println("Y show");
	}
	B makeB() {
		// 返回一個匿名類
		return new B() {
			public void func() {
				System.out.println("Y func");
			}
		};
	}
}
public class MultiInterfaces {
	static void takeA(A a) {
		a.show();
	}
	static void takeB(B b) {
		b.func();
	}
	public static void main(String[] args) {
		X x = new X();
		Y y = new Y();
		takeA(x);
		takeA(y);
		takeB(x);
		takeB(y.makeB());
	}
}

但若是擁有抽象類或具體類,而不是接口, 則只能使用內部類才能實現多重繼承.

interface A {
	void show();
}
abstract class B {
	abstract void func();
}

class Y implements A {
	public void show() {
		System.out.println("Y show");
	}
	B makeB() {
		// 返回一個匿名類
		return new B() {
			public void func() {
				System.out.println("Y func");
			}
		};
	}
}
public class MultiInterfaces {
	static void takeA(A a) {
		a.show();
	}
	static void takeB(B b) {
		b.func();
	}
	public static void main(String[] args) {
		Y y = new Y();
		takeA(y);
		takeB(y.makeB());
	}
}

 

1. 內部類能夠有多個實例, 每一個實例都有本身的狀態信息, 而且與其外圍類對象的信息相互獨立.

2. 在單個外圍類中, 可讓多個內部類以不一樣的方式實現同一個接口, 或繼承同一個類.

3. 建立內部類對象並不依賴於外圍類對象的建立.

4. 內部類是獨立的實體.

閉包與回調

閉包是一個可調用的對象,它記錄了一些信息,這些信息來自於建立它的做用域. 經過此定義,能夠看出內部類是面向對象的閉包, 由於它不只包含外圍類對象(建立內部類的做用域)的信息, 還自動擁有一個指向此外圍類對象的引用, 在此做用域內, 內部類有權操做全部的成員, 包括private成員.

內部類的繼承

在繼承內部類的時候, 因爲內部類關聯一個外部類的實例, 因此大概格式以下:

class WithInner {
	class Inner{}
}
public class InheritInner extends WithInner.Inner {
	InheritInner(WithInner wi) {
		wi.super();
	}
	public static void main(String[] args) {
		WithInner wi = new WithInner();
		InheritInner ii = new InheritInner(wi);
	}
}

內部類的覆蓋

兩個內部類是獨立的兩個實體,各自在本身的命名空間, 它們須要具體的外部類實例進行引用.

class Egg {
	private Yolk y;
	protected class Yolk {
		public Yolk() {
			System.out.println("Egg.Yolk()");
		}
	}
	public Egg() {
		System.out.println("New Egg()");
		y = new Yolk();
	}
}
public class BigEgg extends Egg {
	public class Yolk {
		public Yolk() {
			System.out.println("BigEgg Yolk()");
		}
	}
	public static void main(String[] args) {
		//New Egg()
		//Egg.Yolk()
		new BigEgg();
	}
}

局部內部類

咱們能夠在一個方法體的裏面建立一個內部類,局部內部類不能有訪問說明符,由於它不是外圍類的一部分,可是它能夠訪問當前代碼塊內的常量,以及此外圍類的全部成員.

下例對局部內部類與匿名內部類的建立進行了比較:

interface Counter {
	int next();
}
public class LocalInnerClass {
	private int count = 0;
	Counter getCounter(final String name) {
		class LocalCounter implements Counter {
			public LocalCounter() {
				System.out.println("LocalCounter()");
			}
			public int next() {
				System.out.print(name);
				return count++;
			}
		}
		return new LocalCounter();
	}

	Counter getCounter2(final String name) {
		return new Counter() {
			{
				System.out.println("Counter");
			}
			@Override
			public int next() {
				System.out.print(name);
				return count++;
			}
		};
	}

	public static void main(String[] args) {
		LocalInnerClass lic = new LocalInnerClass();
		Counter c1 = lic.getCounter("Local inner ");
		Counter c2 = lic.getCounter2("Anonymous inner");
		for (int i = 0; i < 5; i++)
			System.out.println(c1.next());
		for (int i = 0; i < 5; i++)
			System.out.println(c2.next());
	}
}

輸出:

LocalCounter()
Counter
Local inner 0
Local inner 1
Local inner 2
Local inner 3
Local inner 4
Anonymous inner5
Anonymous inner6
Anonymous inner7
Anonymous inner8
Anonymous inner9

使用局部類而不是匿名內部類的惟一理由是: 咱們須要一個已命名的構造器,或者須要重載構造器, 而匿名內部類只能用於實例初始化.

相關文章
相關標籤/搜索