簡(jiǎn)書的格式,有點(diǎn)吃不消啊
內(nèi)容整理自《Thinking in Java》(第四版) 第10章 PDF下載地址
什么是內(nèi)部類?
將一個(gè)類的定義,放在另一個(gè)類的定義內(nèi)部,那這個(gè)類坝辫,就是內(nèi)部類
為什么需要內(nèi)部類?
一般來說射亏,內(nèi)部類繼承自某個(gè)類或?qū)崿F(xiàn)某個(gè)接口阀溶,內(nèi)部類的代碼操作創(chuàng)建其的外圍類的對(duì)象。所以你可以認(rèn)為內(nèi)部類提供了某種進(jìn)入其外圍類的窗口鸦泳。
內(nèi)部類的優(yōu)雅之處:
每個(gè)內(nèi)部類都能獨(dú)立的繼承一個(gè)(接口的)實(shí)現(xiàn)银锻,無論外部類是否已經(jīng)繼承了一個(gè)(接口的)實(shí)現(xiàn),對(duì)內(nèi)部類都沒有影響做鹰。
內(nèi)部類主要有以下幾類:
- A:成員內(nèi)部類
- B:局部內(nèi)部類
- C:靜態(tài)內(nèi)部類
- D:匿名內(nèi)部類
注意:
- 定義了成員內(nèi)部類后击纬,必須使用外部類對(duì)象來創(chuàng)建內(nèi)部類對(duì)象,而不能直接去 new 一個(gè)內(nèi)部類對(duì)象钾麸,
即:內(nèi)部類 對(duì)象名 = 外部類對(duì)象.new 內(nèi)部類( );
- 外部類是不能直接使用內(nèi)部類的成員和方法滴更振,可先創(chuàng)建內(nèi)部類的對(duì)象,然后通過內(nèi)部類的對(duì)象來訪問其成員變量和方法饭尝。
- 可先創(chuàng)建內(nèi)部類的對(duì)象肯腕,然后通過內(nèi)部類的對(duì)象來訪問其成員變量和方法。HelloWorld.this.name
A:成員內(nèi)部類
作為外部類的一個(gè)成員存在钥平,與外部類的屬性实撒、方法并列。
public class Outer {
private static int i = 1;
private int j = 10;
private int k = 20;
public static void outerF1() {
}
/**
* 外部類的靜態(tài)方法訪問成員內(nèi)部類涉瘾,與在外部類外部訪問成員內(nèi)部類一樣
*/
public static void outerF4() {
//step1 建立外部類對(duì)象
Outer out = new Outer();
//step2 根據(jù)外部類對(duì)象建立內(nèi)部類對(duì)象
Inner inner = out.new Inner();
//step3 訪問內(nèi)部類的方法
inner.innerF1();
}
public static void main(String[] args) {
/*
* outerF4();該語句的輸出結(jié)果和a下面三條語句的輸出結(jié)果一樣
*如果要直接創(chuàng)建內(nèi)部類的對(duì)象知态,不能想當(dāng)然地認(rèn)為只需加上外圍類Outer的名字,
*就可以按照通常的樣子生成內(nèi)部類的對(duì)象立叛,而是必須使用此外圍類的一個(gè)對(duì)象來
*創(chuàng)建其內(nèi)部類的一個(gè)對(duì)象:
*Outer.Inner outin = out.new Inner()
*因此负敏,除非你已經(jīng)有了外圍類的一個(gè)對(duì)象,否則不可能生成內(nèi)部類的對(duì)象秘蛇。因?yàn)榇? *內(nèi)部類的對(duì)象會(huì)悄悄地鏈接到創(chuàng)建它的外圍類的對(duì)象其做。如果你用的是靜態(tài)的內(nèi)部類顶考,
*那就不需要對(duì)其外圍類對(duì)象的引用。
*/
Outer out = new Outer();
Outer.Inner outin = out.new Inner();
outin.innerF1();
}
public void outerF2() {
}
/**
* 外部類的非靜態(tài)方法訪問成員內(nèi)部類
*/
public void outerF3() {
Inner inner = new Inner();
inner.innerF1();
}
/**
* 成員內(nèi)部類中妖泄,不能定義靜態(tài)成員
* 成員內(nèi)部類中村怪,可以訪問外部類的所有成員
*/
class Inner {
// static int innerI = 100;內(nèi)部類中不允許定義靜態(tài)變量
// 內(nèi)部類和外部類的實(shí)例變量可以共存
int j = 100;
int innerI = 1;
void innerF1() {
System.out.println(i);
//在內(nèi)部類中訪問內(nèi)部類自己的變量直接用變量名
System.out.println(j);
//在內(nèi)部類中訪問內(nèi)部類自己的變量也可以用this.變量名
System.out.println(this.j);
//在內(nèi)部類中訪問外部類中與內(nèi)部類同名的實(shí)例變量用外部類名.this.變量名
System.out.println(Outer.this.j);
//如果內(nèi)部類中沒有與外部類同名的變量,則可以直接用變量名訪問外部類變量
System.out.println(k);
outerF1();
outerF2();
}
}
}
<font color=red>注意:內(nèi)部類是一個(gè)編譯時(shí)的概念浮庐,一旦編譯成功,就會(huì)成為完全不同的兩類柬焕。</font>
對(duì)于一個(gè)名為outer的外部類和其內(nèi)部定義的名為inner的內(nèi)部類审残。編譯完成后出現(xiàn)outer.class和outer$inner.class兩類。
B:局部內(nèi)部類
在方法中定義的內(nèi)部類稱為局部內(nèi)部類斑举。與局部變量類似搅轿,局部內(nèi)部類不能有訪問說明符,因?yàn)樗皇峭鈬惖囊徊糠指荤瑁撬梢栽L問當(dāng)前代碼塊內(nèi)的常量璧坟,和此外圍類所有的成員。
public class Outer {
private int s = 100;
private int outI = 1;
public static void main(String[] args) {
// 訪問局部內(nèi)部類必須先有外部類對(duì)象
Outer out = new Outer();
out.f(3);
}
public void f(final int k) {
final int s = 200;
int i = 1;
final int j = 10;
/**
* 定義在方法內(nèi)部
*/
class Inner {
// 可以定義與外部類同名的變量
int s = 300;
int innerI = 100;
// static int m = 20; 不可以定義靜態(tài)變量
Inner(int k) {
innerF(k);
}
void innerF(int k) {
// java如果內(nèi)部類沒有與外部類同名的變量赎懦,在內(nèi)部類中可以直接訪問外部類的實(shí)例變量
System.out.println(outI);
// 可以訪問外部類的局部變量(即方法內(nèi)的變量)雀鹃,但是變量必須是final的
System.out.println(j);
//System.out.println(i);
// 如果內(nèi)部類中有與外部類同名的變量,直接用變量名訪問的是內(nèi)部類的變量
System.out.println(s);
// 用this.變量名訪問的也是內(nèi)部類變量
System.out.println(this.s);
// 用外部類名.this.內(nèi)部類變量名訪問的是外部類變量
System.out.println(Outer.this.s);
}
}
new Inner(k);
}
}
C:靜態(tài)內(nèi)部類(嵌套類):
注意:前兩種內(nèi)部類與變量類似励两,所以可以對(duì)照參考變量
如果你不需要內(nèi)部類對(duì)象與其外圍類對(duì)象之間有聯(lián)系黎茎,那你可以將內(nèi)部類聲明為static。這通常稱為嵌套類(nested class)当悔。想要理解static應(yīng)用于內(nèi)部類時(shí)的含義傅瞻,你就必須記住,普通的內(nèi)部類對(duì)象隱含地保存了一個(gè)引用盲憎,指向創(chuàng)建它的外圍類對(duì)象嗅骄。然而,當(dāng)內(nèi)部類是static的時(shí)饼疙,就不是這樣了溺森。嵌套類意味著:
- 要?jiǎng)?chuàng)建嵌套類的對(duì)象,并不需要其外圍類的對(duì)象窑眯。
- 不能從嵌套類的對(duì)象中訪問非靜態(tài)的外圍類對(duì)象儿惫。
<font color=red>單例模式:由于靜態(tài)內(nèi)部類的加載機(jī)制,決定了他可以使用來處理單例模式,而且性能客觀</font>單例模式相關(guān)內(nèi)容>>點(diǎn)我
public class Outer {
private static int i = 1;
private int j = 10;
public static void outerF1() {
}
public static void main(String[] args) {
new Outer().outerF3();
}
public void outerF2() {
}
public void outerF3() {
// 外部類訪問內(nèi)部類的靜態(tài)成員:內(nèi)部類.靜態(tài)成員
System.out.println(Inner.inner_i);
Inner.innerF1();
// 外部類訪問內(nèi)部類的非靜態(tài)成員:實(shí)例化內(nèi)部類即可
Inner inner = new Inner();
inner.innerF2();
}
/**
* 靜態(tài)內(nèi)部類可以用public,protected,private修飾
* 靜態(tài)內(nèi)部類中可以定義靜態(tài)或者非靜態(tài)的成員
*/
static class Inner {
static int inner_i = 100;
int innerJ = 200;
static void innerF1() {
// 靜態(tài)內(nèi)部類只能訪問外部類的靜態(tài)成員(包括靜態(tài)變量和靜態(tài)方法)
System.out.println("Outer.i" + i);
outerF1();
}
void innerF2() {
// 靜態(tài)內(nèi)部類不能訪問外部類的非靜態(tài)成員(包括非靜態(tài)變量和非靜態(tài)方法)
// System.out.println("Outer.i"+j);
// outerF2();
}
}
}
靜態(tài)內(nèi)部類和成員內(nèi)部類的區(qū)別
生成一個(gè)靜態(tài)內(nèi)部類不需要外部類成員
靜態(tài)內(nèi)部類的對(duì)象可以直接生成:
Outer.Inner in = new Outer.Inner();
而不需要通過生成外部類對(duì)象來生成。這樣實(shí)際上使靜態(tài)內(nèi)部類成為了一個(gè)頂級(jí)類
(正常情況下伸但,你不能在接口內(nèi)部放置任何代碼肾请,但嵌套類可以作為接口的一部分,因?yàn)樗莝tatic 的更胖。只是將嵌套類置于接口的命名空間內(nèi)铛铁,這并不違反接口的規(guī)則)
D:匿名內(nèi)部類(from thinking in java 3th)
匿名內(nèi)部類就是沒有名字的內(nèi)部類隔显。
什么情況下需要使用匿名內(nèi)部類?
如果滿足下面的一些條件饵逐,使用匿名內(nèi)部類是比較合適的:
- 只用到類的一個(gè)實(shí)例括眠。
- 類在定義后馬上用到。
- 類非常斜度ā(SUN推薦是在4行代碼以下)
- 給類命名并不會(huì)導(dǎo)致你的代碼更容易被理解掷豺。
在使用匿名內(nèi)部類時(shí),要記住以下幾個(gè)原則:
- 匿名內(nèi)部類一般不能有構(gòu)造方法薄声。
- 匿名內(nèi)部類不能定義任何靜態(tài)成員当船、方法和類。
- 匿名內(nèi)部類不能是public,protected,private,static默辨。
- 只能創(chuàng)建匿名內(nèi)部類的一個(gè)實(shí)例德频。
- 一個(gè)匿名內(nèi)部類一定是在new的后面,用其隱含實(shí)現(xiàn)一個(gè)接口或?qū)崿F(xiàn)一個(gè)類缩幸。
- 因匿名內(nèi)部類為局部內(nèi)部類壹置,所以局部內(nèi)部類的所有限制都對(duì)其生效。
下面的例子看起來有點(diǎn)奇怪:
// 在方法中返回一個(gè)匿名內(nèi)部類
public class Parcel6 {
public static void main(String[] args) {
Parcel6 p = new Parcel6();
Contents c = p.cont();
}
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() {
return i;
}
}; // 在這里需要一個(gè)分號(hào)
}
}
cont()
方法將下面兩個(gè)動(dòng)作合并在一起:返回值的生成表谊,與表示這個(gè)返回值的類的定義钞护!
進(jìn)一步說,這個(gè)類是匿名的爆办,它沒有名字患亿。更糟的是,看起來是你正要?jiǎng)?chuàng)建一個(gè)Contents對(duì)象:
return new Contents()
但是押逼,在到達(dá)語句結(jié)束的分號(hào)之前步藕,你卻說:“等一等,我想在這里插入一個(gè)類的定義”:
return new Contents() {
private int i = 11;
public int value() {
return i;
}
};
這種奇怪的語法指的是:“創(chuàng)建一個(gè)繼承自Contents的匿名類的對(duì)象挑格×撸”通過new 表達(dá)式返回的引用被自動(dòng)向上轉(zhuǎn)型為對(duì)Contents的引用。匿名內(nèi)部類的語法是下面例子的簡(jiǎn)略形式:
class MyContents implements Contents {
private int i = 11;
public int value() {
return i;
}
}
return new MyContents();
在這個(gè)匿名內(nèi)部類中漂彤,使用了缺省的構(gòu)造器來生成Contents雾消。下面的代碼展示的是,如果你的基類需要一個(gè)有參數(shù)的構(gòu)造器挫望,應(yīng)該怎么辦:
public class Parcel7 {
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Wrapping w = p.wrap(10);
}
public Wrapping wrap(int x) {
// Base constructor call:
// Pass constructor argument.
return new Wrapping(x) {
public int value() {
return super.value() * 47;
}
}; // Semicolon required
}
}
只需簡(jiǎn)單地傳遞合適的參數(shù)給基類的構(gòu)造器即可立润,這里是將x 傳進(jìn)new Wrapping(x)。在匿名內(nèi)部類末尾的分號(hào)媳板,并不是用來標(biāo)記此內(nèi)部類結(jié)束(C++中是那樣)桑腮。實(shí)際上,它標(biāo)記的是表達(dá)式的結(jié)束蛉幸,只不過這個(gè)表達(dá)式正巧包含了內(nèi)部類罷了破讨。因此丛晦,這與別的地方使用的分號(hào)是一致的。
如果在匿名類中定義成員變量提陶,你同樣能夠?qū)ζ鋱?zhí)行初始化操作:
public class Parcel8 {
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania");
}
// Argument must be final to use inside
// anonymous inner class:
public Destination dest(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() {
return label;
}
};
}
}
如果你有一個(gè)匿名內(nèi)部類烫沙,它要使用一個(gè)在它的外部定義的對(duì)象,編譯器會(huì)要求其參數(shù)引用是final 型的隙笆,就像dest()
中的參數(shù)锌蓄。如果你忘記了,會(huì)得到一個(gè)編譯期錯(cuò)誤信息撑柔。如果只是簡(jiǎn)單地給一個(gè)成員變量賦值瘸爽,那么此例中的方法就可以了。但是乏冀,如果你想做一些類似構(gòu)造器的行為,該怎么辦呢洋只?在匿名類中不可能有已命名的構(gòu)造器(因?yàn)樗緵]名字A韭佟),但通過實(shí)例初始化识虚,你就能夠達(dá)到為匿名內(nèi)部類“制作”一個(gè)構(gòu)造器的效果肢扯。像這樣做:
abstract class Base {
public Base(int i) {
System.out.println("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
{
System.out.println("Inside instance initializer");
}
public void f() {
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}
在此例中,不要求變量i 一定是final 的担锤。因?yàn)閕 被傳遞給匿名類的基類的構(gòu)造器蔚晨,它并不會(huì)在匿名類內(nèi)部被直接使用。下例是帶實(shí)例初始化的“parcel”形式肛循。注意dest()的參數(shù)必須是final铭腕,因?yàn)樗鼈兪窃谀涿悆?nèi)被使用的。
public class Parcel9 {
public Destinationdest(final String dest, final float price) {
return new Destination() {
private int cost;
private String label = dest;
// Instance initialization for each object:
{
cost = Math.round(price);
if (cost > 100)
System.out.println("Over budget!");
}
public String readLabel() {
return label;
}
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.dest("Tanzania", 101.395F);
}
}
在實(shí)例初始化的部分多糠,你可以看到有一段代碼累舷,那原本是不能作為成員變量初始化的一部分而執(zhí)行的(就是if 語句)。所以對(duì)于匿名類而言夹孔,實(shí)例初始化的實(shí)際效果就是構(gòu)造器被盈。當(dāng)然它受到了限制:你不能重載實(shí)例初始化,所以你只能有一個(gè)構(gòu)造器搭伤。
從多層嵌套類中訪問外部
一個(gè)內(nèi)部類被嵌套多少層并不重要只怎,它能透明地訪問所有它所嵌入的外圍類的所有成員,如下所示:
class MNA {
private void f() {
}
class A {
private void g() {
}
public class B {
void h() {
g();
f();
}
}
}
}
public class MultiNestingAccess {
public static void main(String[] args) {
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
}
可以看到在MNA.A.B中怜俐,調(diào)用方法g()和f()不需要任何條件(即使它們被定義為private)身堡。這個(gè)例子同時(shí)展示了如何從不同的類里面創(chuàng)建多層嵌套的內(nèi)部類對(duì)象的基本語法∨睦穑“.new”語法能產(chǎn)生正確的作用域盾沫,所以你不必在調(diào)用構(gòu)造器時(shí)限定類名裁赠。
內(nèi)部類的重載問題
如果你創(chuàng)建了一個(gè)內(nèi)部類,然后繼承其外圍類并重新定義此內(nèi)部類時(shí)赴精,會(huì)發(fā)生什么呢佩捞?也就是說,內(nèi)部類可以被重載嗎蕾哟?這看起來似乎是個(gè)很有用的點(diǎn)子一忱,但是“重載”內(nèi)部類就好像它是外圍類的一個(gè)方法,其實(shí)并不起什么作用:
class Egg {
private Yolk y;
public Egg() {
System.out.println("New Egg()");
y = new Yolk();
}
protected class Yolk {
public Yolk() {
System.out.println("Egg.Yolk()");
}
}
}
public class BigEgg extends Egg {
public static void main(String[] args) {
new BigEgg();
}
public class Yolk {
public Yolk() {
System.out.println("BigEgg.Yolk()");
}
}
}
輸出結(jié)果為:
New Egg()
Egg.Yolk()
缺省的構(gòu)造器是編譯器自動(dòng)生成的谭确,這里是調(diào)用基類的缺省構(gòu)造器帘营。你可能認(rèn)為既然創(chuàng)建了BigEgg 的對(duì)象,那么所使用的應(yīng)該是被“重載”過的Yolk逐哈,但你可以從輸出中看到實(shí)際情況并不是這樣的芬迄。
這個(gè)例子說明,當(dāng)你繼承了某個(gè)外圍類的時(shí)候昂秃,內(nèi)部類并沒有發(fā)生什么特別神奇的變化禀梳。這兩個(gè)內(nèi)部類是完全獨(dú)立的兩個(gè)實(shí)體,各自在自己的命名空間內(nèi)肠骆。當(dāng)然算途,明確地繼承某個(gè)內(nèi)部類也是可以的:
class Egg2 {
private Yolk y = new Yolk();
public Egg2() {
System.out.println("New Egg2()");
}
public void insertYolk(Yolk yy) {
y = yy;
}
public void g() {
y.f();
}
protected class Yolk {
public Yolk() {
System.out.println("Egg2.Yolk()");
}
public void f() {
System.out.println("Egg2.Yolk.f()");
}
}
}
public class BigEgg2 extends Egg2 {
public BigEgg2() {
insertYolk(new Yolk());
}
public static void main(String[] args) {
Egg2 e2 = new BigEgg2();
e2.g();
}
public class Yolk extends Egg2.Yolk {
public Yolk() {
System.out.println("BigEgg2.Yolk()");
}
public void f() {
System.out.println("BigEgg2.Yolk.f()");
}
}
}
輸出結(jié)果為:
Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()
現(xiàn)在BigEgg2.Yolk
通過extends Egg2.Yolk
明確地繼承了此內(nèi)部類,并且重載了其中的方法蚀腿。Egg2
的insertYolk()
方法使得BigEgg2
將它自己的Yolk
對(duì)象向上轉(zhuǎn)型嘴瓤,然后傳遞給引用y。所以當(dāng)g()
調(diào)用y.f()
時(shí)莉钙,重載后的新版的f()被執(zhí)行廓脆。第二次調(diào)用Egg2.Yolk()
是BigEgg2.Yolk
的構(gòu)造器調(diào)用了其基類的構(gòu)造器〈庞瘢可以看到在調(diào)用g()
的時(shí)候狞贱,新版的f()
被調(diào)用了。
內(nèi)部類的繼承問題(thinking in java 3th p294)
因?yàn)閮?nèi)部類的構(gòu)造器要用到其外圍類對(duì)象的引用蜀涨,所以在你繼承一個(gè)內(nèi)部類的時(shí)候瞎嬉,事情變得有點(diǎn)復(fù)雜。問題在于厚柳,那個(gè)“秘密的”外圍類對(duì)象的引用必須被初始化氧枣,而在被繼承的類中并不存在要聯(lián)接的缺省對(duì)象。要解決這個(gè)問題别垮,需使用專門的語法來明確說清它們之間的關(guān)聯(lián):
class WithInner {
class Inner {
Inner() {
System.out.println("this is a constructor in WithInner.Inner");
}
;
}
}
public class InheritInner extends WithInner.Inner {
// ! InheritInner() {} // Won't compile
InheritInner(WithInner wi) {
wi.super();
System.out.println("this is a constructor in InheritInner");
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
輸出結(jié)果為:
this is a constructor in WithInner.Inner
this is a constructor in InheritInner
可以看到便监,InheritInner 只繼承自內(nèi)部類,而不是外圍類。但是當(dāng)要生成一個(gè)構(gòu)造器時(shí)烧董,缺省的構(gòu)造器并不算好毁靶,而且你不能只是傳遞一個(gè)指向外圍類對(duì)象的引用。此外逊移,你必須在構(gòu)造器內(nèi)使用如下語法:
enclosingClassReference.super();
關(guān)于Java回調(diào)函數(shù)
在Java中预吆,通常就是編寫另外一個(gè)類或類庫的人規(guī)定一個(gè)接口,然后你來實(shí)現(xiàn)這個(gè)接口胳泉,然后把這個(gè)接口的一個(gè)對(duì)象作為參數(shù)傳給別人的程序拐叉,別人的程序必要時(shí)就會(huì)通過那個(gè)接口來調(diào)用你編寫的函數(shù),執(zhí)行后續(xù)的一些方法,。
public class CallBack {
public static void main(String[] args) {
CallBack callBack = new CallBack();
callBack.toDoSomethings(100, new CallBackInterface() {
public void execute() {
System.out.println("我的請(qǐng)求處理成功了");
}
});
}
public void toDoSomethings(int a, CallBackInterface callBackInterface) {
long start = System.currentTimeMillis();
if (a > 100) {
callBackInterface.execute();
} else {
System.out.println("a < 100 不需要執(zhí)行回調(diào)方法");
}
long end = System.currentTimeMillis();
System.out.println("該接口回調(diào)時(shí)間 : " + (end - start));
}
}
public interface CallBackInterface {
void execute();
}
Java回調(diào)的實(shí)現(xiàn),是不是就是基于匿名內(nèi)部類實(shí)現(xiàn)的呢?答案是肯定的.
Java里的回調(diào),可以說是匿名內(nèi)部類精彩表演,優(yōu)美的編碼風(fēng)格,真是讓人陶醉~
參考
- Thinking in Java
- HTTPS://BLOG.CSDN.NET/HIKVISION_JAVA_GYH 《JAVA內(nèi)部類 (吐血之作)