Java內(nèi)部類一直是我感覺(jué)很生疏的地方,但是這個(gè)知識(shí)點(diǎn)在面試中被問(wèn)到的幾率還是很高,而且在最近的需求中也用到了靜態(tài)內(nèi)部類,索性就把這一塊又好好學(xué)習(xí)了下,趁熱打鐵,寫(xiě)一篇博客鞏固下
為什么會(huì)有內(nèi)部類
開(kāi)始學(xué)習(xí)內(nèi)部類的時(shí)候,最讓我懵比的就是為啥要設(shè)計(jì)一個(gè)內(nèi)部類,直接就新建一個(gè)普通的類豈不是更簡(jiǎn)單粗暴,但是大神既然設(shè)計(jì),那肯定存在即有理。
在我看來(lái)內(nèi)部類的作用主要包括以下幾點(diǎn):
- 更好的封裝性,因?yàn)閮?nèi)部類可以被訪問(wèn)限定符修飾,所以可以完全對(duì)外部其他類隱藏;
- 彌補(bǔ)Java單繼承的問(wèn)題,利用內(nèi)部類,可以在類里利用內(nèi)部類再繼承其他的抽象類或?qū)嶓w類,提高代碼的復(fù)用率;
- 內(nèi)部類可以直接訪問(wèn)所在作用域中的其他數(shù)據(jù),包括私有數(shù)據(jù),這是直接新建一個(gè)普通類做不到的;
- 內(nèi)部類可以簡(jiǎn)化代碼,在Android中最常用的就是匿名內(nèi)部類,例如注冊(cè)Button的Onclick時(shí),使用匿名內(nèi)部類簡(jiǎn)化代碼,不用再單獨(dú)建一個(gè)類來(lái)實(shí)現(xiàn)抽象類或接口
內(nèi)部類的分類
內(nèi)部類主要可以分為成員內(nèi)部類,局部?jī)?nèi)部類以及匿名內(nèi)部類。其中成員內(nèi)部類又包括靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類。
靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類的使用和區(qū)別
- 使用非靜態(tài)內(nèi)部類之前必須先實(shí)例化外部類,而靜態(tài)類不需要。這個(gè)很好理解,內(nèi)部類也是外部類的成員,靜態(tài)成員可以直接通過(guò)類來(lái)調(diào)用,而非靜態(tài)的必須通過(guò)實(shí)例來(lái)調(diào)用;
- 非靜態(tài)內(nèi)部類不能有靜態(tài)成員,而靜態(tài)內(nèi)部類可以有。因?yàn)槿缟弦稽c(diǎn)所說(shuō)非靜態(tài)類的使用必須依賴于外部類的實(shí)例,而靜態(tài)成員是類成員,不屬于任何實(shí)例,所以如果在非靜態(tài)內(nèi)部類聲明靜態(tài)成員就存在了矛盾,因此在非靜態(tài)內(nèi)部類里不能有靜態(tài)成員;
- 靜態(tài)內(nèi)部類可以訪問(wèn)外部類的靜態(tài)成員,但不能訪問(wèn)外部類的非靜態(tài)成員。這個(gè)也很好理解,因?yàn)殪o態(tài)內(nèi)部類不依賴外部類的實(shí)例化,所以不能訪問(wèn)外部類的非靜態(tài)成員;而非靜態(tài)內(nèi)部類可以訪問(wèn)外部類的所有成員;
還是用代碼說(shuō)最直白
public class ClassOuter {
private int a = 1;
private static int static_b = 2;
public int c =3;
public static int static_d = 4;
public class ClassInner {
private int x;
public void innerFun(){
System.out.println("內(nèi)部類方法");
System.out.println(a);
System.out.println(static_b);
System.out.println(c);
System.out.println(static_d);
}
}
public static class ClassStaticInner{
private int y;
private static int static_z;
public void innerFun() {
System.out.println("靜態(tài)類方法");
System.out.println(static_b);
System.out.println(static_d);
}
}
}
public class TestInnerClass {
public static void main(String[] args){
ClassOuter.ClassInner classInner = new ClassOuter(). new ClassInner();
ClassOuter.ClassStaticInner classStaticInner = new ClassOuter.ClassStaticInner();
classInner.innerFun();
classStaticInner.innerFun();
}
}
從demo中可以看出
- 代碼中非靜態(tài)內(nèi)部類ClassInner內(nèi)部只定義了一個(gè)非靜態(tài)的成員變量,若定義靜態(tài)的成員變量IDE會(huì)報(bào)錯(cuò);而在靜態(tài)內(nèi)部類ClassStaticInner 中既能定義靜態(tài)成員,也能定義非靜態(tài)成員;
- 非靜態(tài)內(nèi)部類ClassInner里既能訪問(wèn)外部類的靜態(tài)變量,也能訪問(wèn)非靜態(tài)變量,而靜態(tài)內(nèi)部類只能訪問(wèn)外部類的靜態(tài)變量;
- 使用非靜態(tài)內(nèi)部類時(shí)必須先new一個(gè)外部類,再通過(guò)外部類的實(shí)例去new內(nèi)部類,而靜態(tài)內(nèi)部類不需要外部類的實(shí)例;
局部?jī)?nèi)部類
局部?jī)?nèi)部類相對(duì)于上面所說(shuō)的兩種內(nèi)部類的區(qū)別就在于作用域發(fā)生了變化,它定義在方法中,或者某個(gè)作用域中,這個(gè)類的使用僅限于這個(gè)方法內(nèi)或作用域內(nèi)。
使用局部?jī)?nèi)部類的作用在于,當(dāng)我們?cè)谝粋€(gè)方法的內(nèi)部想要實(shí)現(xiàn)一個(gè)較為復(fù)雜的邏輯時(shí),想用一個(gè)類把他封裝起來(lái),但又不想暴露在別的地方,這時(shí)就可以使用局部?jī)?nèi)部類。
使用局部?jī)?nèi)部類時(shí)需要注意的是:
- 局部?jī)?nèi)部類不能使用訪問(wèn)修飾符修飾,它對(duì)外是完全隱藏的,只有在定義該局部?jī)?nèi)部類的方法里或作用域內(nèi)才能訪問(wèn);
- 局部?jī)?nèi)部類可以使用該類外部方法的局部變量,但是該局部變量必須被聲明為final
public class ClassOuter {
public void testInnerClass(final int a){
final int b = 1;
class LocalInnerClass{
int c;
public void test(){
System.out.println(a);
System.out.println(b);
System.out.print(c);
}
}
LocalInnerClass localInnerClass = new LocalInnerClass();
localInnerClass.test();
}
}
如demo中代碼所示,局部?jī)?nèi)部類LocalInnerClass聲明在testInnerClass方法內(nèi)部,則只能在該方法內(nèi)部使用,對(duì)方法以外的作用域是隱藏的;
在局部?jī)?nèi)部類中使用了方法中的參數(shù)a以及方法內(nèi)定義的局部變量b,這兩個(gè)變量在該局部?jī)?nèi)部類使用時(shí)都需要被聲明為final,否則IDE會(huì)報(bào)錯(cuò);而局部?jī)?nèi)部類自己的成員變量c的使用和普通類的成員變量使用一致,不需要聲明為final。
匿名內(nèi)部類
匿名內(nèi)部類和局部?jī)?nèi)部類比較類似,最大的區(qū)別就在于他沒(méi)有類名,所以稱為匿名內(nèi)部類。
匿名內(nèi)部類的特點(diǎn):
- 沒(méi)有訪問(wèn)修飾符修飾;
- 匿名內(nèi)部類需繼承一個(gè)抽象類或者實(shí)現(xiàn)一個(gè)接口;
- 匿名內(nèi)部類中不能定義任何靜態(tài)成員;
- 匿名內(nèi)部類沒(méi)有構(gòu)造方法,因?yàn)樗麤](méi)有類名;
- 和局部?jī)?nèi)部類一樣,當(dāng)他使用該類外部方法的局部變量時(shí),必須被聲明為final;
public class ClassOuter {
public void testInnerClass(final int a, final int c){
final int b = 1;
new testInterface() {
int d = c;
@Override
public void onClick() {
System.out.println(a);
System.out.println(b);
}
};
}
public interface testInterface{
void onClick();
}
}
在如上的demo中,通過(guò)testInterface接口實(shí)現(xiàn)了一個(gè)匿名內(nèi)部類,在匿名內(nèi)部類中訪問(wèn)外部的局部變量時(shí),都需要該變量聲明為final,否則IDE會(huì)報(bào)錯(cuò)
為什么局部和匿名內(nèi)部類在使用局部變量時(shí),該局部變量需要被聲明為final
這個(gè)問(wèn)題實(shí)際上是由于各自的生命周期不同所引起的。
因?yàn)槟涿麅?nèi)部類被創(chuàng)建后存在于堆中,而方法的局部變量在棧中,當(dāng)方法里的代碼執(zhí)行完成后,局部變量就會(huì)出棧,而匿名內(nèi)部類存在于堆中,還可能在其他地方被引用,這樣就引起了沖突,方法的局部變量已經(jīng)消失,而內(nèi)部類還需要用到這個(gè)變量;
所以為了解決這個(gè)問(wèn)題,編譯器就幫我們?cè)谀涿麅?nèi)部類中創(chuàng)建了一個(gè)局部變量的備份,內(nèi)部類直接使用的是該備份,備份的生命周期和內(nèi)部類是一樣的。但是如果局部變量發(fā)生變化,備份也要不斷發(fā)生變化,所以索性采取了一個(gè)比較妥協(xié)的辦法,把該變量聲明為final,這樣變量是只可讀的,就保證了內(nèi)部類和外部的該變量的值是一樣。
以上就是我學(xué)習(xí)內(nèi)部類時(shí)的一些理解,能力有限,有理解不對(duì)的地方歡迎指正
2019.3.6