ch10:內部類(inner class)
定義
將一個類的定義放在另一個類的定義內部,這就是內部類。
創建內部類
創建內部類的方式就如定義一樣,放置在外圍類裏面就行:
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. withing 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");
}
/**
* output:
* Tasmania
*/
}
在ship()方法裏面使用內部類時,與普通類沒有什麼區別。
更常用的是外圍類定義一個方法只想內部類,代碼如下:
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 class:
Parcel2.Contents c = q.contents();
Parcel2.Destination d = q.to("Borneo");
}
/**
* output:
* Tasmania
*/
}
如果想從外部類的非靜態方法之外的任意位置創建某個內部類對象,那麼必須指明具體的外部類名:OuterClassName.InnerClassName
鏈接到外部類
以上講的內部類還只是一種代碼隱藏和組織代碼的模式。
當然還有其他用途,當生成一個內部類對象時,此對象與製造它的外圍對象(enclosing object)之間就有了一種聯繫,所以可以隨意訪問外圍對象的所有成員,不需要任何特殊條件。此外,還擁有外圍類的所有元素的訪問權。
示例代碼如下:
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;
@Override
public boolean end() {
return i == items.length;
}
@Override
public Object current() {
return items[i];
}
@Override
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
*/
}
內部類可以任意訪問外部類的元素。
使用.this與.new
如果需要生成對外部對象的引用,可以使用外部類加.this。這樣產生的引用自動地具有正確的類型,這一點在編譯器知曉識別了,因此沒有任何運行時開銷。
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"
//如果僅用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語法創建。如下:
public class DotNew {
public class Inner {}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
}
注意,必須在有外部類的情況下才能創建內部類對象,因爲內部類會暗暗地連接到創建它的外部類對象上,對於嵌套類(靜態內部類),就可以不需要外部類對象的引用(也就是可以直接創建內部類對象)。
在方法和作用域內的內部類
內部類的位置是靈活的,在外部類裏面,外部類的方法裏面,外部類的代碼塊中都可以,只是位置決定了該內部類的作用範圍,使用的時候需要注意。
public class Parcel5 {
public Destination destination(String s) {
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");
}
}
PDestination是destination()方法的一部分,作用點僅在方法內部。返回的是向上轉型的Destination。
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();
}
//can't use it here! Out of scope:
//! TrackingSlip ts = new TrackingSlip("x");
}
public void track() {
internalTracking(true);
}
public static void main(String[] args) {
Parcel6 p = new Parcel6();
p.track();
}
}
上面的代碼,TrackingSlip類被嵌入在if語句的作用域內,並不是說該類的創建是有條件的,它其實與別的類一起編譯過了。除了作用域外,與普通類一樣。
匿名內部類
創建一個繼承自接口或者類的一個匿名類的對象,通過new表達式返回的引用被自動向上轉型爲對父類(接口或者類)的引用。
class Contents {
private int i = 11;
public int value() {
return i;
}
}
public class Parcel7 {
public Contents contents() { // insert a class definition
return new Contents() {
private int i = 12;
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,該contents是一個實體類,一般而言,我們創建的匿名內部類以接口居多,上面展示的是對於類也是可以使用該方式。
對於需要有參數構造器的,以下展示的是帶構造參數的匿名內部類。
class Wrapping {
private int x;
public Wrapping(int x) {
this.x = x;
}
int value() {
return x;
};
}
public class Parcel8 {
//構造器參數x不必是final的
public Wrapping wrapping(int x) {
//base constructor call:
return new Wrapping(x) {
@Override
public int value() {
return super.value() * 47;
}
};// semicolon required
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Wrapping w = p.wrapping(10);
}
}
在匿名內部類末尾的分號,並不是用來標記此內部類結束的,只是該表達式的結束,只不過這個表達式正巧包含了匿名內部類罷了。
在匿名類中定義字段時,還能夠對其執行初始化操作:
public class Parcel9 {
//匿名類對象中初始化的參數dest必須是final的
public Destination destination(final String dest) {
return new Destination() {
private String label = dest;
@Override
public String readLabel() {
return label;
}
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination("Tasmania");
}
}
匿名內部類構造器方法傳入的參數因爲是初始化時就定了,所以不必要求是final的,但是對於匿名內部類初始化時的參數,必須是final的。
嵌套類
如果不需要內部類對象與外圍類對象有聯繫,可以將內部類聲明爲static。這個通常稱爲嵌套類。
普通內部類對象隱式地保存了一個引用,指向創建它的外圍類。
使用嵌套類需要注意:
1. 創建嵌套類,不需要外圍對象
2. 不能從嵌套類的對象中訪問非靜態的外圍類對象。
示例代碼如下:
public class Parcel11 {
private String value1 = "value1";
private static String value2 = "value2";
public static class Inner {
public String getValue() {
return "value";
//無法獲取到value1的值,因爲對於該嵌套類來說是單獨存在的,不能訪問非靜態變量
//! return value1;
}
//可以獲取到靜態變量的值
public String getValue2() {
return value2;
}
}
public static void main(String[] args) {
//直接使用new OuterClassName.InnerClassName()創建嵌套類對象,
// 前提是嵌套類的在這裏是可見的
System.out.println(new Parcel11.Inner().getValue());
}
}
接口內部類
正常情況下,不能在接口中放置任何代碼,但是嵌套類可作爲接口的一部分存在,因爲實在接口中,所以該類自動的爲public和static屬性。因爲類是static的,所以只是將嵌套類置於接口的命名空間內,並不違反接口的規則。
示例:
public interface ClassInterface {
void howdy();
class Test implements ClassInterface {
@Override
public void howdy() {
System.out.println("howdy()");
}
public static void main(String[] args) {
new Test().howdy();
}
}
}
多層嵌套類
一個類中可以嵌套很多層,這個沒有限制。
示例:
class MNA {
private void f() {}
class A {
private void g() {}
public class B {
void h() {
g();
f();
}
}
}
}
public class MutiNestingAccess {
public static void main(String[] args) {
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnab = mnaa.new B();
mnab.h();
}
}
爲什麼需要內部類
每個內部類都能獨立地繼承一個(接口)的實現,所以無論外圍類是否已經繼承了某個(接口)的實現,對於內部類是沒有影響的。
對於實現多接口,外部類一個即可以實現,內部類也同樣可以,但是對於繼承(普通類和抽象類),只有通過內部類解決,即內部類比較完美的完善了多繼承的問題,相對於c++來說(實現多接口只是一部分上解決了這個問題)。
class D {}
abstract class E {}
class Z extends D {
E makeE() {
return new E() {};
}
}
public class MutiImplementation {
static void taskD(D d) {}
static void taskE(E e) {}
public static void main(String[] args) {
Z z = new Z();
taskD(z);
taskE(z.makeE());
}
}
如果不需要解決“多重繼承”的問題,自然可以使用其他方式解決,可以不使用內部類,但是如果使用內部類,還可以獲得其他的一些特性:
1. 內部類可以有多個實例,每個實例都有自己的狀態信息,並且與其外圍類對象的信息相互獨立。
2. 在單個外圍類,可以讓多個內部類以不同的方式實現同一個接口,或繼承同一個類。
3. 創建內部類對象的時刻並不依賴於外圍類對象的創建。
4. 內部類並沒有令人迷惑的“is-a”關係;它就是一個獨立的實體。
- 閉包與回調
閉包(closure)是一個可調用的對象,它記錄了一些信息,這些信息來自於創建它的作用域。
通過這個定義可以看出,內部類是面向對象的閉包,因爲它不僅包含外圍類對象(創建內部類的作用域)的信息,還自動擁有一個指向外圍類對象的引用,在此作用域內,內部類有權限操作所有成員,包括private成員。
內部類的繼承
內部類有點特殊,因爲其必須持有外圍類的引用才能夠進行初始化,所以以下示例展示了內部類的繼承問題:
class WithInner {
class Inner {}
}
public class InheritInner extends WithInner.Inner {
//不能使用默認構造器
//InheritInner() {} 該構造器不會編譯
InheritInner(WithInner withInner) {
withInner.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
如上示例代碼,繼承自內部類,必須在構造器方法中調用外部類中調用
enclosingClassReference.super()
提供必要的引用,方法才能編譯通過。
內部類可以被覆蓋嗎
創建一個內部類,然後繼承其外圍類並重新定義此內部類時,以下爲示例代碼:
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 BigEgg();
}
/**
* output:
* new Egg()
* Egg.Yolk()
*/
}
內部類可以明確的繼承,示例:
class Egg2 {
protected class Yolk {
public Yolk() {
System.out.println("Egg2.Yolk()");
}
public void f() {
System.out.println("Egg2.Yolk.f()");
}
}
private Yolk y = new Yolk();
public Egg2() {
System.out.println("New Egg2()");
}
public void insertYolk(Yolk yy) {
y = yy;
}
public void g() {
y.f();
}
}
public class BigEgg2 extends Egg2 {
public class Yolk extends Egg2.Yolk {
public Yolk() {
System.out.println("BigEgg2.Yolk()");
}
public void f() {
System.out.println("BigEgg2.Yolk.f()");
}
}
public BigEgg2() {
insertYolk(new Yolk());
}
public static void main(String[] args) {
Egg2 e2 = new BigEgg2();
e2.g();
}
/**
* output:
* Egg2.Yolk()
* New Egg2()
* Egg2.Yolk()
* BigEgg2.Yolk()
* BigEgg2.Yolk.f()
*/
}
局部內部類
在代碼塊裏面也可以使用類,這稱之爲局部內部類,其作用範圍是有限的。
局部內部類不能有訪問限制符,因爲不是外圍類的一部分,但是可以訪問當前代碼塊內的常量,以及此外圍類的所有成員。
內部類標識符
由於每個類會產生一個.class文件,其中包含了如何創建該類型的對象的全部信息(此信息產生一個“meta-class”,叫做Class對象),內部類也必須生成一個.class文件包含它們的Class對象信息。這些類文件的命名有嚴格的規則:外圍類的名字,加上“$”,再加上內部類的名字。
如果內部類時匿名的,編譯器會簡單的產生一個數字作爲其標識符。如果內部類時嵌套在別的內部類中,只需直接將它們的名字加載其外圍類標識符與“$”的後面。
//簡單的獲取類名的示例代碼
class Test{
class Inner {
public class Inner1 {}
}
}
public class ClassNameTest {
public static void main(String[] args) {
Test t= new Test();
Test.Inner inner1 = t.new Inner();
Test.Inner.Inner1 inner11 = inner1.new Inner1();
System.out.println(inner1.getClass().getName());
System.out.println(inner11.getClass().getName());
}
/**
* output:
* Test$Inner
* Test$Inner$Inner1
*/
}