內(nèi)部類(lèi)是一種非常有用的特性碘箍,因?yàn)樗试S你把一些邏輯相關(guān)的類(lèi)組織在一起匾鸥,并控制位于內(nèi)部的類(lèi)的可視性酬核。
如果想從外部類(lèi)的非靜態(tài)方法之外的任意位置創(chuàng)建某個(gè)內(nèi)部類(lèi)的對(duì)象蜜另,那么必須具體地指明這個(gè)對(duì)象的類(lèi)型适室,OutClassName.InnerClassName
內(nèi)部類(lèi)自動(dòng)擁有對(duì)其外部類(lèi)所有成員的訪問(wèn)權(quán)。
內(nèi)部類(lèi)的對(duì)象只能在與其外圍類(lèi)的對(duì)象相關(guān)聯(lián)的情況下才能被創(chuàng)建举瑰,構(gòu)建內(nèi)部類(lèi)對(duì)象時(shí)捣辆,需要一個(gè)指向其外圍類(lèi)對(duì)象的引用,如果編譯器訪問(wèn)不到這個(gè)引用就會(huì)報(bào)錯(cuò)此迅。
10.3 使用.this與.new
如果你需要生成對(duì)外部類(lèi)對(duì)象的引用汽畴,可以使用外部類(lèi)的名字后面緊跟.this,這樣產(chǎn)生的引用自動(dòng)地具有正確的類(lèi)型耸序,這一點(diǎn)在編譯期就被知曉并受到檢查忍些,因此沒(méi)有任何運(yùn)行時(shí)開(kāi)銷(xiāo)。
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()
*///:~
創(chuàng)建某個(gè)內(nèi)部類(lèi)的對(duì)象坎怪,需要使用.new語(yǔ)法罢坝,必須使用外部類(lèi)的對(duì)象來(lái)創(chuàng)建該內(nèi)部類(lèi)對(duì)象。
public class DotNew {
public class Inner {}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
} ///:~
10.4 內(nèi)部類(lèi)與向上轉(zhuǎn)型
內(nèi)部類(lèi)——某個(gè)接口的實(shí)現(xiàn)——能夠完全不可見(jiàn)搅窿,并且不可用嘁酿。所得到的只是指向基類(lèi)或接口的引用,所以能夠很方便地隱藏實(shí)現(xiàn)細(xì)節(jié)男应。
private內(nèi)部類(lèi)給類(lèi)的設(shè)計(jì)者提供了一種途徑痹仙,通過(guò)這種方式可以完全阻止任何依賴于類(lèi)型的編碼,并且完全隱藏了實(shí)現(xiàn)的細(xì)節(jié)殉了。此外开仰,從客戶端程序員的角度來(lái)看,由于不能訪問(wèn)任何新增加的薪铜、原本不屬于公共接口的方法众弓,所以擴(kuò)展接口是沒(méi)有價(jià)值的。
10.5 在方法和作用域內(nèi)的內(nèi)部類(lèi)
可以在一個(gè)方法里或者在任意的作用域內(nèi)定義內(nèi)部類(lèi)隔箍,這么做有兩個(gè)理由:
- 你實(shí)現(xiàn)了某類(lèi)型的接口谓娃,于是可以創(chuàng)建并返回對(duì)其的引用。
- 要解決一個(gè)復(fù)雜的問(wèn)題蜒滩,想創(chuàng)建一個(gè)類(lèi)來(lái)輔助你的解決方案滨达,但是又不希望這個(gè)類(lèi)是公共可用的。
//: 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");
}
} ///:~
在方法的作用域內(nèi)(而不是在其他類(lèi)的作用域內(nèi))創(chuàng)建一個(gè)完整的類(lèi)俯艰,這被稱(chēng)為局部?jī)?nèi)部類(lèi)捡遍。在destionation方法之外不能訪問(wèn)PDestination,在return語(yǔ)句中又向上轉(zhuǎn)型成基類(lèi)竹握。
內(nèi)部類(lèi)可以在任意的作用域內(nèi)嵌入画株,并不是說(shuō)該類(lèi)的創(chuàng)建是有條件的,它與其他別的類(lèi)一起編譯過(guò)了,然而在其作用域之外他是不可用的谓传。
10.6 匿名內(nèi)部類(lèi)
匿名內(nèi)部類(lèi)指的是創(chuàng)建一個(gè)繼承自某個(gè)基類(lèi)的匿名類(lèi)的對(duì)象蜈项。
在匿名類(lèi)末尾的分號(hào),并不是用來(lái)標(biāo)記此內(nèi)部類(lèi)結(jié)束的续挟。它標(biāo)記的是表達(dá)式的結(jié)束紧卒,只不過(guò)這個(gè)表達(dá)式正巧包含了匿名內(nèi)部類(lèi)罷了。
如果匿名內(nèi)部類(lèi)使用一個(gè)在外部定義的對(duì)象诗祸,那么編譯器會(huì)要求其參數(shù)引用是final的常侦。
匿名類(lèi)中不可能有命名構(gòu)造器(因?yàn)樗緵](méi)有名字),但通過(guò)實(shí)例初始化贬媒,就能夠達(dá)到為匿名內(nèi)部類(lèi)創(chuàng)建一個(gè)構(gòu)造器的效果聋亡。
//: 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的,因?yàn)閕被傳遞給匿名類(lèi)的基類(lèi)的構(gòu)造器际乘,它并不會(huì)在匿名類(lèi)內(nèi)部被直接使用坡倔。
匿名內(nèi)部類(lèi)與正規(guī)的繼承相比有些受限,因?yàn)槟涿麅?nèi)部類(lèi)既可以擴(kuò)展類(lèi)脖含,也可以實(shí)現(xiàn)接口罪塔,但是不能兩者兼?zhèn)洹6胰绻菍?shí)現(xiàn)接口养葵,也只能實(shí)現(xiàn)一個(gè)接口征堪。
利用匿名內(nèi)部類(lèi)可以更好地創(chuàng)建工廠方法:
//: 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
*///:~
優(yōu)先使用類(lèi)而不是接口,如果你的設(shè)計(jì)中需要某個(gè)接口关拒,你必須了解它佃蚜。否則,不到迫不得已着绊,不要將其放到你的設(shè)計(jì)中谐算。
10.7 嵌套類(lèi)
如果不需要內(nèi)部類(lèi)對(duì)象與其外圍類(lèi)對(duì)象之間有聯(lián)系,那么可以將內(nèi)部類(lèi)聲明為static归露,這通常稱(chēng)為嵌套類(lèi)洲脂。
普通的內(nèi)部類(lèi)對(duì)象隱式地保存了一個(gè)引用,指向創(chuàng)建它的外圍類(lèi)對(duì)象剧包。但是恐锦,當(dāng)內(nèi)部類(lèi)是static的,嵌套類(lèi)意味著:
- 要?jiǎng)?chuàng)建嵌套類(lèi)的對(duì)象疆液,并不需要其外圍類(lèi)的對(duì)象一铅。
- 不能從嵌套類(lèi)的對(duì)象中訪問(wèn)非靜態(tài)的外圍類(lèi)對(duì)象。
正常情況下不能在接口內(nèi)部放置任何代碼枚粘,但嵌套類(lèi)可以作為接口的一部分馅闽。因?yàn)轭?lèi)是static的,只是將嵌套類(lèi)置于接口的命名空間內(nèi)馍迄,這并不違反接口的原則福也,甚至可以在內(nèi)部類(lèi)實(shí)現(xiàn)其外圍接口。
//: innerclasses/ClassInInterface.java
// {main: ClassInInterface$Test}
public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface {
public void howdy() {
System.out.println("Howdy!");
}
public static void main(String[] args) {
new Test().howdy();
}
}
} /* Output:
Howdy!
*///:~
一個(gè)內(nèi)部類(lèi)被嵌套多少層并不重要——它能透明地訪問(wèn)所有它所嵌入的外圍類(lèi)的所有成員攀圈。
10.8 為什么需要內(nèi)部類(lèi)
每個(gè)內(nèi)部類(lèi)都能獨(dú)立地繼承自一個(gè)接口的實(shí)現(xiàn)暴凑,所以無(wú)論外圍類(lèi)是否已經(jīng)繼承了某個(gè)接口的實(shí)現(xiàn),對(duì)于內(nèi)部類(lèi)都沒(méi)有影響赘来。內(nèi)部類(lèi)使得多重繼承的解決方案變得完整现喳。
使用內(nèi)部類(lèi)獲得其他一些特性:
- 內(nèi)部類(lèi)可以有多個(gè)實(shí)例,每個(gè)實(shí)例都有自己的狀態(tài)信息犬辰,并且與其外圍類(lèi)對(duì)象的信息相互獨(dú)立嗦篱。
- 在單個(gè)外圍類(lèi)中,可以讓多個(gè)內(nèi)部類(lèi)以不同的方式實(shí)現(xiàn)同一個(gè)接口幌缝,或繼承同一個(gè)類(lèi)灸促。
- 創(chuàng)建內(nèi)部類(lèi)對(duì)象的時(shí)刻并不依賴于外圍類(lèi)對(duì)象的創(chuàng)建。
- 內(nèi)部類(lèi)并沒(méi)有令人迷惑的is-a關(guān)系涵卵,它就是一個(gè)獨(dú)立的實(shí)體浴栽。
上面的地三條,這里的重音在時(shí)刻兩個(gè)字轿偎,作者想突出內(nèi)部類(lèi)是外部類(lèi)“輕量級(jí)的可選組件”這個(gè)特性典鸡。其實(shí)也很好理解,比如迭代器作為很多容器的外部類(lèi)坏晦,并不是在創(chuàng)建容器的時(shí)候就被一起創(chuàng)建的萝玷。而是要我們?cè)傩枰臅r(shí)候,手動(dòng)創(chuàng)建實(shí)例昆婿,日工容器內(nèi)部元素的視圖间护。突出“optional”的特性,而不是說(shuō)它本身和外部類(lèi)容器沒(méi)關(guān)系挖诸。
//: innerclasses/Callbacks.java
// Using inner classes for callbacks
package innerclasses;
import static net.mindview.util.Print.*;
interface Incrementable {
void increment();
}
// Very simple to just implement the interface:
class Callee1 implements Incrementable {
private int i = 0;
public void increment() {
i++;
print(i);
}
}
class MyIncrement {
public void increment() { print("Other operation"); }
static void f(MyIncrement mi) { mi.increment(); }
}
// If your class must implement increment() in
// some other way, you must use an inner class:
class Callee2 extends MyIncrement {
private int i = 0;
public void increment() {
super.increment();
i++;
print(i);
}
private class Closure implements Incrementable {
public void increment() {
// Specify outer-class method, otherwise
// you'd get an infinite recursion:
Callee2.this.increment();
}
}
Incrementable getCallbackReference() {
return new Closure();
}
}
class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh) { callbackReference = cbh; }
void go() { callbackReference.increment(); }
}
public class Callbacks {
public static void main(String[] args) {
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
} /* Output:
Other operation
1
1
2
Other operation
2
Other operation
3
*///:~
此實(shí)例展示了回調(diào)汁尺,和當(dāng)繼承父類(lèi)重寫(xiě)父類(lèi)方法與需要繼承的接口中的方法重疊時(shí),用內(nèi)部類(lèi)解決的思路多律。
內(nèi)部類(lèi)在實(shí)現(xiàn)控制框架上痴突,可以在總控制類(lèi)內(nèi)部實(shí)現(xiàn)具體操作的內(nèi)部類(lèi),能夠很好地繼承事件抽象類(lèi)狼荞,而且能夠方便訪問(wèn)外部類(lèi)的所有方法和實(shí)例辽装。
10.9 內(nèi)部類(lèi)的繼承
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);
}
} ///:~
注意構(gòu)造器的實(shí)現(xiàn)。
10.10 內(nèi)部類(lèi)可以被覆蓋嗎
//: innerclasses/BigEgg.java
// An inner class cannot be overriden like a method.
import static net.mindview.util.Print.*;
class Egg {
private Yolk y;
protected class Yolk {
public Yolk() { print("Egg.Yolk()"); }
}
public Egg() {
print("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg {
public class Yolk {
public Yolk() { print("BigEgg.Yolk()"); }
}
public static void main(String[] args) {
new BigEgg();
}
} /* Output:
New Egg()
Egg.Yolk()
*///:~
當(dāng)繼承了某個(gè)外圍類(lèi)的時(shí)候相味,內(nèi)部類(lèi)并沒(méi)有發(fā)生什么特別神奇的變化拾积。這兩個(gè)內(nèi)部類(lèi)是完全獨(dú)立的兩個(gè)實(shí)體,各自在自己的命名空間內(nèi)。
//: innerclasses/BigEgg2.java
// Proper inheritance of an inner class.
import static net.mindview.util.Print.*;
class Egg2 {
protected class Yolk {
public Yolk() { print("Egg2.Yolk()"); }
public void f() { print("Egg2.Yolk.f()");}
}
private Yolk y = new Yolk();
public Egg2() { print("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() { print("BigEgg2.Yolk()"); }
public void f() { print("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()
*///:~
10.11 局部?jī)?nèi)部類(lèi)
局部?jī)?nèi)部類(lèi)不能有訪問(wèn)說(shuō)明符拓巧,因?yàn)樗皇峭鈬?lèi)的一部分斯碌;但是它可以訪問(wèn)當(dāng)前代碼塊內(nèi)的常量,以及此外圍類(lèi)的所有成員肛度。
既然局部?jī)?nèi)部類(lèi)的名字在方法外是不可見(jiàn)的傻唾,那為什么我們?nèi)匀皇褂镁植績(jī)?nèi)部類(lèi)而不是匿名內(nèi)部類(lèi)呢?唯一的理由是承耿,我們需要一個(gè)已命名的構(gòu)造器冠骄,或者需要重載構(gòu)造器,而匿名內(nèi)部類(lèi)只能用于實(shí)例初始化加袋。另一個(gè)理由就是需要不止一個(gè)該內(nèi)部類(lèi)的對(duì)象凛辣。
內(nèi)部類(lèi)標(biāo)識(shí)符
內(nèi)部類(lèi)生成的.class文件以包含它們的Class對(duì)象信息。外圍類(lèi)的名字职烧,加上“$”蟀给,再加上內(nèi)部類(lèi)的名字。匿名內(nèi)部類(lèi)會(huì)產(chǎn)生一個(gè)數(shù)字作為其表示符阳堕。