小胖從官網(wǎng)出發(fā)础淤,研究下為什么我們需要些內(nèi)部類,內(nèi)部類的區(qū)別和聯(lián)系紫新。
思考三個問題:
(1)為什么需要內(nèi)部類项炼?靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類有什么區(qū)別皆辽;
(2)為什么內(nèi)部類可以無條件訪問外部類成員柑蛇;
(3)為什么jdk1.8之前,局部內(nèi)部類和匿名內(nèi)部類訪問局部變量或者方法參數(shù)需要加final修飾符驱闷?
1. 官網(wǎng)閱讀:
1.1 為什么需要內(nèi)部類
It is a way of logically grouping classes that are only used in one place: If a class is useful to only one other class, then it is logical to embed it in that class and keep the two together. Nesting such "helper classes" makes their package more streamlined.
簡化包配置:如果一個類只對另一個類有用耻台,將他們嵌套在一起是合理的。嵌套一些“有幫助的類”可以使得包更加簡化
It increases encapsulation: Consider two top-level classes, A and B, where B needs access to members of A that would otherwise be declared private. By hiding class B within class A, A's members can be declared private and B can access them. In addition, B itself can be hidden from the outside world.
增加了封裝:兩個頂級類A和B空另,B需要訪問A中聲明為private
的成員盆耽。
It can lead to more readable and maintainable code: Nesting small classes within top-level classes places the code closer to where it is used.
易讀和可維護:在頂級類中嵌套小類會使代碼更接近于使用它的位置。
1.2 為什么Java內(nèi)部類設計為靜態(tài)和非靜態(tài)
Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.
嵌套類一般分為兩類:靜態(tài)和非靜態(tài)扼菠。聲明static
的嵌套類稱為靜態(tài)嵌套類
摄杂。非靜態(tài)嵌套類稱為內(nèi)部類
。
A static nested class interacts with the instance members of its outer class (and other classes) just like any other top-level class. In effect, a static nested class is behaviorally a top-level class that has been nested in another top-level class for packaging convenience.
靜態(tài)嵌套類就像任何頂級類一樣 與 其外部類(其他的類)的實例成員交互循榆。事實上析恢,靜態(tài)內(nèi)部類在行為上就是一個頂級類,它嵌套在一個頂級類中以方便打包秧饮。
As with class methods and variables, a static nested class is associated with its outer class. And like static class methods, a static nested class cannot refer directly to instance variables or methods defined in its enclosing class: it can use them only through an object reference.
和靜態(tài)方法一樣映挂,靜態(tài)嵌套類不能直接引用封閉類中定義的實例變量和方法。只能通過對象的引用來使用它們盗尸。
靜態(tài)方法引用對象:
public class TestNest {
private String abc;
public String getAbc() {
return abc;
}
public static String nestSta() {
TestNest testNest = new TestNest();
return testNest.getAbc();
}
}
As with instance methods and variables, an inner class is associated with an instance of its enclosing class and has direct access to that object's methods and fields. Also, because an inner class is associated with an instance, it cannot define any static members itself.
內(nèi)部類可以直接訪問該對象的字段和方法柑船,由于內(nèi)部類和實例相關(guān)聯(lián),因此無法定義任何的靜態(tài)成員變量泼各。
那我們怎么理解呢鞍时?
靜態(tài)內(nèi)部類就是一個獨立的類。為什么使用靜態(tài)內(nèi)部類呢扣蜻?
比如A逆巍,B兩個類,B有點特殊莽使,雖然可以單獨存在锐极,但只能被A使用。那么此時應該怎么辦吮旅?把B并入到A里面溪烤,復雜性提高味咳,搞的A違反單一職責庇勃。如果B獨立,又可以被其他類依賴槽驶,不符合設計本意责嚷,不如將其變成A.B。其他類就不能使用B了掂铐。
而相比起來罕拂,非靜態(tài)的才是真正的內(nèi)部類揍异,對其外部類有一個引用。
1.3 序列化
Serialization of inner classes, including local and anonymous classes, is strongly discouraged. When the Java compiler compiles certain constructs, such as inner classes, it creates synthetic constructs;.....However, synthetic constructs can vary among different Java compiler implementations, which means that .class files can vary among different implementations as well. Consequently, you may have compatibility issues if you serialize an inner class and then deserialize it with a different JRE implementation.
強烈建議不要對內(nèi)部類進行序列化爆班。java編譯器編譯某些構(gòu)造(如內(nèi)部類)時衷掷,他會創(chuàng)建“合成構(gòu)造”。合成構(gòu)造在不同的java編譯器中變化柿菩。序列化內(nèi)部類戚嗅,然后使用不同的JRE實現(xiàn)反序列化,則可能存在兼容的問題枢舶。
2. 實戰(zhàn)內(nèi)部類
2.1 成員內(nèi)部類
類的成員
無條件訪問外部類
依賴外部類
多種訪問權(quán)限
2.1.1 內(nèi)部類的特點
成員內(nèi)部類中不能定義靜態(tài)變量
public class Outer {
private String name;
public Outer(String name) {
this.name = name;
}
//外部類使用內(nèi)部類的方法
public void write() {
Inner inner = new Inner();
inner.say();
System.out.println(inner.getInnnerName());
}
//成員內(nèi)部類
class Inner {
private String InnnerName;
public String getInnnerName() {
return InnnerName;
}
public void setInnnerName(String innnerName) {
InnnerName = innnerName;
}
public void say() {
System.out.println(name);
}
}
}
成員內(nèi)部類相當于類的成員變量懦胞,可以無條件訪問外部類的成員。但不過要注意的是凉泄,成員內(nèi)部類和外部內(nèi)部類擁有同名的成員方法或者方法時躏尉,要顯式的聲明,否則默認情況下訪問的是內(nèi)部類成員后众。
外部類.this.成員變量
外部類.this.成員方法
反編譯后的class文件:
public class Outer {
private String name;
public Outer(String name) {
this.name = name;
}
public void write() {
Outer.Inner inner = new Outer.Inner();
inner.say();
System.out.println(inner.getInnnerName());
}
class Inner {
private String InnnerName;
Inner() {
}
public String getInnnerName() {
return this.InnnerName;
}
public void setInnnerName(String innnerName) {
this.InnnerName = innnerName;
}
public void say() {
System.out.println(Outer.this.name);
}
}
}
外部類想訪問內(nèi)部類的成員必須先創(chuàng)建一個內(nèi)部類的對象胀糜,再通過指向這個對象的引用來訪問。
public void wirte() {
Inner inner = new Inner();
inner.say();
System.out.println(i + ":" + name);
}
于是外部類就可以訪問內(nèi)部類的所有成員了吼具!
2.1.2 如何創(chuàng)建內(nèi)部類
我們知道僚纷,成員內(nèi)部類是依賴于外部類而存在的。也就是說拗盒,想要創(chuàng)建成員內(nèi)部類怖竭,前提是有一個外部類對象。
方式一:
Outer outer = new Outer();
Inner inner = outer.new Inner();
方式二:
Outter.Inner inner1 = outter.getInnerInstance();
//getInnerInstance()方法
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
2.1.3 成員內(nèi)部類權(quán)限問題
成員內(nèi)部類可以擁有public
陡蝇、default
痊臭、protected
、private
權(quán)限登夫。
-
private
只能在外部類中被訪問广匙。 -
default
同一個包下訪問。 -
protected
同一個包下或繼承外部類的情況下恼策。 -
public
任何地方鸦致。
而外部類只有public
和default
兩種訪問權(quán)限。
2.1.4 小結(jié)
成員內(nèi)部類是依附于外部類存在的涣楷,并且和外部類的一個成員變量有相似之處分唾。內(nèi)部類可以無條件訪問外部類的成員、外部類需要內(nèi)部類的引用才能訪問狮斗、四種訪問權(quán)限绽乔。
請注意(下面兩個內(nèi)部類):jdk版本是1.8,看起來似乎編譯器取消了沒有聲明為
final
的變量或參數(shù)也可以在局部內(nèi)部類和匿名內(nèi)部類被訪問碳褒。但事實上是Java8引入了effectively final
概念折砸,被默認成為了final類型看疗。
2.2 局部內(nèi)部類
方法或作用域內(nèi)
局部變量
注意:局部內(nèi)部類和成員內(nèi)部類的區(qū)別就是局部內(nèi)部類的訪問僅限于方法內(nèi)或者該作用域內(nèi)。不能定義靜態(tài)變量睦授。
class Outer {
Object method() {
int locvar = 1;
System.out.println("1111");
class Inner {
void displayLocvar() {
System.out.println("locvar = " + locvar);
}
}
Object in = new Inner();
return in;
}
}
注意:局部內(nèi)部類更像一個局部變量两芳,是沒有訪問修飾符的。
2.3 匿名內(nèi)部類
一般來說去枷,匿名內(nèi)部類用于繼承其他類或是實現(xiàn)接口盗扇,并不需要增加額外的方法,只是對繼承的方法的實現(xiàn)或者重寫沉填。
匿名類的格式:
new Thread(new Runnable() {
@Override
public void run() {
//TODO
}
});
通過new XXX(){};
的方式創(chuàng)建了一個只能使用一次子類疗隶。
--
為什么叫做匿名內(nèi)部類呢?
匿名內(nèi)部類只能使用一次翼闹。他通常用來簡化代碼斑鼻。但是使用匿名內(nèi)部類還有一個前提條件:必須繼承一個父類(抽象類或具體類
)或是實現(xiàn)一個接口。
對于這個問題猎荠,首先我們應該明確的一點是對于匿名內(nèi)部類坚弱,它可能引用三種外部變量:
- 外部類的成員變量(所有的變量);
- 外部方法或作用域內(nèi)的局部變量关摇;
- 外部方法的參數(shù)荒叶;
實際上,只有第一種變量不需要聲明為final输虱。
原因:
首先些楣,在這里提出,網(wǎng)上的答案基本是:局部變量聲明周期和局部類的聲明周期不同宪睹。會導致內(nèi)部類失去引用造成錯誤3钭隆!亭病!等等鹅很,一個變量加上final就可以延長生命周期了嗎?那加上final豈不是會造成內(nèi)存短暫泄露罪帖?
正解:匿名內(nèi)部類和所有類一樣促煮,也是有自己的構(gòu)造函數(shù)的,只不過這個構(gòu)造函數(shù)是隱式的整袁。
加入final修飾符是為了保持內(nèi)部和外部的數(shù)據(jù)的一致性菠齿。
編譯前:
public class Outer {
String string = "";
void outerTest(final char ch){
final Integer integer = 1;
Inner inner = new Inner() {
void innerTest() {
System.out.println(string);
System.out.println(ch);
System.out.println(integer);
}
};
}
public static void main(String[] args) {
new Outer().outerTest(' ');
}
class Inner {
}
}
編譯后:
class Outer$1extends Outer.Inner
{
Outer$1(Outer paramOuter, char paramChar, Integer paramInteger)
{
super(paramOuter);
}
void innerTest()
{
System.out.println(this.this$0.string);
System.out.println(this.val$ch);
System.out.println(this.val$integer);
}
}
匿名內(nèi)部類之所以可以訪問局部變量,是因為在底層將這個局部變量的值傳入了匿名內(nèi)部類中葬项,并且以匿名內(nèi)部類的成員變量存在泞当,這個值的傳遞過程是通過匿名內(nèi)部類的構(gòu)造器完成的迹蛤。
我們可以看到匿名內(nèi)部類引用的局部變量
和方法參數(shù)
以及外部類的引用
都會被當做構(gòu)造函數(shù)的參數(shù)民珍。但是外部類的成員變量
是通過外部類的引用來訪問的襟士。
那么為什么匿名內(nèi)部類訪問外部類的成員變量,無需final修飾呢嚷量?
因為非靜態(tài)內(nèi)部類的對象保存了外部類對象的引用陋桂,因此內(nèi)部類對外部類成員變量的修改都會真實的反應到外部類實例本身,所以不需要final修飾蝶溶。
需要引入兩個知識點:
- 值傳遞和引用傳遞:基本類型作為參數(shù)傳遞時嗜历,傳遞的是值的引用,無論怎么改變這個拷貝抖所,原值是不會改變的梨州;當對象作為參數(shù)傳遞時,傳遞是對象引用的拷貝田轧,無論怎么改變新引用的指向暴匠,原引用是不會改變的(當然通過新引用改變對象的內(nèi)容,那么改變就是確確實實發(fā)生了)傻粘。
- final作用:被final修飾基本類型變量每窖,不可更改其值;當被final修飾引用變量弦悉,不可改變其指向窒典,只能改變對象的內(nèi)容。
于是稽莉,假設允許不對局部變量加final瀑志,當匿名內(nèi)部類里面嘗試改變外部基本類型的值的時候,或者改變外部引用變量的指向的時候污秆,表面上看起來是成功了后室,但是實際上并不會影響到外部的變量。
所以java就一刀切混狠,強制加上了final修飾岸霹。
2.4 靜態(tài)內(nèi)部類
我們上面知道,靜態(tài)內(nèi)部類是一個獨立的類将饺,不需要依賴外部類也能存在的贡避。所以靜態(tài)內(nèi)部類不能使用外部類非static成員變量或者方法。
因為外部類的非靜態(tài)成員必須依附于具體的對象予弧。
靜態(tài)內(nèi)部類的創(chuàng)建方法:
外部類.內(nèi)部類 引用名=new 外部類.內(nèi)部類();
public static void main(String[] args) {
//靜態(tài)內(nèi)部類的創(chuàng)建方法:
Outer.Inner in = new Outer.Inner();
in.say();
}
3. 問題解答
看到這里刮吧,我相信大家應該心里對問題也有了自己的答案。
靜態(tài)內(nèi)部類是不依附與外部類存在的掖蛤。而非靜態(tài)內(nèi)部類就是外部類的一個成員杀捻,是需要依附于外部類。
非靜態(tài)內(nèi)部類中含有構(gòu)造函數(shù)蚓庭,構(gòu)造函數(shù)中會將外部類的引用傳入致讥,所以仅仆,內(nèi)部類可以無條件訪問外部類成員。
為什么使用final和生命周期是無關(guān)的垢袱,主要是java為了保持內(nèi)部和外部變量的統(tǒng)一墓拜。
4. 內(nèi)部類常見面試題
- 根據(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
總結(jié):內(nèi)部類和外部類變量的訪問權(quán)限問題:
- 非靜態(tài)內(nèi)部類依賴于外部類對象的創(chuàng)建擎淤,所以,非靜態(tài)類中不能定義靜態(tài)變量秸仙。
- 非靜態(tài)內(nèi)部類的構(gòu)造方法中嘴拢,含有外部類的引用〖偶停可以直接使用所有的外部類成員席吴。
- 外部類不能直接使用非靜態(tài)內(nèi)部類的成員。除非創(chuàng)建內(nèi)部類對象捞蛋。
- 可以把靜態(tài)內(nèi)部類看做一個
獨立的靜態(tài)類
孝冒,所以不能直接使用一個類的實例成員。 -
匿名類必須繼承一個類(
抽象類或具體類
)或者實現(xiàn)一個接口
拟杉。new XXX(){};
就是一個內(nèi)部類庄涡。 - 只含有
private
構(gòu)造方法的類不能被繼承,所以可以使用protected
修飾類搬设,以達到讓子類繼承的目的穴店,此時,使用匿名內(nèi)部類的new XXX(){};
的方式就可以創(chuàng)建出一個XXX的子類對象拿穴。