天天看點

内部類

内部類是指在一個外部類的内部再定義一個類。内部類作為外部類的一個成員,并且依附于外部類而存在的。

内部類可為靜态,可用protected和private修飾(而外部類隻能使用public和預設的包通路權限)。

内部類主要有以下幾類:成員内部類、局部内部類、靜态内部類、匿名内部類

  為什麼需要内部類?

  典型的情況是,内部類繼承自某個類或實作某個接口,内部類的代碼操作建立其的外圍類的對象。是以你可以認為内部類提供了某種進入其外圍類的視窗。使用内部類最吸引人的原因是:

  每個内部類都能獨立地繼承自一個(接口的)實作,是以無論外圍類是否已經繼承了某個(接口的)實作,對于内部類都沒有影響。如果沒有内部類提供的可以繼承多個具體的或抽象的類的能力,一些設計與程式設計問題就很難解決。從這個角度看,内部類使得多重繼承的解決方案變得完整。接口解決了部分問題,而内部類有效地實作了“多重繼承”。

  A:成員内部類

  作為外部類的一個成員存在,與外部類的屬性、方法并列。

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() { }

// 成員内部類中,不能定義靜态成員

// 成員内部類中,可以通路外部類的所有成員

class Inner {

// static int inner_i = 100; //内部類中不允許定義靜态變量

intj = 100; // 内部類和外部類的執行個體變量可以共存

int inner_i = 1;

void inner_f1() {

System.out.println(i);

//在内部類中通路内部類自己的變量直接用變量名

System.out.println(j);

//在内部類中通路内部類自己的變量也可以用this.變量名

System.out.println(this.j);

//在内部類中通路外部類中與内部類同名的執行個體變量用外部類名.this.變量名

System.out.println(Outer.this.j);

//如果内部類中沒有與外部類同名的變量,則可以直接用變量名通路外部類變量

System.out.println(k);

outer_f1();

outer_f2();

}

//外部類的非靜态方法通路成員内部類

public void outer_f3() {

Inner inner = new Inner();

inner.inner_f1();

// 外部類的靜态方法通路成員内部類,與在外部類外部通路成員内部類一樣

public static void outer_f4() {

//step1 建立外部類對象

Outer out = new Outer();

//step2 根據外部類對象建立内部類對象

Inner inner = out.new Inner();

//step3 通路内部類的方法

public static void main(String[] args) {

//outer_f4(); //該語句的輸出結果和下面三條語句的輸出結果一樣

//如果要直接建立内部類的對象,不能想當然地認為隻需加上外圍類Outer的名字,

//就可以按照通常的樣子生成内部類的對象,而是必須使用此外圍類的一個對象來

//建立其内部類的一個對象:

//Outer.Inner outin = out.new Inner()

//是以,除非你已經有了外圍類的一個對象,否則不可能生成内部類的對象。因為此

//内部類的對象會悄悄地連結到建立它的外圍類的對象。如果你用的是靜态的内部類,

//那就不需要對其外圍類對象的引用。

Outer.Inner outin = out.new Inner();

outin.inner_f1();

注意:内部類是一個編譯時的概念,一旦編譯成功,就會成為完全不同的兩類。

對于一個名為outer的外部類和其内部定義的名為inner的内部類。編譯完成後出現outer.class和outer$inner.class兩類。

  B:局部内部類

  在方法中定義的内部類稱為局部内部類。與局部變量類似,局部内部類不能有通路說明符,因為它不是外圍類的一部分,但是它可以通路目前代碼塊内的常量,和此外圍類所有的成員。

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;

//定義在方法内部

int s = 300; // 可以定義與外部類同名的變量

// static int m = 20; //不可以定義靜态變量

Inner(int k) {

inner_f(k);

int inner_i = 100;

void inner_f(int k) {

//如果内部類沒有與外部類同名的變量,在内部類中可以直接通路外部類的執行個體變量

System.out.println(out_i);

//可以通路外部類的局部變量(即方法内的變量),但是變量必須是final的

//System.out.println(i);

//如果内部類中有與外部類同名的變量,直接用變量名通路的是内部類的變量

System.out.println(s);

//用this.變量名通路的也是内部類變量

System.out.println(this.s);

//用外部類名.this.内部類變量名通路的是外部類變量

System.out.println(Outer.this.s);

new Inner(k);

// 通路局部内部類必須先有外部類對象

out.f(3);

  C:靜态内部類(嵌套類):(注意:前兩種内部類與變量類似,是以可以對照參考變量)

  如果你不需要内部類對象與其外圍類對象之間有聯系,那你可以将内部類聲明為static。這通常稱為嵌套類(nested class)。想要了解static應用于内部類時的含義,你就必須記住,普通的内部類對象隐含地儲存了一個引用,指向建立它的外圍類對象。然而,當内部類是static的時,就不是這樣了。嵌套類意味着:

  1. 要建立嵌套類的對象,并不需要其外圍類的對象。

  2. 不能從嵌套類的對象中通路非靜态的外圍類對象。

// 靜态内部類可以用public,protected,private修飾

// 靜态内部類中可以定義靜态或者非靜态的成員

static class Inner {

static int inner_i = 100;

int inner_j = 200;

static void inner_f1() {

//靜态内部類隻能通路外部類的靜态成員(包括靜态變量和靜态方法)

System.out.println("Outer.i" + i);

void inner_f2() {

// 靜态内部類不能通路外部類的非靜态成員(包括非靜态變量和非靜态方法)

// System.out.println("Outer.i"+j);

// outer_f2();

// 外部類通路内部類的靜态成員:内部類.靜态成員

System.out.println(Inner.inner_i);

Inner.inner_f1();

// 外部類通路内部類的非靜态成員:執行個體化内部類即可

inner.inner_f2();

new Outer().outer_f3();

  生成一個靜态内部類不需要外部類成員:這是靜态内部類和成員内部類的差別。靜态内部類的對象可以直接生成:Outer.Inner in = new Outer.Inner(); 而不需要通過生成外部類對象來生成。這樣實際上使靜态内部類成為了一個頂級類(正常情況下,你不能在接口内部放置任何代碼,但嵌套類可以作為接口的一部分,因為它是static 的。隻是将嵌套類置于接口的命名空間内,這并不違反接口的規則)

  D:匿名内部類(from thinking in java 3th)

  簡單地說:匿名内部類就是沒有名字的内部類。什麼情況下需要使用匿名内部類?如果滿足下面的一些條件,使用匿名内部類是比較合适的:

  ·隻用到類的一個執行個體。

  ·類在定義後馬上用到。

  ·類非常小(SUN推薦是在4行代碼以下)

  ·給類命名并不會導緻你的代碼更容易被了解。

  在使用匿名内部類時,要記住以下幾個原則:

  ·匿名内部類不能有構造方法。

  ·匿名内部類不能定義任何靜态成員、方法和類。

  ·匿名内部類不能是public,protected,private,static。

  ·隻能建立匿名内部類的一個執行個體。

·一個匿名内部類一定是在new的後面,用其隐含實作一個接口或實作一個類。

  ·因匿名内部類為局部内部類,是以局部内部類的所有限制都對其生效。

  下面的例子看起來有點奇怪:

//在方法中傳回一個匿名内部類

public class Parcel6 {

public Contents cont() {

return new Contents() {

private int i = 11;

public int value() {

return i;

}; // 在這裡需要一個分号

Parcel6 p = new Parcel6();

Contents c = p.cont();

  cont()方法将下面兩個動作合并在一起:傳回值的生成,與表示這個傳回值的類的定義!進一步說,這個類是匿名的,它沒有名字。更糟的是,看起來是你正要建立一個Contents對象:

return new Contents()

  但是,在到達語句結束的分号之前,你卻說:“等一等,我想在這裡插入一個類的定義”:

public int value() { return i; }

};

  這種奇怪的文法指的是:“建立一個繼承自Contents的匿名類的對象。”通過new 表達式傳回的引用被自動向上轉型為對Contents的引用。匿名内部類的文法是下面例子的簡略形式:

class MyContents implements Contents {

return new MyContents();

  在這個匿名内部類中,使用了預設的構造器來生成Contents。下面的代碼展示的是,如果你的基類需要一個有參數的構造器,應該怎麼辦:

public class Parcel7 {

public Wrapping wrap(int x) {

// Base constructor call:

return new Wrapping(x) { // Pass constructor argument.

return super.value() * 47;

}; // Semicolon required

Parcel7 p = new Parcel7();

Wrapping w = p.wrap(10);

  隻需簡單地傳遞合适的參數給基類的構造器即可,這裡是将x 傳進new Wrapping(x)。在匿名内部類末尾的分号,并不是用來标記此内部類結束(C++中是那樣)。實際上,它标記的是表達式的結束,隻不過這個表達式正巧包含了内部類罷了。是以,這與别的地方使用的分号是一緻的。

  如果在匿名類中定義成員變量,你同樣能夠對其執行初始化操作:

public class Parcel8 {

// Argument must be final to use inside

// anonymous inner class:

public Destination dest(final String dest) {

return new Destination() {

private String label = dest;

public String readLabel() { return label; }

Parcel8 p = new Parcel8();

Destination d = p.dest("Tanzania");

  如果你有一個匿名内部類,它要使用一個在它的外部定義的對象,編譯器會要求其參數引用是final 型的,就像dest()中的參數。如果你忘記了,會得到一個編譯期錯誤資訊。如果隻是簡單地給一個成員變量指派,那麼此例中的方法就可以了。但是,如果你想做一些類似構造器的行為,該怎麼辦呢?在匿名類中不可能有已命名的構造器(因為它根本沒名字!),但通過執行個體初始化,你就能夠達到為匿名内部類“制作”一個構造器的效果。像這樣做:

abstract class Base {

public Base(int i) {

System.out.println("Base constructor, i = " + i);

public abstract void f();

public class AnonymousConstructor {

public static Base getBase(int i) {

return new Base(i) {

{

System.out.println("Inside instance initializer");

public void f() {

System.out.println("In anonymous f()");

Base base = getBase(47);

base.f();

  在此例中,不要求變量i 一定是final 的。因為i 被傳遞給匿名類的基類的構造器,它并不會在匿名類内部被直接使用。下例是帶執行個體初始化的“parcel”形式。注意dest()的參數必須是final,因為它們是在匿名類内被使用的。

public class Parcel9 {

public Destinationdest(final String dest, final float price) {

private int cost;

// Instance initialization for each object:

cost = Math.round(price);

if(cost > 100)

System.out.println("Over budget!");

Parcel9 p = new Parcel9();

Destination d = p.dest("Tanzania", 101.395F);

  在執行個體初始化的部分,你可以看到有一段代碼,那原本是不能作為成員變量初始化的一部分而執行的(就是if 語句)。是以對于匿名類而言,執行個體初始化的實際效果就是構造器。當然它受到了限制:你不能重載執行個體初始化,是以你隻能有一個構造器。

  從多層嵌套類中通路外部

  一個内部類被嵌套多少層并不重要,它能透明地通路所有它所嵌入的外圍類的所有成員,如下所示:

class MNA {

private void f() {}

class A {

private void g() {}

public class B {

void h() {

g();

f();

public class MultiNestingAccess {

MNA mna = new MNA();

MNA.A mnaa = mna.new A();

MNA.A.B mnaab = mnaa.new B();

mnaab.h();

  可以看到在MNA.A.B中,調用方法g()和f()不需要任何條件(即使它們被定義為private)。這個例子同時展示了如何從不同的類裡面建立多層嵌套的内部類對象的基本文法。“.new”文法能産生正确的作用域,是以你不必在調用構造器時限定類名。

  内部類的重載問題

  如果你建立了一個内部類,然後繼承其外圍類并重新定義此内部類時,會發生什麼呢?也就是說,内部類可以被重載嗎?這看起來似乎是個很有用的點子,但是“重載”内部類就好像它是外圍類的一個方法,其實并不起什麼作用:

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 {

System.out.println("BigEgg.Yolk()");

new BigEgg();

  輸出結果為:

New Egg()

Egg.Yolk()

  預設的構造器是編譯器自動生成的,這裡是調用基類的預設構造器。你可能認為既然建立了BigEgg 的對象,那麼所使用的應該是被“重載”過的Yolk,但你可以從輸出中看到實際情況并不是這樣的。

  這個例子說明,當你繼承了某個外圍類的時候,内部類并沒有發生什麼特别神奇的變化。這兩個内部類是完全獨立的兩個實體,各自在自己的命名空間内。當然,明确地繼承某個内部類也是可以的:

class Egg2 {

System.out.println("Egg2.Yolk()");

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 {

System.out.println("BigEgg2.Yolk()");

System.out.println("BigEgg2.Yolk.f()");

public BigEgg2() {

insertYolk(new Yolk());

Egg2 e2 = new BigEgg2();

e2.g();

Egg2.Yolk()

New Egg2()

BigEgg2.Yolk()

BigEgg2.Yolk.f()

  現在BigEgg2.Yolk 通過extends Egg2.Yolk 明确地繼承了此内部類,并且重載了其中的方法。Egg2 的insertYolk()方法使得BigEgg2 将它自己的Yolk 對象向上轉型,然後傳遞給引用y。是以當g()調用y.f()時,重載後的新版的f()被執行。第二次調用Egg2.Yolk()是BigEgg2.Yolk 的構造器調用了其基類的構造器。可以看到在調用g()的時候,新版的f()被調用了。

  内部類的繼承問題(thinking in java 3th p294)

  因為内部類的構造器要用到其外圍類對象的引用,是以在你繼承一個内部類的時候,事情變得有點複雜。問題在于,那個“秘密的”外圍類對象的引用必須被初始化,而在被繼承的類中并不存在要聯接的預設對象。要解決這個問題,需使用專門的文法來明确說清它們之間的關聯:

class WithInner {

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");

WithInner wi = new WithInner();

InheritInner ii = new InheritInner(wi);

this is a constructor in WithInner.Inner

this is a constructor in InheritInner

  可以看到,InheritInner 隻繼承自内部類,而不是外圍類。但是當要生成一個構造器時,預設的構造器并不算好,而且你不能隻是傳遞一個指向外圍類對象的引用。此外,你必須在構造器内使用如下文法:

上一篇: 内部類
下一篇: 内部類

繼續閱讀