1.內(nèi)部類(lèi)概述
一個(gè)類(lèi)的定義放在另一個(gè)類(lèi)的內(nèi)部浴栽,這個(gè)類(lèi)就叫做內(nèi)部類(lèi)。內(nèi)部類(lèi)是一種非常有用的特性,允許把一些邏輯相關(guān)的類(lèi)組織在一起最冰。
內(nèi)部類(lèi)大體上可以分為四種:
成員內(nèi)部類(lèi),靜態(tài)內(nèi)部類(lèi)稀火,局部?jī)?nèi)部類(lèi)暖哨,匿名內(nèi)部類(lèi)
首先按照順序了解一下這四種內(nèi)部類(lèi)的特點(diǎn)。
2.成員內(nèi)部類(lèi)
成員的內(nèi)部類(lèi)是最常見(jiàn)也是最基礎(chǔ)的內(nèi)部類(lèi)憾股,沒(méi)有那些花里胡哨的修飾:
//外部類(lèi)
public class Outer {
private String a = "a";
public int i = 1;
//內(nèi)部類(lèi)
class Inner{
private String b = "b";
public String c = "c";
public int getInt(){
return i; // 內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)變量
}
private String getString(){
return a + b + c; // 內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)的private變量
}
}
public String getParam(){
Inner inner = new Inner();
inner.b = "bb"; // 外部類(lèi)可以訪問(wèn)內(nèi)部類(lèi)的private變量
inner.c = "cc";
return inner.getInt() + inner.getString();
}
}
//測(cè)試類(lèi)
class Test {
public static void main(String[] args) {
Outer outer = new Outer();
System.out.println(outer.getParam()); // 輸出:1abbcc
Outer.Inner oi = outer.new Inner();
oi.c = "ccc";
//oi.b = "bbb"; 編譯失敗
System.out.println(oi.getInt()); // 輸出:1
//System.out.println(oi.getString()); 編譯失敗
}
}
從以上代碼可以總結(jié)出以下普通內(nèi)部類(lèi)的特點(diǎn):
- 內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)變量鹿蜀,包括私有變量
- 在外部類(lèi)中使用內(nèi)部類(lèi)的方法需要new一個(gè)內(nèi)部類(lèi)的對(duì)象。
- 在外部類(lèi)中可以訪問(wèn)到內(nèi)部類(lèi)的任何變量服球,包括私有變量茴恰。
- 在其它類(lèi)中創(chuàng)建內(nèi)部類(lèi)對(duì)象需要使用這樣的形式:
OuterClassName.InnerClassName oi = new OuterClassName().new InnerClassName();
在其他類(lèi)中定義的內(nèi)部類(lèi)對(duì)象不能訪問(wèn)內(nèi)部類(lèi)中的私有變量。除此之外斩熊,內(nèi)部類(lèi)中還可以通過(guò).this訪問(wèn)到外部類(lèi)的對(duì)象往枣。
public class Outer{
private int num ;
public Outer(){}
public Outer(int num){
this.num = num;
}
private class Inner{
public Outer getTest2(){
return Outer.this; // Outer.this指的是外部類(lèi)的對(duì)象
}
public Outer newTest2(){
return new Outer();
}
}
public static void main(String [] args){
Outer test = new Outer(5);
Outer.Inner inner = test.new Inner();
Outer o1 = inner.getTest2();
Outer o2 = inner.newTest2();
System.out.println(o1.num); // 5
System.out.println(o2.num); // 0 這個(gè)是新創(chuàng)建了一個(gè)外部類(lèi)對(duì)象
}
}
這里需要注意的是,通過(guò).this獲得對(duì)象是不同于new出來(lái)的對(duì)象的。使用.this后分冈,得到的是創(chuàng)建該內(nèi)部類(lèi)時(shí)使用的外圍類(lèi)對(duì)象的引用圾另,new則是創(chuàng)建了一個(gè)新的引用。
內(nèi)部類(lèi)是個(gè)編譯時(shí)期的概念雕沉,一旦編譯成功它和外部類(lèi)是兩個(gè)完全不同的類(lèi)集乔。(當(dāng)然內(nèi)外部類(lèi)之間還是有聯(lián)系的)
對(duì)于一個(gè)名為OuterClass的外圍類(lèi)和一個(gè)名為InnerClass的內(nèi)部類(lèi),在編譯成功后坡椒,會(huì)出現(xiàn)這樣兩個(gè)class文件:OuterClass.class和 OuterClass$InnerClass.class.
3.內(nèi)部類(lèi)與向上轉(zhuǎn)型
目前看來(lái)只是給類(lèi)中隱藏了一個(gè)類(lèi)扰路,java本身就自帶這種隱藏機(jī)制,只要給某個(gè)類(lèi)包訪問(wèn)權(quán)限就好倔叼,也是用不著創(chuàng)建內(nèi)部類(lèi)的吧汗唱。但是,當(dāng)一個(gè)內(nèi)部類(lèi)向上轉(zhuǎn)型為其基類(lèi)丈攒,尤其是轉(zhuǎn)型為一個(gè)接口時(shí)哩罪,內(nèi)部類(lèi)就有了用武之地。這是因?yàn)檫@樣的內(nèi)部類(lèi)(某個(gè)接口的實(shí)現(xiàn)類(lèi))對(duì)于其他人完全不可見(jiàn)且不可用巡验,所得到的只是指向基類(lèi)或者接口的引用际插,所以能很方便的隱藏實(shí)現(xiàn)細(xì)節(jié)。
以代碼為例:
//定義兩個(gè)接口
public interface Run{
void run();
}
public interface Eat{
void eat();
}
//外部類(lèi)
public class Person{
//這里是private
private class PEat implements Eat{
@Override
public void eat(){
System.out.println("eat with mouse");
}
}
//這里是protected
protected class PRun implements Run{
@Override
public void run() {
System.out.println("run with leg");
}
}
public Eat howToEat(){
return new PEat();//向上轉(zhuǎn)型
}
public Run houToRun(){
return new PRun(); //向上轉(zhuǎn)型
}
}
class TestPerson{
public static void main(String[] args) {
Person p = new Person();
Eat e = p.howToEat();
Run r = p.houToRun();
e.eat();
r.run();
Person.PRun ppr = p.new PRun();
//Person.PEat ppe = p.new PEat(); 編譯失敗,因?yàn)镻Eat是private的
}
}
從這段代碼可以看出显设,PEat是private腹鹉,所以除了Person(它的外部類(lèi)),沒(méi)有人能訪問(wèn)到它敷硅。
PRun是protected功咒,所以只有Person及其子類(lèi)、還有與Person同一個(gè)包中的類(lèi)能訪問(wèn)PRun绞蹦,其他類(lèi)不能訪問(wèn)力奋。
這意味著,如果客戶(hù)端想要訪問(wèn)這些成員是要受到限制的幽七,除此之外景殷,private內(nèi)部類(lèi)也不可被向下轉(zhuǎn)型,因?yàn)闊o(wú)法訪問(wèn)到它澡屡。所以private內(nèi)部類(lèi)給類(lèi)的設(shè)計(jì)者提供了一種途徑猿挚,通過(guò)這樣的方式可以完全阻止任何依賴(lài)于類(lèi)的編碼,并且隱藏了實(shí)現(xiàn)的細(xì)節(jié)驶鹉。此外绩蜻,對(duì)于客戶(hù)端程序員來(lái)說(shuō),由于不能訪問(wèn)任何新增加的室埋,原本不屬于公共接口的方法办绝,所以擴(kuò)展接口是沒(méi)有價(jià)值的伊约。這也給java編譯器提供了更高效代碼的機(jī)會(huì)。所以說(shuō)一般成員內(nèi)部類(lèi)孕蝉,都會(huì)定義成private.
普通的類(lèi)(非內(nèi)部類(lèi))屡律,不能聲明為private或protected,它們只給你被賦予public或者包訪問(wèn)權(quán)限降淮。
4.靜態(tài)內(nèi)部類(lèi)(嵌套類(lèi))
如果不需要內(nèi)部類(lèi)對(duì)象與其它外圍類(lèi)對(duì)象之間有聯(lián)系超埋,可以將內(nèi)部類(lèi)設(shè)置為static.這就是靜態(tài)內(nèi)部類(lèi),也稱(chēng)為嵌套類(lèi)佳鳖。普通的內(nèi)部類(lèi)對(duì)象隱式的保存了一個(gè)指向它外部類(lèi)引用的變量纳本,所以可以無(wú)條件的使用外部類(lèi)的變量,但是內(nèi)部類(lèi)用static修飾時(shí)腋颠,就不會(huì)有這個(gè)變量了。這也意味著:要?jiǎng)?chuàng)建嵌套類(lèi)的對(duì)象吓笙,并不需要其外圍類(lèi)的對(duì)象淑玫。
靜態(tài)內(nèi)部類(lèi)中不能訪問(wèn)非靜態(tài)的外部類(lèi)變量,但是可以訪問(wèn)外部類(lèi)的靜態(tài)變量面睛。
除此之外絮蒿,由于普通內(nèi)部類(lèi)的字段與方法,只能放在類(lèi)的外部層次上叁鉴,所以普通的內(nèi)部類(lèi)不能有static方法和static變量土涝,也不能在普通內(nèi)部類(lèi)中再包含靜態(tài)內(nèi)部類(lèi)。
但是靜態(tài)內(nèi)部類(lèi)可以包含所有這些東西:
public class Outer{
private int i =1;
public static String str = "str";
static class StaClass implements inter{
private String s = "s";
static int j = 2;
static int getInt(){
//return i+j幌墓;//訪問(wèn)不到i非靜態(tài)
return j;
}
private String getString(){
return str + s;
}
@Override
public void inter() {
System.out.println("inter");
}
static class InStaClass{
int x = 4;
static int y = 5;
static int getInt(){
//return x; // x是非靜態(tài)變量 不可以在靜態(tài)方法中使用
return y;
}
}
}
public inter getInter(){
return new StaClass();
}
}
class Test{
public static void main(String[] args) {
int a = Outer.StaClass.getInt();
//Outer.StaClass.getString(); // getString()為非靜態(tài)方法但壮,不能這樣調(diào)用
int b = Outer.StaClass.InStaClass.getInt();
System.out.println(a + "----" + b); // 輸出 2----5
//new Outer().new StaClass(); 編譯失敗 StaClass是靜態(tài)的
new Outer().getInter().inter(); // 輸出 inter
}
}
這里總結(jié)一下靜態(tài)內(nèi)部類(lèi)的要點(diǎn):
- 在靜態(tài)內(nèi)部類(lèi)可以存在靜態(tài)成員。
- 靜態(tài)內(nèi)部類(lèi)只能訪問(wèn)外圍類(lèi)的靜態(tài)成員變量和方法常侣,不能訪問(wèn)外圍類(lèi)的非靜態(tài)成員變量和方法蜡饵。
- 靜態(tài)內(nèi)部類(lèi)中的靜態(tài)方法,可以通過(guò)外部類(lèi).內(nèi)部類(lèi).方法名直接調(diào)用
- 靜態(tài)內(nèi)部類(lèi)在其它類(lèi)中不能被new出來(lái)胳施,new Outer().new StaClass()這樣是不行的溯祸,但是在外部類(lèi)中,可以new一個(gè)靜態(tài)內(nèi)部類(lèi)的對(duì)象舞肆。
- 靜態(tài)內(nèi)部類(lèi)中不能使用.this,因?yàn)闆](méi)有默認(rèn)的引用
5.局部?jī)?nèi)部類(lèi)
在方法里或者任何作用域里定義的內(nèi)部類(lèi)叫做局部?jī)?nèi)部類(lèi)焦辅。如前所示,你實(shí)現(xiàn)了某類(lèi)型的接口椿胯,于是你可以創(chuàng)建并返回對(duì)其的引用筷登,你需要這樣的引用。
你要解決一個(gè)復(fù)雜的問(wèn)題哩盲,想創(chuàng)建一個(gè)類(lèi)來(lái)輔助你的解決方案仆抵,但是又不希望這個(gè)類(lèi)是公共可用的跟继。
5.1一個(gè)定義在方法中的類(lèi)
public class Person {
public Eat howToEat(){
// 定義在方法中的類(lèi)
class EatWithMouth implements Eat{
@Override
public void eat() {
System.out.println("eat with mouth");
}
}
// 向上轉(zhuǎn)型
return new EatWithMouth();
}
public static void main(String[] args) {
Eat e = new Person().howToEat();
e.eat(); // eat with mouth
}
}
EatWithMouth是方法howToEat中的類(lèi)而不是Person中的類(lèi),你甚至可以在同一個(gè)子目錄下的任意一個(gè)類(lèi)中給任意一個(gè)內(nèi)部類(lèi)起EatWithMouth這個(gè)名字镣丑,而不會(huì)由命名沖突舔糖。當(dāng)然在howToEat方法外的任何地方都不能訪問(wèn)到EatWithMouth類(lèi)。但是這并不意味一旦howToEat方法執(zhí)行完畢莺匠,EatWithMouth類(lèi)就不能用了金吗。
5.2在任意作用域嵌入一個(gè)內(nèi)部類(lèi)
public class EveryBlock {
private String test(boolean b){
if (b){
class A{
private String a = "a";
String getString(){
return a;
}
}
A a = new A();
String s = a.getString();
return s;
}
//A a = new A(); 編譯失敗 超出作用域
return null;
}
public static void main(String[] args) {
EveryBlock eb = new EveryBlock();
System.out.println(eb.test(true)); // a
}
}
雖然這個(gè)類(lèi)A是在條件語(yǔ)句中,但是它的創(chuàng)建是無(wú)條件的趣竣,和其它類(lèi)一樣進(jìn)行編譯摇庙。僅僅只是作用域不同而已。通過(guò)這樣的方式遥缕,就解決了上面提到的第二個(gè)問(wèn)題:不希望這個(gè)類(lèi)是公用的卫袒。
6.匿名內(nèi)部類(lèi)
匿名內(nèi)部類(lèi)使用的地方有很多,看一個(gè)例子:
public class OuterClass{
public InnerClass getInnerClass(final int num,String str2){
return new InnerClass(){
int number = num + 3;
public int getNumber(){
return number;
}
};
}
public static void main(String[] args) {
OuterClass out = new OuterClass();
InnerClass inner = out.getInnerClass(2, "chengfan");
System.out.println(inner.getNumber());
}
}
interface InnerClass{
int getNumber();
}
這段代碼里有一段很奇怪的東西:
return new InnerClass(){
int number = num + 3;
public int getNumber(){
return number;
}
};
這不是一個(gè)接口嗎?沒(méi)錯(cuò)這就是匿名內(nèi)部類(lèi)单匣。事實(shí)上夕凝,這段代碼和下面的寫(xiě)法是等價(jià)的:
public class OuterCla {
class InnerClassImpl implements InnerClass{
int number ;
public InnerClassImpl(int num){
number = num + 3;
}
public int getNumber(){
return number;
}
}
public InnerClass getInnerClass(final int num){
return new InnerClassImpl(2);
}
public static void main(String[] args) {
OuterCla out = new OuterCla();
InnerClass inner = out.getInnerClass(2);
System.out.println(inner.getNumber());
}
}
這段代碼你應(yīng)該懂了。將兩段代碼一比較户秤,你大概也清楚了码秉,上面那樣寫(xiě),意思是創(chuàng)建了一個(gè)實(shí)現(xiàn)了InnerClass的匿名類(lèi)的對(duì)象鸡号。
匿名類(lèi)可以創(chuàng)建接口转砖、抽象類(lèi)、與普通類(lèi)的對(duì)象鲸伴。創(chuàng)建接口和抽象類(lèi)時(shí)府蔗,必須實(shí)現(xiàn)接口中所有方法。 創(chuàng)建匿名類(lèi)時(shí)汞窗,可以是無(wú)參的礁竞,也可以有參數(shù)的,但是如果這個(gè)參數(shù)要在匿名類(lèi)中使用杉辙,參數(shù)必須是final的模捂,如果不使用,可以不被final修飾(代碼中有體現(xiàn))蜘矢。
6.1為什么必須是final呢狂男?
首先我們知道在內(nèi)部類(lèi)編譯成功后,它會(huì)產(chǎn)生一個(gè)class文件品腹,該class文件與外部類(lèi)并不是同一class文件岖食,僅僅只保留對(duì)外部類(lèi)的引用。當(dāng)外部類(lèi)傳入的參數(shù)需要被內(nèi)部類(lèi)調(diào)用時(shí)舞吭,從java程序的角度來(lái)看是直接被調(diào)用:
public class OuterClass {
public void display(final String name,String age){
class InnerClass{
void display(){
System.out.println(name);
}
}
}
}
從上面代碼中看好像name參數(shù)應(yīng)該是被內(nèi)部類(lèi)直接調(diào)用泡垃?其實(shí)不然析珊,在java編譯之后實(shí)際的操作如下:
public class OuterClass$InnerClass {
public InnerClass(String name,String age){
this.InnerClass$name = name;
this.InnerClass$age = age;
}
public void display(){
System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );
}
}
所以從上面代碼來(lái)看,內(nèi)部類(lèi)并不是直接調(diào)用方法傳遞的參數(shù)蔑穴,而是利用自身的構(gòu)造器對(duì)傳入的參數(shù)進(jìn)行備份忠寻,自己內(nèi)部方法調(diào)用的實(shí)際上時(shí)自己的屬性而不是外部方法傳遞進(jìn)來(lái)的參數(shù)。
直到這里還沒(méi)有解釋為什么是final存和?
在內(nèi)部類(lèi)中的屬性和外部方法的參數(shù)兩者從外表上看是同一個(gè)東西奕剃,但實(shí)際上卻不是,所以他們兩者是可以任意變化的捐腿,也就是說(shuō)在內(nèi)部類(lèi)中我對(duì)屬性的改變并不會(huì)影響到外部的形參纵朋,而然這從程序員的角度來(lái)看這是不可行的,畢竟站在程序的角度來(lái)看這兩個(gè)根本就是同一個(gè)茄袖,如果內(nèi)部類(lèi)該變了操软,而外部方法的形參卻沒(méi)有改變這是難以理解和不可接受的,所以為了保持參數(shù)的一致性宪祥,就規(guī)定使用final來(lái)避免形參的不改變聂薪。
簡(jiǎn)單理解就是,拷貝引用品山,為了避免引用值發(fā)生改變,例如被外部類(lèi)的方法修改等烤低,而導(dǎo)致內(nèi)部類(lèi)得到的值不一致肘交,于是用final來(lái)讓該引用不可改變。
故如果定義了一個(gè)匿名內(nèi)部類(lèi)扑馁,并且希望它使用一個(gè)其外部定義的參數(shù)涯呻,那么編譯器會(huì)要求該參數(shù)引用是final的。
6.2匿名內(nèi)部類(lèi)小結(jié)
- 匿名內(nèi)部類(lèi)是沒(méi)有訪問(wèn)修飾符的腻要。
- 匿名內(nèi)部類(lèi)中不能存在任何的靜態(tài)成員變量和靜態(tài)方法复罐。
- new 匿名內(nèi)部類(lèi),這個(gè)類(lèi)首先是要存在的雄家。如果我們將那個(gè)InnerClass接口注釋掉效诅,就會(huì)出現(xiàn)編譯出錯(cuò)。
- 當(dāng)所在方法的形參需要被匿名內(nèi)部類(lèi)使用趟济,那么這個(gè)形參就必須為final乱投。
- 匿名內(nèi)部類(lèi)創(chuàng)建一個(gè)接口的引用時(shí)是沒(méi)有構(gòu)造方法的。但是可以通過(guò)構(gòu)造代碼塊來(lái)模擬構(gòu)造器顷编,像下面這樣:
public A getA(){
return new A(){
int num = 0;
String str;
{
str = "這是構(gòu)造代碼塊戚炫!";
System.out.println("str 已經(jīng)被初始化!");
}
}
}
但是當(dāng)匿名內(nèi)部類(lèi)創(chuàng)建一個(gè)抽象類(lèi)或者實(shí)體類(lèi)的引用時(shí)媳纬,如果有必要双肤,是可以定義構(gòu)造函數(shù)的:
public class Outer{
public static void main(String[] args) {
Outer outer = new Outer();
Inner inner = outer.getInner("Inner", "gz");
System.out.println(inner.getName());
}
public Inner getInner(final String name, String city) {
return new Inner(name, city) {
private String nameStr = name;
public String getName() {
return nameStr;
}
};
}
}
abstract class Inner {
Inner(String name, String city) {
System.out.println(city);
}
abstract String getName();
}
//注意這里的形參city施掏,由于它沒(méi)有被匿名內(nèi)部類(lèi)直接使用,而是被抽象類(lèi)Inner的構(gòu)造函數(shù)所使用茅糜,所以不必定義為final七芭。
匿名內(nèi)部類(lèi)不能是抽象的,它必須要實(shí)現(xiàn)繼承的類(lèi)或者實(shí)現(xiàn)的接口的所有抽象方法限匣。
事實(shí)上抖苦,創(chuàng)建匿名內(nèi)部類(lèi)要寫(xiě)的模板代碼太多了贪绘,java8中的lambda表達(dá)式能夠替代大部分的匿名類(lèi)跷坝,優(yōu)雅簡(jiǎn)潔代碼少,所以建議大家學(xué)習(xí)java8蹄葱,當(dāng)然峦筒,匿名內(nèi)部類(lèi)的知識(shí)還是要掌握的究西。
7.內(nèi)部類(lèi)的繼承
內(nèi)部類(lèi)的繼承,是指內(nèi)部類(lèi)被繼承物喷,普通類(lèi) extents 內(nèi)部類(lèi)卤材。而這時(shí)候代碼上要有點(diǎn)特別處理,具體看以下例子:
public class InheritInner extends WithInner.Inner{
//InheritInner();是不能通過(guò)編譯的峦失,要加上形參
InheritInner(WithInner wi){
wi.super();
}
public static void main(String[] args){
WithInner wi = new WithInner();
InheritInner obj = new InheritInner(wi);
}
}
class WithInner{
class Inner{
}
}
可以看到子類(lèi)的構(gòu)造函數(shù)里面要使用父類(lèi)的外部類(lèi)對(duì)象.super();而這個(gè)對(duì)象需要從外面創(chuàng)建并傳給形參扇丛。
8.多重繼承
內(nèi)部類(lèi)是除了接口外實(shí)現(xiàn)多重繼承的又一有利工具。
利用接口實(shí)現(xiàn)多重繼承我們都知道尉辑,就是一次性實(shí)現(xiàn)很多接口帆精。那么,如何利用內(nèi)部類(lèi)實(shí)現(xiàn)多重繼承呢隧魄?
//父親
public class Father {
public int strong(){
return 9;
}
}
//母親
public class Mother {
public int kind(){
return 8;
}
}
//兒子
public class Son {
// /**
* 內(nèi)部類(lèi)繼承Father類(lèi)
class Father_1 extends Father{
public int strong(){
return super.strong() + 1;
}
}
class Mother_1 extends Mother{
public int kind(){
return super.kind() - 2;
}
}
public int getStrong(){
return new Father_1().strong();
}
public int getKind(){
return new Mother_1().kind();
}
}
public class Test1 {
public static void main(String[] args) {
Son son = new Son();
System.out.println("Son 的Strong:" + son.getStrong());
System.out.println("Son 的kind:" + son.getKind());
}
}
//輸出
//Son 的Strong:10
//Son 的kind:6
兒子繼承了父親卓练,變得比父親更加強(qiáng)壯,同時(shí)也繼承了母親购啄,只不過(guò)溫柔指數(shù)下降了襟企。這里定義了兩個(gè)內(nèi)部類(lèi),他們分別繼承父親Father類(lèi)狮含、母親類(lèi)Mother類(lèi)顽悼,且都可以非常自然地獲取各自父類(lèi)的行為,這是內(nèi)部類(lèi)一個(gè)重要的特性:內(nèi)部類(lèi)可以繼承一個(gè)與外部類(lèi)無(wú)關(guān)的類(lèi)几迄,保證了內(nèi)部類(lèi)的獨(dú)立性表蝙,正是基于這一點(diǎn),多重繼承才會(huì)成為可能乓旗。
9. 內(nèi)部類(lèi)的原理簡(jiǎn)析
上面說(shuō)過(guò)這樣兩點(diǎn):
(1) 在外部類(lèi)的作用范圍內(nèi)可以任意創(chuàng)建內(nèi)部類(lèi)對(duì)象府蛇,即使內(nèi)部類(lèi)是私有的(私有內(nèi)部類(lèi))。即內(nèi)部類(lèi)對(duì)包圍它的外部類(lèi)可見(jiàn)屿愚。
(2) 在內(nèi)部類(lèi)中可以訪問(wèn)其外部類(lèi)的所有域汇跨,即使是私有域务荆。即外部類(lèi)對(duì)內(nèi)部類(lèi)可見(jiàn)。
問(wèn)題來(lái)了:上面兩個(gè)特點(diǎn)到底如何辦到的呢穷遂??jī)?nèi)部類(lèi)的"內(nèi)部"到底發(fā)生了什么函匕?
其實(shí),內(nèi)部類(lèi)是Java編譯器一手操辦的蚪黑。虛擬機(jī)并不知道內(nèi)部類(lèi)與常規(guī)類(lèi)有什么不同盅惜。 編譯器是如何瞞住虛擬機(jī)的呢?
我們用javac命令編譯一下下面的代碼:
class Outer{
//外部類(lèi)私有數(shù)據(jù)域
private int data=0;
//內(nèi)部類(lèi)
class Inner{
void print(){
//內(nèi)部類(lèi)訪問(wèn)外部私有數(shù)據(jù)域
System.out.println(data);
}
}
}
對(duì)內(nèi)部類(lèi)進(jìn)行編譯后發(fā)現(xiàn)有兩個(gè)class文件:Outer.class 忌穿、和OuterInner.class),而不是Outer類(lèi)的某一個(gè)域掠剑。 虛擬機(jī)運(yùn)行的時(shí)候屈芜,也是把Inner作為一種常規(guī)類(lèi)來(lái)處理的。
但問(wèn)題又來(lái)了朴译,即然是兩個(gè)常規(guī)類(lèi)井佑,為什么他們之間可以互相訪問(wèn)私有域那(最開(kāi)始提到的兩個(gè)內(nèi)部類(lèi)特點(diǎn))?這就要問(wèn)問(wèn)編譯器到底把這兩個(gè)類(lèi)編譯成什么東西了眠寿。
我們利用reflect反射機(jī)制來(lái)探查了一下內(nèi)部類(lèi)編譯后的情況:
//反編譯后的Outer$Inner
class Outer$Inner{
Outer$Inner(Outer,Outer$Inner); //包可見(jiàn)構(gòu)造器
private Outer$Inner(Outer); //私有構(gòu)造器將設(shè)置this$0域
final Outer this$0; //外部類(lèi)實(shí)例域this$0
}
好了躬翁,現(xiàn)在我們可以解釋上面的第一個(gè)內(nèi)部類(lèi)特點(diǎn)了: 為什么外部類(lèi)可以創(chuàng)建內(nèi)部類(lèi)的對(duì)象?并且內(nèi)部類(lèi)能夠方便的引用到外部類(lèi)對(duì)象?
首先編譯器將外盯拱、內(nèi)部類(lèi)編譯后放在同一個(gè)包中盒发。在內(nèi)部類(lèi)中附加一個(gè)包可見(jiàn)構(gòu)造器。這樣坟乾, 虛擬機(jī)運(yùn)行Outer類(lèi)中Inner in=new Inner(); 實(shí)際上調(diào)用的是包可見(jiàn)構(gòu)造:
new Outer$Inner(this,null)迹辐。
因此即使是private內(nèi)部類(lèi)蝶防,也會(huì)通過(guò)隱含的包可見(jiàn)構(gòu)造器成功的獲得私有內(nèi)部類(lèi)的構(gòu)造權(quán)限甚侣。
再者,Outer0间学,那么通過(guò)這個(gè)引用就可以方便的得到外部類(lèi)對(duì)象中可見(jiàn)成員殷费。
但是Outer類(lèi)中的private成員是如何訪問(wèn)到的呢?這就要看看下面Outer.class文件中的秘密了低葫。
class Outer{
static int access$0(Outer); //靜態(tài)方法详羡,返回值是外部類(lèi)私有域 data 的值。
}
現(xiàn)在可以解釋第二個(gè)特點(diǎn)了:為什么內(nèi)部類(lèi)可以引用外部類(lèi)的私有域嘿悬?
原因的關(guān)鍵就在編譯器在外圍類(lèi)中添加了靜態(tài)方法access0.access$0(Outer));
總結(jié)一下編譯器對(duì)類(lèi)中內(nèi)部類(lèi)做的手腳吧:
(1) 在內(nèi)部類(lèi)中偷偷摸摸的創(chuàng)建了包可見(jiàn)構(gòu)造器善涨,從而使外部類(lèi)獲得了創(chuàng)建權(quán)限窒盐。
(2) 在外部類(lèi)中偷偷摸摸的創(chuàng)建了訪問(wèn)私有變量的靜態(tài)方法草则,從而 使 內(nèi)部類(lèi)獲得了訪問(wèn)權(quán)限。這樣蟹漓,類(lèi)中定義的內(nèi)部類(lèi)無(wú)論私有炕横,公有,
靜態(tài)都可以被包圍它的外部類(lèi)所訪問(wèn)葡粒。
內(nèi)部類(lèi)我還是一知半解份殿,以后還是要回過(guò)頭來(lái)結(jié)合實(shí)例詳細(xì)了解。