内部類簡單來說就是定義在一個類内部的類。一直很難了解為什麼要使用内部類,對内部類的了解始終停留在表明。今天詳細學習了Java内部類的機制,總結下内部類的使用。歸納大綱如下:
1. 内部類的基礎結構
2. 内部類的優點和使用場景
3. 内部類的分類
4. 内部類的繼承
若有不正之處,請批評指教,共同成長!請尊重作者勞動成果,轉載請标明原文連結
- 1. 内部類的基礎結構
- 2. 内部類的優點和使用場景
- 3. 内部類的分類
- 成員内部類
- 局部内部類
- 匿名内部類
- 嵌套類
- 4. 内部類的繼承
- 參考資料
1. 内部類的基礎結構
package c10;
public class Parcel1 {
class Contents {
private int i = ;
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 = ;
class Contents {
private int num = ;
public void print() {
int num = ;
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();
}
}
局部變量:
内部局部變量:
外部局部變量:
局部内部類
當你要解決一個複雜的問題,想建立一個類來輔助你的解決方案,但又不希望這個類是公共可用的時,可以通過以下方式實作:
- 一個定義在方法中的類
- 一個定義在作用域内的類
- 一個實作了接口的匿名類
- 一個擴充了非預設構造器的匿名類
- 執行字段初始化的匿名類
定義在方法中的内部類:
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 = ;
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 > ) {
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", F);
}
}
通過匿名内部類,可以寫出一個完美的工廠模式:
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 = ;
static class Contents {
public void print() {
System.out.println(age);
}
}
public static void main(String[] args) {
Contents c = new Contents();
c.print();
}
}
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