內部類簡單來說就是定義在一個類內部的類。一直很難理解爲什麼要使用內部類,對內部類的理解始終停留在表明。今天詳細學習了Java內部類的機制,總結下內部類的使用。歸納大綱如下:
1. 內部類的基礎結構
2. 內部類的優點和使用場景
3. 內部類的分類
4. 內部類的繼承
若有不正之處,請批評指教,共同成長!請尊重作者勞動成果,轉載請標明原文鏈接
1. 內部類的基礎結構
package c10;
public class Parcel1 {
class Contents {
private int i = 11;
public int value(){
return i;
}
}
class Destination {
private String label;
Destination (String whereTo) {
label = whereTo;
}
String readLabel() {
return label;
}
}
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
Parcel1.Contents contents = p.new Contents();
Parcel1.Destination dest = p.new Destination("Tasmania");
}
}
輸出結果:Tasmania
內部類其實是一個編譯時的概念(內部類與普通類的不同體現在編譯上),如上例所示,編譯完成後,分別生成三個class文件:Parcel1.class, Parcel1$Contents.class, Destination$Contents.class。
.class
文件包含了如何創建該類型對象的全部信息。內部類生成的.class
文件有嚴格的規則:外部類名稱+$+內部類名稱
。如果是匿名內部類,編譯器會簡單地產生一個數字作爲其表示符,如Parcel1$1.class
.
2. 內部類的優點和使用場景
Thinking in Java中通過一個章節詳細討論了爲什麼需要內部類,可能是因爲筆者的理解能力有限,直到今天也無法明確體會作者的意思,總結起來有以下這些:
1. 內部類最吸引人的原因是:每個內部類都能獨立繼承一個(接口或類)的實現,所以無論外圍類是否已經繼承了某個(接口或類)實現,對於內部類都沒有影響
2. 內部類可以有多個實例,每個實例都有自己的狀態信息,並且與外圍類對象的信息相互獨立
3. 單個外圍類中,可以讓多個內部類以不同的方式實現同一個接口,或集成同一個類
4. 內部類並沒有令人迷惑的”is-a”關係,是一個獨立的實體
也就是說通過內部類可以變相的實現類的多重繼承(我們知道Java中只能引用多個接口,而不能繼承多個類)。比如這樣:
//基類A、B、C
class A {}
abstract class B {}
class C {}
//派生類通過內部類同時繼承ABC
class Z extends A {
class Z1 extends B{
C makeC() { return new C(){}; }
}
}
也看了網上各位大神的文章,總結歸納,自己對爲什麼使用內部類的理解是這樣的:
使用內部類會破壞良好的代碼結構(第一次看到會覺得怪怪的),但爲類的設計者提供了一種途徑來隱藏類的實現細節(這些往往是客戶端程序員所不關注的),同時也是代碼變的更加靈活。
3. 內部類的分類
筆者認爲內部類之所以很難理解,正是因爲語法覆蓋了大量難以理解的技術(如果都像基礎內部類那樣,就沒有多少意思了)。內部類可以分爲四種:成員內部類,局部內部類,嵌套類,匿名內部類。
- 靜態內部類的應用場景是:只可以訪問外部類的靜態成員變量和靜態成員方法。
- 成員內部類的應用場景是:它可以訪問它的外部類的所有成員變量和方法,不管是靜態的還是非靜態的都可以。
- 局部內部類:像局部變量一樣,不能被public, protected,
private和static修飾。只能訪問方法中定義的final類型的局部變量。 - 匿名內部類:匿名內部類就是沒有名字的局部內部類,不使用關鍵字class, extends, implements,沒有構造方法。匿名內部類隱式地繼承了一個父類或者實現了一個接口。匿名內部類使用得比較多,通常是作爲一個方法參數。
成員內部類
成員內部類擁有對外部類所有元素的訪問權。
在成員內部類要引用外部類對象時,使用outer.this
來表示外部類對象;
而需要創建內部類對象,可以使用outer.inner obj = outer.new inner();
(注意,在擁有外部類對象之前是不可能創建內部類對象的,除非你創建的是嵌套類)
舉個例子:
package c10;
public class Parcel {
private int num = 11;
class Contents {
private int num = 12;
public void print() {
int num = 13;
System.out.println("局部變量:" + num);
System.out.println("內部局部變量:" + this.num);
System.out.println("外部局部變量:" + Parcel.this.num);
}
}
public static void main(String[] args) {
Parcel p = new Parcel();
Parcel.Contents c = p.new Contents();
c.print();
}
}
局部變量:13
內部局部變量:12
外部局部變量:11
局部內部類
當你要解決一個複雜的問題,想創建一個類來輔助你的解決方案,但又不希望這個類是公共可用的時,可以通過以下方式實現:
- 一個定義在方法中的類
- 一個定義在作用域內的類
- 一個實現了接口的匿名類
- 一個擴展了非默認構造器的匿名類
- 執行字段初始化的匿名類
定義在方法中的內部類:
public class Parcel5 {
public Destination destination(String s) {
class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() {
return label;
}
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Destination d = p.destination("Tasmania");
}
}
Tasmania
PDestination
類是destination()
方法的一部分,在之外不能被訪問。注意return
語句中的向上轉型,返回的是Destination
的引用,它是PDestination
的基類。
定義在作用域中的內部類:
public class Parcel6 {
private void internalTracking(boolean b) {
if (b) {
class TrackingSlip {
private String id;
TrackingSlip(String s) {
id = s;
}
String getSlip() {
return id;
}
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
}
}
public void track() {
internalTracking(true);
}
public static void main(String[] args) {
Parcel5 p = new Parcel5();
p.track();
}
}
匿名內部類
一個匿名內部類的例子如下,匿名類是內部類比較常用的方式,簡化了代碼,更加靈活:
package c10;
//註釋後,編譯報錯:Contents cannot be resolved to a type
//interface Contents { }
public class Parcel7 {
public Contents contents() {
return new Contents() {
private int i = 11;
public int value(){ return i; }
};
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
}
需要注意:
1. new匿名類前,這個類需要定義,否則編譯報錯;
2. 當所在的方法的形參需要被內部類裏面使用時,該形參必須爲final,否則編譯報錯,如下例所示:
package c10;
interface Destination {}
public class Parcel10 {
public Destination destination(
final String dest,final float price) {
return new Destination() {
private int cost;
{
cost = Math.round(price);
if( cost > 100 ) {
System.out.println("Over budget");
}
}
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel10 p = new Parcel10();
Destination d = p.destination("Nanjing", 101.396F);
}
}
通過匿名內部類,可以寫出一個完美的工廠模式:
package c10;
interface Service {
void method1();
void method2();
}
interface ServiceFacotry {
Service getService();
}
class Implementation1 implements Service {
private Implementation1() {}
@Override
public void method1() {
System.out.println("Implementation1 method1");
}
@Override
public void method2() {
System.out.println("Implementation1 method2");
}
public static ServiceFacotry factory =
new ServiceFacotry() {
@Override
public Service getService() {
return new Implementation1();
}
};
}
class Implementation2 implements Service {
private Implementation2() {}
@Override
public void method1() {
System.out.println("Implementation2 method1");
}
@Override
public void method2() {
System.out.println("Implementation2 method2");
}
public static ServiceFacotry factory =
new ServiceFacotry() {
@Override
public Service getService() {
return new Implementation2();
}
};
}
public class Factories {
public static void serviceConsumer(ServiceFacotry fact) {
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args) {
serviceConsumer(Implementation1.factory);
serviceConsumer(Implementation2.factory);
}
}
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
嵌套類
如果不需要內部類對象與其外部類對象之間有聯繫,那麼可以將內部類聲明爲static。嵌套類意味着:
1. 要創建嵌套類的對象,並不需要先創建外部類的對象
2. 不能從嵌套類的對象中訪問非靜態的外部類對象
3. 嵌套類和普通的內部類還有一個區別:普通內部類不能有static
數據和static
屬性,也不能包含嵌套類,但嵌套類可以。而嵌套類不能聲明爲private
,一般聲明爲public
,方便調用。
package c10;
public class Parcel11 {
private static int age = 12;
static class Contents {
public void print() {
System.out.println(age);
}
}
public static void main(String[] args) {
Contents c = new Contents();
c.print();
}
}
12
4. 內部類的繼承
內部類的繼承,是指內部類被繼承,普通類extends
內部類。而這時候代碼上要有點特別處理,具體看以下例子:
public class InheritInner extends WithInner.Inner {
// InheritInner() 是不能通過編譯的,一定要加上形參
InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner obj = new InheritInner(wi);
}
}
class WithInner {
class Inner {
}
}
可以看到子類的構造函數裏面要使用父類的外部類對象.super()
;而這個對象需要從外面創建並傳給形參。
參考資料
- 《Thinking in Java》第4版
- http://www.cnblogs.com/dolphin0520/ 作者:海子
- http://www.cnblogs.com/nerxious/archive/2013/01/24/2875649.htm作者:Nerxious
- http://android.blog.51cto.com/268543/384844/ 作者:Icansoft