可以將一個(gè)類的定義放到另一個(gè)類的定義內(nèi)部,這就是內(nèi)部類。
10.1 創(chuàng)建內(nèi)部類
如果想從外部類的非靜態(tài)方法之外的任意位置創(chuàng)建某個(gè)內(nèi)部類的對(duì)象,那么必須具體指明這個(gè)對(duì)象的類型:OuterClassname.InnerClassName
10.2 鏈接到外部類
當(dāng)生成一個(gè)內(nèi)部類的對(duì)象時(shí),此對(duì)象與制造它的外圍對(duì)象之間就有了一種關(guān)系,所以他能訪問其外圍對(duì)象的所有成員,而不需要任何特殊條件。此外,內(nèi)部類還擁有其外圍類的所有元素的訪問權(quán)。
當(dāng)某個(gè)外圍類的對(duì)象創(chuàng)建一個(gè)內(nèi)部類對(duì)象時(shí),次內(nèi)部類對(duì)象必定會(huì)秘密地捕獲一個(gè)指向那個(gè)外圍類對(duì)象的引用(內(nèi)部類是非static類時(shí))。
10.3 使用.this 和 .new
如果你需要生成對(duì)外部類對(duì)象的引用,可以使用外部類的名字后面緊跟圓點(diǎn)和this:OuterClassName.this
要想直接創(chuàng)建內(nèi)部類的對(duì)象,你不能按照你想象的方式,去引用外部類的名字,而是必須使用外部類的對(duì)象來創(chuàng)建該內(nèi)部類的對(duì)象。
在擁有外部類對(duì)象之前是不可能創(chuàng)建內(nèi)部類對(duì)象的。
一個(gè)內(nèi)部類被嵌套多少層并不重要,它能透明地訪問所有它所嵌入的外圍類的所有成員。
10.4 內(nèi)部類和向上轉(zhuǎn)型
當(dāng)內(nèi)部類向上轉(zhuǎn)型為其基類,尤其是轉(zhuǎn)型為一個(gè)接口的時(shí)候,內(nèi)部類有了特殊的作用。內(nèi)部類關(guān)于某個(gè)接口的實(shí)現(xiàn)能夠完全不可見,并且不可用。我們只能得到指向基類或者接口的引用,所以能夠很方便地隱藏實(shí)現(xiàn)細(xì)節(jié)。
10.5 在方法和作用域內(nèi)的內(nèi)部類
又名局部內(nèi)部類。
使用場景:
- 你實(shí)現(xiàn)了某類型的接口,于是可以創(chuàng)建并返回對(duì)其的引用
- 你要解決一個(gè)復(fù)雜的問題,想創(chuàng)建一個(gè)雷來輔助你的解決方案,但是又不希望這個(gè)雷士公共可用的。
10.6 匿名內(nèi)部類
看起來似乎是你正要返回一個(gè)對(duì)象。但是然后(在到達(dá)預(yù)付結(jié)束的分號(hào)之前)你卻說:“等一等,我想在這里插入一個(gè)類的定義”。即:定義并立即返回一個(gè)類的實(shí)例。
- 可以使用實(shí)例初始化達(dá)到匿名內(nèi)部類的構(gòu)造器的效果。但是不能重載實(shí)力初始化方法,所以你僅有一個(gè)這樣的構(gòu)造器。
- 匿名內(nèi)部類既可以擴(kuò)展類,又可以實(shí)現(xiàn)接口,但是不能兩者兼?zhèn)洹6覍?shí)現(xiàn)接口,也只能實(shí)現(xiàn)一個(gè)接口。
如果定義一個(gè)匿名內(nèi)部類,并且希望它使用一個(gè)在其外部定義的對(duì)象,那么編譯器要求其參數(shù)引用是final的。
10.7 嵌套類 static
- 要?jiǎng)?chuàng)建嵌套類的對(duì)象,并不需要其外圍類的對(duì)象
- 不能從嵌套類的對(duì)象中訪問非靜態(tài)的外圍類的對(duì)象。
10.7.1 接口內(nèi)部的類
如果你想要?jiǎng)?chuàng)建某些公用代碼,使得他們可以被某個(gè)接口的所有不同實(shí)現(xiàn)所公用,那么使用接口內(nèi)部的嵌套類會(huì)顯得很方便。
10.8 為什么需要內(nèi)部類
每個(gè)內(nèi)部類都能獨(dú)立地繼承自一個(gè)(接口的)實(shí)現(xiàn),所以無論外圍類是否已經(jīng)繼承了某個(gè)(接口的)實(shí)現(xiàn),對(duì)內(nèi)部類都沒有影響。
如果沒有內(nèi)部類提供的、可以繼承多個(gè)具體的或抽象的類的能力,一些設(shè)計(jì)與編程問題就很難解決。
如果不需要解決“多重繼承”的問題,那么自然可以用別的方式編碼,而不需要使用內(nèi)部類。但如果使用內(nèi)部類,還可以獲得其他一些特性:
- 內(nèi)部類可以有多個(gè)實(shí)例,每個(gè)實(shí)例都有自己的狀態(tài)信息,并且與其外圍類對(duì)象的信息相互獨(dú)立
- 在單個(gè)外圍類中,可以讓多個(gè)內(nèi)部類以不同的方式實(shí)現(xiàn)同一個(gè)接口,或繼承同一個(gè)類。
- 創(chuàng)建內(nèi)部類對(duì)象的時(shí)刻并不依賴于外圍類對(duì)象的創(chuàng)建。
- 內(nèi)部類沒有令人迷糊的"is-a"關(guān)系,他就是一個(gè)獨(dú)立地實(shí)體。
10.8.1閉包與回調(diào)
內(nèi)部類是面向?qū)ο蟮拈]包。
interface Incrementable{
void increment();
}
class MyIncrement{
public void increment(){
System.out.println("Other operation");
}
public static void f(MyIncrement mi){
mi.increment();
}
}
class Callee1 implements Incrementable{
private int i=0;
@Override
public void increment() {
i++;
System.out.println(i);
}
}
class Callee2 extends MyIncrement{
private int i=0;
@Override
public void increment() {
super.increment();
i++;
System.out.println(i);
}
private class Closure implements Incrementable{
@Override
public void increment() {
Callee2.this.increment();
System.out.println("Closure increment");
}
}
Incrementable getCallbackReference(){
return new Closure();
}
}
class Caller{
private Incrementable callbackRefrence;
Caller(Incrementable cbh){
callbackRefrence=cbh;
}
void go(){
callbackRefrence.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();
}
}
Callee1
是簡單地解決方式.Callee2
繼承自MyIncrement,已經(jīng)有一個(gè)不同的increment()
方法,與Incrementable沖突且完全不想關(guān)。所以不能為了Incrementable
的用途而覆蓋inrement()方法,于是只能使用內(nèi)部類獨(dú)立地實(shí)現(xiàn)Inrementable。
在Callee2中除了
getCallbackReference()
以外,其他成員都是private的。只能通過interfaceIncrementable
接口。這是一個(gè)安全的鉤子(hook),無論誰獲得Incrementable
的引用,都只能調(diào)用increment()
方法。
10.8.2 內(nèi)部類與控制框架
在控制框架中使用內(nèi)部類 ,可以:
- 控制框架的完整實(shí)現(xiàn)是由單個(gè)的類創(chuàng)建的,從而使得實(shí)現(xiàn)的細(xì)節(jié)被封裝起來。內(nèi)部類用來表示解決問題所需的各種不同的action()。
- 內(nèi)部類能夠很容易地訪問外圍類的任意成員,避免實(shí)現(xiàn)變得笨拙。
10.9 內(nèi)部類的繼承
語法:
enclosingClassReference.super();
class WithInner{
class Inner{}
}
public class InheritInner extends WithInner.Inner{
public InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args){
WithInner inner=new WithInner();
InheritInner ii=new InheritInner(inner);
}
}
10.10 內(nèi)部類可以被覆蓋嗎?
當(dāng)繼承了某個(gè)外圍類的時(shí)候,內(nèi)部類并沒有發(fā)生什么神奇的變化。這兩個(gè)內(nèi)部類是完全獨(dú)立的兩個(gè)實(shí)體,各自在自己的命名空間內(nèi)。
10.11 局部內(nèi)部類
可以在代碼塊里創(chuàng)建內(nèi)部類,典型的方式是在一個(gè)方法體的里面創(chuàng)建。局部內(nèi)部類不能有訪問說明符,因?yàn)樗皇峭鈬惖囊徊糠郑坏撬梢栽L問當(dāng)前代碼塊內(nèi)的常量,以及此外圍類的所有成員。
使用局部內(nèi)部類而不是匿名內(nèi)部類的理由:
- 我們需要一個(gè)已命名的構(gòu)造器,或者需要重載構(gòu)造器,而匿名內(nèi)部類只能用于實(shí)力初始化。
- 需要不止一個(gè)該內(nèi)部類的對(duì)象。
10.12 內(nèi)部類標(biāo)識(shí)符
內(nèi)部類類文件的命名有嚴(yán)格的規(guī)則:外圍類的名字,加上"$",再加上內(nèi)部類的名字。
如果內(nèi)部類是匿名的,編譯器會(huì)簡單地產(chǎn)生一個(gè)數(shù)字作為其標(biāo)識(shí)符。