一、內(nèi)部類的分類及區(qū)別
內(nèi)部類的表現(xiàn)形式為一個(gè)類可以在另一個(gè)類的內(nèi)部存在暮屡,其中,內(nèi)部包含其它類的類稱為外部類毅桃,被包含的類稱為內(nèi)部類褒纲。在如下的示例代碼中准夷,Outer就是外部類,Inner就是內(nèi)部類莺掠。
public class Outer {
public class Inner {
}
}
常見的內(nèi)部類主要分為以下四種形式:
- 成員內(nèi)部類
- 靜態(tài)內(nèi)部類
- 局部?jī)?nèi)部類
- 匿名內(nèi)部類
值得說明的是衫嵌,包含內(nèi)部類的外部類在編譯過后會(huì)成為兩個(gè)完全不同的類,分別是Outer.class
和Outer$Inner.class
彻秆。
1.1 成員內(nèi)部類
此時(shí)的內(nèi)部類相當(dāng)于是外部類的一個(gè)普通成員楔绞,只要當(dāng)外部類被實(shí)例化成一個(gè)對(duì)象后,借用該對(duì)象就可以對(duì)內(nèi)部類進(jìn)行操作唇兑。
通常成員內(nèi)部類表現(xiàn)為如下的代碼形式:
public class Outer {
private String outerValue = "outer";
// 成員內(nèi)部類酒朵,相當(dāng)于外部類的一個(gè)成員變量
public class Inner {
private String innerValue = "inner";
public void printInner(){
System.out.println("訪問外部類的私有屬性:" + outerValue);
System.out.println("訪問內(nèi)部類的私有屬性:" + innerValue);
}
}
}
public class Test {
public static void main(String[] args) {
// 01使用成員內(nèi)部類
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.printInner();
}
}
在使用成員內(nèi)部類的時(shí)候,需要記住以下幾點(diǎn):
- 內(nèi)部類可以直接使用外部類的任何屬性和方法扎附,即使是private的蔫耽。
- 外部類不能直接使用內(nèi)部類的屬性及方法,只能通過內(nèi)部類的對(duì)象來使用留夜。
- 內(nèi)部類中不能包含static的內(nèi)容匙铡。
- 內(nèi)部類中需要使用外部類的對(duì)象時(shí),使用
outer.this
來指代碍粥。
1.2 靜態(tài)內(nèi)部類
靜態(tài)內(nèi)部類又稱為嵌套內(nèi)部類鳖眼,是在外部類中具有static關(guān)鍵字聲明的內(nèi)部類,相當(dāng)于是外部類的類變量嚼摩。
通常靜態(tài)內(nèi)部類表現(xiàn)為如下的代碼形式:
public class Outer2 {
private String outerValue1 = "outerValue1";
private static String outerValue2 = "outerValue2";
// 靜態(tài)內(nèi)部類/嵌套類
public static class Inner2{
private String innerValue1 = "innerValue1";
public void printInfo(){
System.out.println("訪問外部類中的私有屬性:" + new Outer2().outerValue1);
System.out.println("訪問外部類中的靜態(tài)變量:" + Outer2.outerValue2);
System.out.println("訪問內(nèi)部類中的私有屬性:" + innerValue1);
}
}
}
public class Test {
public static void main(String[] args) {
// 02使用靜態(tài)內(nèi)部類/嵌套類
Outer2.Inner2 inner2 = new Outer2.Inner2();
inner2.printInfo();
}
在使用靜態(tài)內(nèi)部類的時(shí)候具帮,需要記住以下幾點(diǎn):
- 靜態(tài)內(nèi)部類的內(nèi)部不能直接訪問外部類的非靜態(tài)成員,只能通過實(shí)例化外部類的對(duì)象低斋,然后通過該對(duì)象進(jìn)行訪問蜂厅。
- 創(chuàng)建靜態(tài)內(nèi)部類的對(duì)象時(shí),不需要像成員內(nèi)部類一樣需要先行創(chuàng)建外部類的對(duì)象膊畴,而是直接使用內(nèi)部類的名稱即可創(chuàng)建掘猿。
1.3 局部?jī)?nèi)部類
局部?jī)?nèi)部類是出現(xiàn)在外部類的方法或者某個(gè)代碼塊的作用域范圍內(nèi)的內(nèi)部類,通常表現(xiàn)為如下的代碼形式:
public class Outer3 {
private String outerValue = "outerValue";
// 在方法內(nèi)部定義的局部?jī)?nèi)部類
public void outerInfo(){
final String methodValue = "methodValue";
class Inner3 {
String innerValue = "innerValue";
public void printInner3(){
System.out.println("訪問外部類的屬性:" + outerValue);
System.out.println("訪問外部類的方法的常量:" + methodValue);
System.out.println("訪問內(nèi)部類的屬性:" + innerValue);
}
}
Inner3 inner3 = new Inner3();
inner3.printInner3();
}
// 在作用域內(nèi)部定義的局部?jī)?nèi)部類
public void outerInfo2(boolean flag){
if(flag){
final String methodValue2 = "methodValue2";
class Inner4{
String innerValue2 = "innerValue2";
public void printInner4(){
System.out.println("訪問外部類的屬性:" + outerValue);
System.out.println("訪問外部類的方法的常量:" + methodValue2);
System.out.println("訪問內(nèi)部類的屬性:" + innerValue2);
}
}
Inner4 inner4 = new Inner4();
inner4.printInner4();
}else{
System.out.println("outerInfo2:else");
}
}
}
public class Test {
public static void main(String[] args) {
// 03使用局部?jī)?nèi)部類
Outer3 outer3 = new Outer3();
outer3.outerInfo();
outer3.outerInfo2(true);
}
在使用局部?jī)?nèi)部類的時(shí)候唇跨,需要記住以下幾點(diǎn):
- 局部?jī)?nèi)部類僅在作用域范圍內(nèi)有效稠通,超出范圍則不再可使用。
- 局部?jī)?nèi)部類中可以使用方法或者代碼塊級(jí)別的變量买猖,但是要求它們必須是final類型的常量改橘。
1.4 匿名內(nèi)部類
匿名內(nèi)部類沒有類名,通常被設(shè)計(jì)為只使用一次玉控,用以簡(jiǎn)化代碼的書寫飞主,通常表現(xiàn)為如下的代碼形式:
public class Outer4 {
private String outerValue = "outerValue";
// 在方法內(nèi)部使用匿名內(nèi)部類
public Inner5 getInnerInfo(final String name, String value){
final String methodValue = "methodValue";
return new Inner5(){
private String innerValue = name;
@Override
public String getInner5() {
System.out.println("訪問外部類的私有屬性:" + outerValue);
System.out.println("訪問外部類的方法的常量" + methodValue);
return innerValue;
}
};
}
}
interface Inner5 {
String getInner5();
}
public class Test {
public static void main(String[] args) {
// 04使用匿名內(nèi)部類
Outer4 outer4 = new Outer4();
Inner5 inner5 = outer4.getInnerInfo("zhangsan", "lisi");
System.out.println(inner5.getInner5());
}
}
在使用匿名內(nèi)部類的時(shí)候,需要記住以下幾點(diǎn):
- 匿名內(nèi)部類必須繼承一個(gè)父類或者實(shí)現(xiàn)一個(gè)接口。
- 在匿名內(nèi)部類中需要使用外部類的方法的屬性時(shí)碌识,要求該屬性必須為final類型的常量碾篡。
- 在匿名內(nèi)部類中需要使用外部類的方法的形參時(shí),要求該形參必須為final類型的常量筏餐。
二开泽、內(nèi)部類的作用及意義
學(xué)會(huì)使用內(nèi)部類,是掌握J(rèn)ava高級(jí)編程的一部分魁瞪,能讓你更優(yōu)雅地設(shè)計(jì)自己的程序結(jié)構(gòu)穆律。
2.1 為了實(shí)現(xiàn)封裝特性
我們?cè)诹硪黄恼?a href="http://www.reibang.com/p/7b08e274bc0b" target="_blank">《面向?qū)ο笕筇匦缘目偨Y(jié)》中有詳細(xì)介紹關(guān)于封裝的常用方法,其中就有使用內(nèi)部類的方法导俘。
外部類對(duì)內(nèi)部類的封裝主要表現(xiàn)為在其它地方想要使用內(nèi)部類的話需要受到外部類的限制峦耘。
public class Animal {
public class Dog {
}
public Dog getDog(){
return new Dog();
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
Dog d = a.new Dog();
Dog o = a.getDog();
// 無法直接實(shí)例化內(nèi)部類的對(duì)象,如下語句無法編譯通過
Dog g = new Dog();
}
}
在如上的例子中趟畏,內(nèi)部類雖然是public的贡歧,但是想要使用內(nèi)部類必須借助外部類的對(duì)象才能做到,這就是封裝赋秀。更進(jìn)一步利朵,如果你只希望在外部類中才能使用內(nèi)部類,而在其它地方都不能使用的話猎莲,只需要將內(nèi)部類改為private即可绍弟。
public class Animal {
private class Dog {
}
public Dog getDog(){
return new Dog();
}
public static void main(String[] args) {
Animal a = new Animal();
// 在外部類中可以使用private的內(nèi)部類,但是仍然只能通過外部類的對(duì)象才能訪問
Dog d = a.new Dog();
Dog o = a.getDog();
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
// 下面兩行語句都無法編譯通過著洼,因?yàn)镈og是private的內(nèi)部類樟遣,無法在外部類之外的其它任何地方使用
Dog d = a.new Dog();
Dog o = a.getDog();
}
}
2.2 為了完善多重繼承
在Java中一個(gè)子類只能繼承一個(gè)父類,如果想繼承多個(gè)父類身笤,只能將這些父類改為接口宣羊,然后子類實(shí)現(xiàn)多個(gè)接口寥袭。然而接口中的方法都是抽象的,是必須在子類中予以實(shí)現(xiàn)的,這就帶來了很大的不便等缀,那有沒有什么方法能讓子類同時(shí)繼承多個(gè)父類中的已經(jīng)實(shí)現(xiàn)好了的方法呢胃惜?當(dāng)然是通過內(nèi)部類除破,具體示例如下:
public class Animal {
public String bark(){
return "WOW";
}
}
public class Creature {
public String drink(){
return "water";
}
}
public class Mammal {
public class Dog extends Creature {
}
public class Cat extends Animal {
}
public String getDogAction(){
return new Dog().drink();
}
public String getCatAction(){
return new Cat().bark();
}
}
public class Test {
public static void main(String[] args) {
Mammal m = new Mammal();
// 對(duì)于Mammal來說并扇,其通過內(nèi)部類Dog和Cat間接地分別繼承了Creature和Animal中的方法
System.out.println(m.getCatAction());
System.out.println(m.getDogAction());
}
}
2.3 讓Java也有閉包
首先我們需要先理解什么是閉包?
閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)文搂∈实叮——百度百科
那么映射到Java里面,所謂的閉包就可以指在內(nèi)部類中可以訪問并使用外部類中的所有變量和方法的特性煤蹭。那么這樣的閉包究竟有什么用處呢笔喉?別急取视,我們先來看下面這樣一個(gè)例子:
public class Fruit {
private Integer num = 10;
private void addOneFruit(){
num++;
System.out.println("fruit增加到" + num + "個(gè)");
}
public class Child {
public void getOneFruit(){
num--;
System.out.println("fruit減少到" + num + "個(gè)");
}
public void putOneFruit(){
addOneFruit();
}
}
}
public class Test {
public static void main(String[] args) {
Fruit fruit = new Fruit();
Child child = fruit.new Child();
// 通過內(nèi)部類對(duì)象引用來操作外部類的私有屬性和方法(稱為閉包)
child.getOneFruit();
child.putOneFruit();
}
}
在上面的例子中,main
方法中原來是無論如何都不能訪問Fruit
的私有屬性num
和addOneFruit
的然遏,但是通過內(nèi)部類Child
贫途,這一切就成了可能吧彪,從而實(shí)現(xiàn)了閉包待侵。下面是列舉的關(guān)于閉包的好處:
- 使得環(huán)境外部(類、函數(shù))可以有一種途徑訪問環(huán)境內(nèi)的私有成員姨裸。
- 使得當(dāng)前環(huán)境的對(duì)象得以一直保留在內(nèi)存中秧倾,即使將該對(duì)象的引用置為null。
關(guān)于上述第一點(diǎn)傀缩,你是否會(huì)有這樣的疑問:為什么不使用setter和getter方法對(duì)環(huán)境內(nèi)的私有成員進(jìn)行訪問呢那先?
如果單從能否實(shí)現(xiàn)角度看,我們要訪問環(huán)境內(nèi)的私有成員赡艰,當(dāng)然可以通過setter和getter方法售淡,但是從面向?qū)ο缶幊探嵌瓤矗瑒t不能依靠這種方法慷垮。比如Fruit
的減少只能通過Child
的操作來進(jìn)行揖闸,這是模擬現(xiàn)實(shí)世界中的操作“吃”,只有Child
才能吃掉Fruit
達(dá)到讓Fruit
減少的目的料身,總不能讓Fruit自己吃自己吧汤纸。
關(guān)于上述的第二點(diǎn),我們可以改下main
方法進(jìn)行說明:
public class Test {
public static void main(String[] args) {
Fruit fruit = new Fruit();
Child child = fruit.new Child();
// 通過內(nèi)部類對(duì)象引用來操作外部類的私有熟悉和方法(稱為閉包)
child.getOneFruit();
child.putOneFruit();
// 即使將外部類的引用置為null芹血,但因其內(nèi)部類引用仍然存在贮泞,內(nèi)部類需要依賴外部類,所以外部類的對(duì)象仍然得以保留幔烛,不會(huì)被GC回收
fruit = null;
child.putOneFruit();
child.getOneFruit();
child = null;
// 內(nèi)部類的引用被置為null后啃擦,內(nèi)部類的對(duì)象和外部類的對(duì)象從此就沒有引用指向它們,很快會(huì)被GC回收
// 下面兩行編譯無法通過
child.getOneFruit();
child.putOneFruit();
}
}
三饿悬、常見的使用內(nèi)部類的例子
待補(bǔ)充......