深(shen)入理解Java:內(nei)部類
一、什么是內部類?
內部類是指在一個外部類的內部再定義一個類。內部類作為外部類的一個成員,并且依附于外部類而存在的。內部類可為靜態,可用protected和private修飾(而外部類只能使用public和缺省的包訪問權限)。內部類主要有以下幾類:成員內部類、局部內部類、靜態內部類、匿名內部類
二、內部類的共性
(1)、內(nei)部類仍然是一個獨(du)立的類,在(zai)編譯之后內(nei)部類會(hui)被(bei)編譯成獨(du)立的.class文件,但是前面冠以外(wai)部類的類名和$符號(hao) 。
(2)、內部類(lei)不能(neng)用普(pu)通的方式訪問。
(3)、內(nei)部(bu)類聲(sheng)明(ming)成靜態(tai)的,就(jiu)不能隨便的訪問外部(bu)類的成員變量了,此時內(nei)部(bu)類只能訪問外部(bu)類的靜態(tai)成員變量 。
(4)、外部類不能直接訪(fang)問(wen)內(nei)部類的的成(cheng)員,但可以(yi)通過內(nei)部類對象來(lai)訪(fang)問(wen)
內部(bu)(bu)類(lei)是外(wai)(wai)部(bu)(bu)類(lei)的(de)(de)一個成員,因此內部(bu)(bu)類(lei)可(ke)以(yi)自由地訪問(wen)外(wai)(wai)部(bu)(bu)類(lei)的(de)(de)成員變量,無論是否是private的(de)(de)。
因為當(dang)某個外圍類(lei)(lei)(lei)的對(dui)象(xiang)創建內(nei)部類(lei)(lei)(lei)的對(dui)象(xiang)時,此內(nei)部類(lei)(lei)(lei)會(hui)捕(bu)獲一個隱(yin)式引用(yong),它引用(yong)了實(shi)例化該內(nei)部對(dui)象(xiang)的外圍類(lei)(lei)(lei)對(dui)象(xiang)。通(tong)過這個指針,可(ke)以訪問外圍類(lei)(lei)(lei)對(dui)象(xiang)的全(quan)部狀態。
通(tong)(tong)過(guo)反編譯內部類的字節碼,分析(xi)之后主要(yao)是通(tong)(tong)過(guo)以(yi)下(xia)幾步做(zuo)到的:
1 編譯器自(zi)動(dong)為內(nei)部類(lei)添加一個成(cheng)員(yuan)變量(liang), 這個成(cheng)員(yuan)變量(liang)的類(lei)型和外部類(lei)的類(lei)型相同, 這個成(cheng)員(yuan)變量(liang)就是指(zhi)向外部類(lei)對象(xiang)的引(yin)用;
2 編譯器自(zi)動為內部類(lei)(lei)的(de)構造方法添加一個(ge)參數, 參數的(de)類(lei)(lei)型(xing)(xing)是(shi)外(wai)部類(lei)(lei)的(de)類(lei)(lei)型(xing)(xing), 在構造方法內部使(shi)用這(zhe)個(ge)參數為1中添加的(de)成員變量賦值;
3 在(zai)調(diao)用(yong)內部(bu)類(lei)的構造函(han)數初始化內部(bu)類(lei)對象時, 會默認傳入外部(bu)類(lei)的引用(yong)。
三、為什么需要內部類?
其主要原因有以下幾點:
-
內(nei)部類(lei)方(fang)法可以訪問(wen)該類(lei)定(ding)義所在的(de)作用域的(de)數據,包括私有(you)的(de)數據
-
內部(bu)類可以對(dui)同一個包中的(de)其他類隱藏(zang)起來,一般的(de)非內部(bu)類,是不允(yun)許(xu)有 private 與protected權限的(de),但(dan)內部(bu)類可以
-
可以(yi)實現多重繼(ji)承
-
當(dang)想(xiang)要(yao)定(ding)義一個回(hui)調函(han)數且(qie)不想(xiang)編寫大量代碼時,使用匿名(ming)內部(bu)類比較便捷
使用內部類最吸引人的(de)原因是:
每個(ge)(ge)內部(bu)類(lei)(lei)(lei)都能獨立地繼(ji)承(cheng)(cheng)(cheng)自一(yi)個(ge)(ge)(接口的(de)(de)(de)(de))實現(xian),所(suo)以(yi)無論外(wai)圍類(lei)(lei)(lei)是(shi)否已(yi)經繼(ji)承(cheng)(cheng)(cheng)了某個(ge)(ge)(接口的(de)(de)(de)(de))實現(xian),對于(yu)內部(bu)類(lei)(lei)(lei)都沒有影響(xiang)。大家(jia)都知(zhi)道Java只能繼(ji)承(cheng)(cheng)(cheng)一(yi)個(ge)(ge)類(lei)(lei)(lei),它的(de)(de)(de)(de)多重繼(ji)承(cheng)(cheng)(cheng)在我們(men)(men)沒有學習內部(bu)類(lei)(lei)(lei)之前是(shi)用(yong)接口來實現(xian)的(de)(de)(de)(de)。但使用(yong)接口有時候有很多不(bu)方(fang)(fang)便的(de)(de)(de)(de)地方(fang)(fang)。比(bi)如我們(men)(men)實現(xian)一(yi)個(ge)(ge)接口就必須(xu)實現(xian)它里面的(de)(de)(de)(de)所(suo)有方(fang)(fang)法(fa)。而(er)有了內部(bu)類(lei)(lei)(lei)就不(bu)一(yi)樣了。它可(ke)以(yi)使我們(men)(men)的(de)(de)(de)(de)類(lei)(lei)(lei)繼(ji)承(cheng)(cheng)(cheng)多個(ge)(ge)具體(ti)類(lei)(lei)(lei)或抽象類(lei)(lei)(lei)。
大家(jia)看下(xia)面的例子:
public class Example1 {
public String name(){
return "liutao";
}
}
public class Example2 {
public int age(){
return 25;
}
}
public class MainExample {
private class test1 extends Example1{
public String name(){
return super.name();
}
}
private class test2 extends Example2 {
public int age(){
return super.age();
}
}
public String name(){
return new test1().name();
}
public int age(){
return new test2().age();
}
public static void main(String args[]){
MainExample mi=new MainExample();
System.out.println("姓名:"+mi.name());
System.out.println("年齡:"+mi.age());
}
}
四、成員內部類:
即在一個類中直接定義的內部類, 成員內部類與普通的成員沒什么區別,可以與普通成員一樣進行修飾和限制。成員內部類不能含有static的變量和方法。
public class Outer {
private static int i = 1;
private int j = 10;
private int k = 20;
public static void outer_f1() {}
public void outer_f2() {}
// 成員內部類中,不(bu)能定義靜態(tai)成員
// 成(cheng)員內部類(lei)中,可以(yi)訪問外部類(lei)的所(suo)有成(cheng)員
class Inner {
// static int inner_i = 100;//內(nei)部類中不(bu)允許定義靜態變量
int j = 100; // 內部類和外部類的實例變量可以(yi)共存
int inner_i = 1;
void inner_f1() {
System.out.println(i);
// 在內部類中(zhong)訪問內部類自己的變量(liang)直接用變量(liang)名
System.out.println(j);
// 在內(nei)部(bu)類中訪問內(nei)部(bu)類自己(ji)的變量也可以用this.變量名
System.out.println(this.j);
// 在內(nei)部(bu)(bu)類中(zhong)訪問外部(bu)(bu)類中(zhong)與內(nei)部(bu)(bu)類同名(ming)的實例變量用外部(bu)(bu)類名(ming).this.變量名(ming)
System.out.println(Outer.this.j);
// 如果內部類(lei)中沒有與外部類(lei)同(tong)名的變(bian)量,則可以直接(jie)用變(bian)量名訪問外部類(lei)變(bian)量
System.out.println(k);
outer_f1();
outer_f2();
}
}
// 外部(bu)類(lei)的(de)非靜態方(fang)法訪問成員內部(bu)類(lei)
public void outer_f3() {
Inner inner = new Inner();
inner.inner_f1();
}
// 外(wai)部(bu)類(lei)(lei)的(de)靜態(tai)方法(fa)訪(fang)問(wen)成員(yuan)內(nei)部(bu)類(lei)(lei),與在外(wai)部(bu)類(lei)(lei)外(wai)部(bu)訪(fang)問(wen)成員(yuan)內(nei)部(bu)類(lei)(lei)一樣
public static void outer_f4() {
// step1 建立外部類對象
Outer out = new Outer();
// step2 根據外部類對(dui)象建立(li)內部類對(dui)象
Inner inner = out.new Inner();
// step3 訪問內(nei)部類(lei)的(de)方法(fa)
inner.inner_f1();
}
public static void main(String[] args) {
//outer_f4();//該(gai)語句的輸出(chu)(chu)結果和下面三條語句的輸出(chu)(chu)結果一樣(yang)
// 如果(guo)要直接創(chuang)建內部類的對(dui)象,不能想當然地(di)認為(wei)只需加上外圍類Outer的名字,
// 就可以按照通常的(de)樣子生成內部類的(de)對象,而(er)是必須(xu)使(shi)用此外圍類的(de)一個對象來
// 創建其內部類的一個對象:
// Outer.Inner outin = out.new Inner()
// 因此,除非你(ni)已經(jing)有了外(wai)圍(wei)類(lei)(lei)的一個對象,否則不可能生成內(nei)部類(lei)(lei)的對象。因為此
// 內部(bu)(bu)類的(de)(de)對(dui)象會(hui)悄(qiao)(qiao)悄(qiao)(qiao)地鏈接(jie)到創建它(ta)的(de)(de)外圍類的(de)(de)對(dui)象。如果你用的(de)(de)是(shi)靜態的(de)(de)內部(bu)(bu)類,
// 那就不需要對其外圍類對象的(de)引用。
Outer out = new Outer();
Outer.Inner outin = out.new Inner();
outin.inner_f1();
}
}
六、局部內部類:
在方法中定義的(de)內(nei)(nei)部類稱為局部內(nei)(nei)部類。與局部變量類似,局部內(nei)(nei)部類不能有(you)(you)訪(fang)問說(shuo)明符(fu),因(yin)為它不是外圍類的(de)一部分,但是它可以訪(fang)問當前代碼塊內(nei)(nei)的(de)常量,和此外圍類所有(you)(you)的(de)成員。
需要注意的是:
(1)、局(ju)部內部類(lei)只能在定(ding)義該(gai)內部類(lei)的方(fang)法內實例化,不(bu)可以在此(ci)方(fang)法外對其實例化。
(2)、局部(bu)內部(bu)類(lei)對象(xiang)不能使用該(gai)內部(bu)類(lei)所在方法的非final局部(bu)變量。
具體原因等下再說(shuo)
public class Outer {
private int s = 100;
private int out_i = 1;
public void f(final int k) {
final int s = 200;
int i = 1;
final int j = 10;
// 定(ding)義在方法內部
class Inner {
int s = 300;// 可以定義與外(wai)部(bu)類同名(ming)的(de)變量
// static int m = 20;//不可以定義靜態變量
Inner(int k) {
inner_f(k);
}
int inner_i = 100;
void inner_f(int k) {
// 如果內(nei)部(bu)類(lei)沒有與(yu)外部(bu)類(lei)同名的變量(liang),在(zai)內(nei)部(bu)類(lei)中(zhong)可以直接訪問外部(bu)類(lei)的實例(li)變量(liang)
System.out.println(out_i);
// 可以訪問外(wai)部(bu)(bu)類的局部(bu)(bu)變(bian)(bian)量(liang)(即方法內的變(bian)(bian)量(liang)),但(dan)是變(bian)(bian)量(liang)必須是final的
System.out.println(j);
// System.out.println(i);
// 如果內(nei)部(bu)(bu)(bu)類(lei)(lei)中(zhong)有與(yu)外部(bu)(bu)(bu)類(lei)(lei)同名(ming)的(de)變量,直接(jie)用變量名(ming)訪(fang)問的(de)是內(nei)部(bu)(bu)(bu)類(lei)(lei)的(de)變量
System.out.println(s);
// 用this.變量名訪問(wen)的也(ye)是內部類變量
System.out.println(this.s);
// 用外部(bu)類(lei)名(ming).this.內部(bu)類(lei)變量(liang)名(ming)訪問的(de)是外部(bu)類(lei)變量(liang)
System.out.println(Outer.this.s);
}
}
new Inner(k);
}
public static void main(String[] args) {
// 訪(fang)問局部(bu)內部(bu)類必須先有外部(bu)類對象
Outer out = new Outer();
out.f(3);
}
}
七、靜態內部類(嵌套類):
如果你(ni)不需要(yao)內部(bu)類對(dui)象(xiang)與其(qi)外圍(wei)類對(dui)象(xiang)之間有(you)聯系,那你(ni)可(ke)以(yi)將內部(bu)類聲明為static。這(zhe)通常稱(cheng)為嵌(qian)套類(nested class)。想要(yao)理(li)解static應(ying)用(yong)于內部(bu)類時的(de)含(han)義,你(ni)就(jiu)(jiu)必須記住,普通的(de)內部(bu)類對(dui)象(xiang)隱含(han)地保(bao)存了一個引用(yong),指(zhi)向創建它的(de)外圍(wei)類對(dui)象(xiang)。然而,當內部(bu)類是static的(de)時,就(jiu)(jiu)不是這(zhe)樣了。嵌(qian)套類意味著:
1. 要創建(jian)嵌套類的對(dui)(dui)象,并不(bu)需要其外圍類的對(dui)(dui)象。
2. 不能(neng)從嵌套(tao)類的(de)(de)對(dui)象(xiang)中訪問(wen)非(fei)靜態(tai)的(de)(de)外(wai)圍類對(dui)象(xiang)。
public class Outer {
private static int i = 1;
private int j = 10;
public static void outer_f1() {}
public void outer_f2() {}
// 靜態內部類可以用public,protected,private修飾
// 靜(jing)態內部類(lei)中可以定(ding)義(yi)靜(jing)態或者(zhe)非(fei)靜(jing)態的成員
private static class Inner {
static int inner_i = 100;
int inner_j = 200;
static void inner_f1() {
// 靜(jing)態(tai)內部類只能訪問外部類的靜(jing)態(tai)成員(yuan)(包(bao)括靜(jing)態(tai)變量和靜(jing)態(tai)方(fang)法)
System.out.println("Outer.i" + i);
outer_f1();
}
void inner_f2() {
// 靜(jing)(jing)態內部類不(bu)能訪問外部類的非(fei)(fei)靜(jing)(jing)態成(cheng)員(包括非(fei)(fei)靜(jing)(jing)態變量和非(fei)(fei)靜(jing)(jing)態方法)
// System.out.println("Outer.i"+j);
// outer_f2();
}
}
public void outer_f3() {
// 外部(bu)類(lei)訪問(wen)內(nei)部(bu)類(lei)的(de)靜態(tai)(tai)成(cheng)員(yuan):內(nei)部(bu)類(lei).靜態(tai)(tai)成(cheng)員(yuan)
System.out.println(Inner.inner_i);
Inner.inner_f1();
// 外部類訪問內部類的非靜態(tai)成(cheng)員:實例化內部類即(ji)可
Inner inner = new Inner();
inner.inner_f2();
}
public static void main(String[] args) {
new Outer().outer_f3();
}
}
生成(cheng)一個靜態內(nei)部(bu)類(lei)不(bu)(bu)(bu)需(xu)要外部(bu)類(lei)成(cheng)員:這(zhe)是(shi)(shi)靜態內(nei)部(bu)類(lei)和成(cheng)員內(nei)部(bu)類(lei)的(de)(de)區別。靜態內(nei)部(bu)類(lei)的(de)(de)對象可以直接(jie)(jie)生成(cheng):Outer.Inner in = new Outer.Inner();而不(bu)(bu)(bu)需(xu)要通(tong)過生成(cheng)外部(bu)類(lei)對象來(lai)生成(cheng)。這(zhe)樣實際上使(shi)靜態內(nei)部(bu)類(lei)成(cheng)為(wei)了一個頂級(ji)類(lei)(正常情(qing)況下,你不(bu)(bu)(bu)能在接(jie)(jie)口(kou)(kou)內(nei)部(bu)放置(zhi)任(ren)何代碼,但嵌套類(lei)可以作為(wei)接(jie)(jie)口(kou)(kou)的(de)(de)一部(bu)分,因為(wei)它是(shi)(shi)static 的(de)(de)。只是(shi)(shi)將嵌套類(lei)置(zhi)于接(jie)(jie)口(kou)(kou)的(de)(de)命名空(kong)間內(nei),這(zhe)并不(bu)(bu)(bu)違反接(jie)(jie)口(kou)(kou)的(de)(de)規則)
八、匿名內部類:
- 只用到類的一個實例。
- 類(lei)在定義(yi)后馬上用(yong)到。
- 類非常小(SUN推薦是在4行代碼以下(xia))
- 給類命(ming)名并不會導(dao)致你的代碼更容(rong)易被理解。
-
匿名內部類不(bu)能有構造方法(fa)。
-
匿名(ming)內(nei)部類(lei)不(bu)能定義任何靜態成員、方法和類(lei)。
-
匿名內部類(lei)不能是public,protected,private,static。
-
只能創建匿名(ming)內部(bu)類的一個實例。
-
一個匿名內部類(lei)一定是在new的(de)后面,用其隱含實現(xian)一個接口或實現(xian)一個類(lei)。
-
因(yin)匿(ni)名內部(bu)類為局(ju)部(bu)內部(bu)類,所以局(ju)部(bu)內部(bu)類的所有限制都對其(qi)生(sheng)效(xiao)。
public class Parcel7 {
public Wrapping wrap(int x) {
// Base constructor call:
return new Wrapping(x) { // Pass constructor argument.
public int value() {
return super.value() * 47;
}
}; // Semicolon required
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Wrapping w = p.wrap(10);
}
}
public class Parcel8 {
// Argument must be final to use inside
// anonymous inner class:
public Destination dest(final String name, String city) {
return new Destination(name, city) {
private String label = name;
public String getName() {
return label;
}
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania", "gz");
}
abstract class Destination {
Destination(String name, String city) {
System.out.println(city);
}
abstract String getName();
}
}
九、內部類的重載問題
class Egg {
private Yolk y;
protected class Yolk {
public Yolk() {
System.out.println("Egg.Yolk()");
}
}
public Egg() {
System.out.println("New Egg()");
y = new Yolk();
}
}
public class BigEgg extends Egg {
public class Yolk {
public Yolk() {
System.out.println("BigEgg.Yolk()");
}
}
public static void main(String[] args) {
new BigEgg();
}
}
class Egg2 {
protected class Yolk {
public Yolk() {
System.out.println("Egg2.Yolk()");
}
public void f() {
System.out.println("Egg2.Yolk.f()");
}
}
private Yolk y = new Yolk();
public Egg2() {
System.out.println("New Egg2()");
}
public void insertYolk(Yolk yy) {
y = yy;
}
public void g() {
y.f();
}
}
public class BigEgg2 extends Egg2 {
public class Yolk extends Egg2.Yolk {
public Yolk() {
System.out.println("BigEgg2.Yolk()");
}
public void f() {
System.out.println("BigEgg2.Yolk.f()");
}
}
public BigEgg2() {
insertYolk(new Yolk());
}
public static void main(String[] args) {
Egg2 e2 = new BigEgg2();
e2.g();
}
}
十、內部類的繼承問題
class WithInner {
class Inner {
Inner(){
System.out.println("this is a constructor in WithInner.Inner");
};
}
}
public class InheritInner extends WithInner.Inner {
// ! InheritInner() {} // Won't compile
InheritInner(WithInner wi) {
wi.super();
System.out.println("this is a constructor in InheritInner");
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
為什么非靜態內部類中不能有static修飾的屬性,但卻可以有常量?
如:
public class InnerClassDemo{
int x;
class A{
static int a = 0;//這樣寫是(shi)不合(he)法的.
static final int b=0;//這樣寫是合法的
}
}
定(ding)義一個靜(jing)態(tai)(tai)(tai)的域(yu)或者(zhe)方法,要求(qiu)在靜(jing)態(tai)(tai)(tai)環境(jing)或者(zhe)頂層環境(jing),即(ji)如果加(jia)上 static class A變成靜(jing)態(tai)(tai)(tai)內部(bu)(bu)類(lei)就ok非靜(jing)態(tai)(tai)(tai)內部(bu)(bu)類(lei) 依(yi)賴于一個外部(bu)(bu)類(lei)對(dui)象(xiang),而靜(jing)態(tai)(tai)(tai)域(yu)/方法是不(bu)(bu)依(yi)賴與對(dui)象(xiang)——僅與類(lei)相(xiang)關(guan)(細說(shuo)了,就是加(jia)載靜(jing)態(tai)(tai)(tai)域(yu)時,根本(ben)沒有外部(bu)(bu)類(lei)對(dui)象(xiang))因此,非靜(jing)態(tai)(tai)(tai)內部(bu)(bu)類(lei)中不(bu)(bu)能定(ding)義靜(jing)態(tai)(tai)(tai)域(yu)/方法,編譯過不(bu)(bu)了。
而常(chang)(chang)量之所(suo)以可以(不論有無static),因為java在(zai)編譯(yi)期(qi)就(jiu)確(que)定所(suo)有常(chang)(chang)量,放到所(suo)謂的(de)常(chang)(chang)量池當中。常(chang)(chang)量的(de)機制和(he)普通變量不一樣
為什么匿名內部類和局部內部類只能訪問final變量
參(can)考:
