內(nèi)部類基礎(chǔ)
在Java中,可以將一個類定義在另一個類里面或者一個方法里面该镣,這樣的類稱為內(nèi)部類廓译。廣泛意義上的內(nèi)部類一般來說包括這四種:成員內(nèi)部類
、局部內(nèi)部類
乙濒、匿名內(nèi)部類
和靜態(tài)內(nèi)部類
陕赃。下面就先來了解一下這四種內(nèi)部類的用法卵蛉。
成員內(nèi)部類
成員內(nèi)部類是最普通的內(nèi)部類,它的定義為位于另一個類的內(nèi)部么库,形如下面的形式:
class Circle {
double radius = 0;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //內(nèi)部類
public void drawSahpe() {
System.out.println("drawshape");
}
}
}
這樣看起來傻丝,類Draw像是類Circle的一個成員,Circle稱為外部類诉儒。成員內(nèi)部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態(tài)成員)葡缰。
class Circle {
private double radius = 0;
public static int count =1;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //內(nèi)部類
public void drawSahpe() {
System.out.println(radius); //外部類的private成員
System.out.println(count); //外部類的靜態(tài)成員
}
}
}
不過要注意的是,當(dāng)成員內(nèi)部類擁有和外部類同名的成員變量或者方法時忱反,會發(fā)生隱藏現(xiàn)象泛释,即默認(rèn)情況下訪問的是成員內(nèi)部類的成員。如果要訪問外部類的同名成員温算,需要以下面的形式進(jìn)行訪問:
外部類.this.成員變量
外部類.this.成員方法
雖然成員內(nèi)部類可以無條件地訪問外部類的成員怜校,而外部類想訪問成員內(nèi)部類的成員卻不是這么隨心所欲了。在外部類中如果要訪問成員內(nèi)部類的成員注竿,必須先創(chuàng)建一個成員內(nèi)部類的對象茄茁,再通過指向這個對象的引用來訪問:
class Circle {
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必須先創(chuàng)建成員內(nèi)部類的對象,再進(jìn)行訪問
}
private Draw getDrawInstance() {
return new Draw();
}
class Draw { //內(nèi)部類
public void drawSahpe() {
System.out.println(radius); //外部類的private成員
}
}
}
成員內(nèi)部類是依附外部類而存在的巩割,也就是說裙顽,如果要創(chuàng)建成員內(nèi)部類的對象,前提是必須存在一個外部類的對象宣谈。創(chuàng)建成員內(nèi)部類對象的一般方式如下:
public class Test {
public static void main(String[] args) {
//第一種方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必須通過Outter對象來創(chuàng)建
//第二種方式:
Outter.Inner inner1 = outter.getInnerInstance();
}
}
class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
}
內(nèi)部類可以擁有private訪問權(quán)限愈犹、protected訪問權(quán)限、public訪問權(quán)限及包訪問權(quán)限蒲祈。比如上面的例子甘萧,如果成員內(nèi)部類Inner用private修飾,則只能在外部類的內(nèi)部訪問梆掸,如果用public修飾扬卷,則任何地方都能訪問;如果用protected修飾酸钦,則只能在同一個包下或者繼承外部類的情況下訪問怪得;如果是默認(rèn)訪問權(quán)限,則只能在同一個包下訪問卑硫。這一點和外部類有一點不一樣徒恋,外部類只能被public和包訪問兩種權(quán)限修飾。我個人是這么理解的欢伏,由于成員內(nèi)部類看起來像是外部類的一個成員入挣,所以可以像類的成員一樣擁有多種權(quán)限修飾。
局部內(nèi)部類
局部內(nèi)部類是定義在一個方法或者一個作用域里面的類硝拧,它和成員內(nèi)部類的區(qū)別在于局部內(nèi)部類的訪問僅限于方法內(nèi)或者該作用域內(nèi)径筏。
class People{
public People() {
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部內(nèi)部類
int age =0;
}
return new Woman();
}
}
注意葛假,局部內(nèi)部類就像是方法里面的一個局部變量一樣,是不能有public滋恬、protected聊训、private以及static修飾符的。
匿名內(nèi)部類
匿名內(nèi)部類應(yīng)該是平時我們編寫代碼時用得最多的恢氯,在編寫事件監(jiān)聽的代碼時使用匿名內(nèi)部類不但方便带斑,而且使代碼更加容易維護(hù)。下面這段代碼是一段Android事件監(jiān)聽代碼:
scan_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
history_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
匿名內(nèi)部類是唯一一種沒有構(gòu)造器的類勋拟。正因為其沒有構(gòu)造器勋磕,所以匿名內(nèi)部類的使用范圍非常有限,大部分匿名內(nèi)部類用于接口回調(diào)指黎。匿名內(nèi)部類在編譯的時候由系統(tǒng)自動起名為Outter$1.class朋凉。一般來說,匿名內(nèi)部類用于繼承其他類或是實現(xiàn)接口醋安,并不需要增加額外的方法杂彭,只是對繼承方法的實現(xiàn)或是重寫。
靜態(tài)內(nèi)部類
靜態(tài)內(nèi)部類也是定義在另一個類里面的類吓揪,只不過在類的前面多了一個關(guān)鍵字static亲怠。靜態(tài)內(nèi)部類是不需要依賴于外部類的,這點和類的靜態(tài)成員屬性有點類似柠辞,并且它不能使用外部類的非static成員變量或者方法团秽,這點很好理解,因為在沒有外部類的對象的情況下叭首,可以創(chuàng)建靜態(tài)內(nèi)部類的對象习勤,如果允許訪問外部類的非static成員就會產(chǎn)生矛盾,因為外部類的非static成員必須依附于具體的對象焙格。
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
深入理解內(nèi)部類
為什么成員內(nèi)部類可以無條件訪問外部類的成員图毕?
在此之前,我們已經(jīng)討論過了成員內(nèi)部類可以無條件訪問外部類的成員眷唉,那具體究竟是如何實現(xiàn)的呢予颤?下面通過反編譯字節(jié)碼文件看看究竟。事實上冬阳,編譯器在進(jìn)行編譯的時候蛤虐,會將成員內(nèi)部類單獨(dú)編譯成一個字節(jié)碼文件。
雖然我們在定義的內(nèi)部類的構(gòu)造器是無參構(gòu)造器肝陪,編譯器還是會默認(rèn)添加一個參數(shù)驳庭,該參數(shù)的類型為指向外部類對象的一個引用,所以成員內(nèi)部類中的Outter this&0 指針便指向了外部類對象氯窍,因此可以在成員內(nèi)部類中隨意訪問外部類的成員嚷掠。從這里也間接說明了成員內(nèi)部類是依賴于外部類的捏检,如果沒有創(chuàng)建外部類的對象荞驴,則無法對Outter this&0引用進(jìn)行初始化賦值不皆,也就無法創(chuàng)建成員內(nèi)部類的對象了。
為什么局部內(nèi)部類和匿名內(nèi)部類只能訪問局部final變量熊楼?
如果局部變量的值在編譯期間就可以確定霹娄,則直接在匿名內(nèi)部里面創(chuàng)建一個拷貝。如果局部變量的值無法在編譯期間確定鲫骗,則通過構(gòu)造器傳參的方式來對拷貝進(jìn)行初始化賦值犬耻。
從上面可以看出,在run方法中訪問的變量a根本就不是test方法中的局部變量a执泰。這樣一來就解決了前面所說的 生命周期不一致的問題枕磁。但是新的問題又來了,既然在run方法中訪問的變量a和test方法中的變量a不是同一個變量术吝,當(dāng)在run方法中改變變量a的值的話计济,會出現(xiàn)什么情況?
對排苍,會造成數(shù)據(jù)不一致性沦寂,這樣就達(dá)不到原本的意圖和要求。
為了解決這個問題淘衙,java編譯器就限定必須將變量a限制為final變量传藏,不允許對變量a進(jìn)行更改(對于引用類型的變量,是不允許指向新的對象)彤守,這樣數(shù)據(jù)不一致性的問題就得以解決了毯侦。
靜態(tài)內(nèi)部類有特殊的地方嗎?
從前面可以知道具垫,靜態(tài)內(nèi)部類是不依賴于外部類的侈离,也就說可以在不創(chuàng)建外部類對象的情況下創(chuàng)建內(nèi)部類的對象。另外做修,靜態(tài)內(nèi)部類是不持有指向外部類對象的引用的霍狰,這個讀者可以自己嘗試反編譯class文件看一下就知道了,是沒有Outter this&0引用的饰及。
內(nèi)部類的使用場景和好處
為什么在Java中需要內(nèi)部類蔗坯?總結(jié)一下主要有以下四點:
1.每個內(nèi)部類都能獨(dú)立的繼承一個接口的實現(xiàn),所以無論外部類是否已經(jīng)繼承了某個(接口的)實現(xiàn)燎含,對于內(nèi)部類都沒有影響宾濒。內(nèi)部類使得多繼承的解決方案變得完整,
2.方便將存在一定邏輯關(guān)系的類組織在一起屏箍,又可以對外界隱藏绘梦。
3.方便編寫事件驅(qū)動程序
4.方便編寫線程代碼
個人覺得第一點是最重要的原因之一橘忱,內(nèi)部類的存在使得Java的多繼承機(jī)制變得更加完善。
常見的與內(nèi)部類相關(guān)的筆試面試題
1.根據(jù)注釋填寫(1)卸奉,(2)钝诚,(3)處的代碼
public class Test{
public static void main(String[] args){
// 初始化Bean1
(1)
bean1.I++;
// 初始化Bean2
(2)
bean2.J++;
//初始化Bean3
(3)
bean3.k++;
}
class Bean1{
public int I = 0;
}
static class Bean2{
public int J = 0;
}
}
class Bean{
class Bean3{
public int k = 0;
}
}
從前面可知,對于成員內(nèi)部類榄棵,必須先產(chǎn)生外部類的實例化對象凝颇,才能產(chǎn)生內(nèi)部類的實例化對象。而靜態(tài)內(nèi)部類不用產(chǎn)生外部類的實例化對象即可產(chǎn)生內(nèi)部類的實例化對象疹鳄。
創(chuàng)建靜態(tài)內(nèi)部類對象的一般形式為:
外部類類名.內(nèi)部類類名 xxx = new 外部類類名.內(nèi)部類類名()
創(chuàng)建成員內(nèi)部類對象的一般形式為:
外部類類名.內(nèi)部類類名 xxx = 外部類對象名.new 內(nèi)部類類名()
因此拧略,(1),(2)瘪弓,(3)處的代碼分別為:
Test test = new Test();
Test.Bean1 bean1 = test.new Bean1();
Test.Bean2 b2 = new Test.Bean2();
Bean bean = new Bean();
Bean.Bean3 bean3 = bean.new Bean3();
2.下面這段代碼的輸出結(jié)果是什么垫蛆?
public class Test {
public static void main(String[] args) {
Outter outter = new Outter();
outter.new Inner().print();
}
}
class Outter
{
private int a = 1;
class Inner {
private int a = 2;
public void print() {
int a = 3;
System.out.println("局部變量:" + a);
System.out.println("內(nèi)部類變量:" + this.a);
System.out.println("外部類變量:" + Outter.this.a);
}
}
}
3
2
1
最后補(bǔ)充一點知識:關(guān)于成員內(nèi)部類的繼承問題。一般來說腺怯,內(nèi)部類是很少用來作為繼承用的袱饭。但是當(dāng)用來繼承的話,要注意兩點:
1)成員內(nèi)部類的引用方式必須為 Outter.Inner.
2)構(gòu)造器中必須有指向外部類對象的引用瓢喉,并通過這個引用調(diào)用super()宁赤。這段代碼摘自《Java編程思想》
class WithInner {
class Inner{
}
}
class InheritInner extends WithInner.Inner {
// InheritInner() 是不能通過編譯的,一定要加上形參
InheritInner(WithInner wi) {
wi.super(); //必須有這句調(diào)用
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner obj = new InheritInner(wi);
}
}