概念
嵌套類(nested class)
指被定義在另一個類的內(nèi)部的類。嵌套類存在的目的應(yīng)該只是為他的外圍類(enclosing class)提供服務(wù)。如果嵌套類將來可以回用于其他的某個環(huán)境中,他就應(yīng)該是頂層類(top-level class)。
分類
嵌套類分為四種:靜態(tài)成員類(static member class)、非靜態(tài)成員類(nonstatic member class)、匿名類(anonymous class)和局部類(local class)。
內(nèi)部類:非靜態(tài)成員類(nonstatic member class)、匿名類(anonymous class)和局部類(local class)。
本條目的
告訴你什么時(shí)候應(yīng)該使用哪種嵌套類,以及這樣做的原因。
靜態(tài)成員類
靜態(tài)成員類是最簡單的一種嵌套類。
最好把他看做是普通的類,只是碰巧被聲明在另一個類的內(nèi)部而已,他可以訪問外圍類的所有成員,包括聲明為私有的成員。
靜態(tài)內(nèi)部類是外圍類的一個靜態(tài)成員,與其他的靜態(tài)成員一樣,也最受同樣的可訪問性規(guī)則。如果他被聲明為私有的,他就只能在外圍類的內(nèi)部才可以被訪問,等等。
注意:如果一個類要被聲明為static的,只有一種情況,就是靜態(tài)內(nèi)部類。如果在外部類聲明為static,程序會編譯都不會過。
示例
public class Outer {
private String name;
private int age;
public static class Builder {
private String name;
private int age;
public Builder(int age) {
this.age = age;
}
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withAge(int age) {
this.age = age;
return this;
}
public Outer build() {
return new Outer(this);
}
}
private Outer(Builder b) {
this.age = b.age;
this.name = b.name;
}
}
靜態(tài)成員類的一種常見用法是作為共有的輔助類,僅當(dāng)與他的外部類一起使用時(shí)才有意義。例如考慮一個枚舉,他描述了計(jì)算機(jī)支持的各種操作(見滴30條)。Operation枚舉應(yīng)該是Calculator類的公有靜態(tài)成員類,然后,Calculator類的客戶端就可以用諸如Calculator.Operation.PLUS和Calculator.Operation.MINUS這樣的名稱來引用這些操作。
public class Calculator {
public enum Operation {
PLUS, MINUS, TIMES, DIVIDE;
double apply(double x, double y) {
switch (this) {
case PLUS:
return x + y;
case MINUS:
return x - y;
case TIMES:
return x * y;
case DIVIDE:
return x / y;
}
throw new AssertionError("Unknow op:" + this);
}
}
}
非靜態(tài)成員類
從語法上講,靜態(tài)成員類和非靜態(tài)成員類之間唯一的區(qū)別是:靜態(tài)成員類的聲明中包含修飾符static。盡管他們語法非常相似,但是這兩種嵌套類有很大的不同。
非靜態(tài)成員類的每個實(shí)例都隱含著與外圍類的一個外圍實(shí)例(enclosing instance)想關(guān)聯(lián)。在非靜態(tài)成員類的實(shí)例方法內(nèi)部,可以調(diào)用外圍實(shí)例上的方法,或者利用修飾過的this構(gòu)造獲得外圍實(shí)例的引用[JLS, 15.8.4]。如果嵌套類的實(shí)例可以在他外圍實(shí)例的情況下,要想創(chuàng)建非靜態(tài)成原來的實(shí)例是不可能的。
代碼示例:
package example.nestedclass;
/**
* Created by Jiang Meiwei on 2017/5/24.
*/
public class Outer {
private int a;
private Inner inner;
public class Inner{
private int b ;
}
public Inner getInner(){
if(inner ==null){
inner = new Inner();
}
return inner;
}
}
調(diào)用方法:
package example.nestedclass;
/**
* Created by Jiang Meiwei on 2017/5/24.
*/
public class test {
Outer outer = new Outer();
Outer.Inner inner = outer.getInner();
}
當(dāng)非靜態(tài)成員類的實(shí)例被創(chuàng)建的時(shí)候,它和外圍實(shí)例之間的關(guān)聯(lián)關(guān)系也隨之建立起來;而且,這種關(guān)聯(lián)關(guān)系以后也不能被修改。通常情況下,當(dāng)在外圍類的某個實(shí)例方法的內(nèi)部調(diào)用了非靜態(tài)成員類的構(gòu)造器時(shí),這種管理就自動建立起來。使用表達(dá)式enclosingInstance.new MemberClass(args)來手工建立這種關(guān)系也是有可能的,但是很少使用。正如你預(yù)料的那樣,這種關(guān)聯(lián)需要消耗非靜態(tài)成員類的實(shí)例空間,并且增加了構(gòu)造的時(shí)間開銷。
java編譯器在創(chuàng)建內(nèi)部類對象時(shí),隱式的把其他外部類的對象也傳了進(jìn)來并一直保存著。這樣就使得內(nèi)部類的對象始終可以訪問其外部類的對象。同時(shí)這也是為什么在外部類作用范圍之外想要創(chuàng)建內(nèi)部類對象必須創(chuàng)建起外部類對象的原因。
有人會問,如果一個外部類的成員變量與內(nèi)部類的成員變量同名,也即外部類的成員變量被屏蔽了,怎么辦?沒事,java里面有如下格式表達(dá)外部類的引用:outerClass.this,有了它,我們就不怕這種屏蔽的情況了。
如果聲明成員類不要求訪問外圍實(shí)例,就要始終把static修飾符放在它的生命中,使它成為靜態(tài)成員類,而不是非靜態(tài)成員類。如果省略了static修飾符,則每個實(shí)例都將包含一個額外的指向外圍對象的引用。保存這份引用要消耗時(shí)間和空間,并且會導(dǎo)致外圍實(shí)例在符合垃圾回收時(shí)仍然得以保留。如果沒有外圍實(shí)例的情況下,也需要分配實(shí)例,就不能使用非靜態(tài)成員類,因?yàn)榉庆o態(tài)成員類的實(shí)例必須要有一個外圍實(shí)例。

私有靜態(tài)成員類的一種常見用法用來代表外圍類所代表的對象的組件。例如,考慮一個Map實(shí)例,他把鍵和值關(guān)聯(lián)起來。許多Map實(shí)現(xiàn)的內(nèi)部都有一個Entry對象,對應(yīng)于map中的每個鍵值對。雖然每個entry斗魚一哥map關(guān)聯(lián),但是entry上的方法并不需要訪問該map。因此,使用非靜態(tài)成員來表示entry是很浪費(fèi)的:私有的靜態(tài)成員類是最佳的選擇。如果不小心漏掉了entry聲明中的static修飾符,該map依然可以工作,但是每個entry中將會包含一個指向該map的引用,這樣就浪費(fèi)了空間和時(shí)間。
非靜態(tài)成員類常見用法
非靜態(tài)成員類常見用法是定義一個Adapter,它允許外部類的實(shí)例被看作是另一個不相關(guān)的類的實(shí)例。例如,Map接口的實(shí)現(xiàn)往往使用非靜態(tài)成員類來實(shí)現(xiàn)它們的集合視圖(collection view),這些集合視圖是由Map的keySet、entrySet和Values方法返回的。
適配器模式
一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以在一起工作。
類圖:

代碼示例1(繼承+實(shí)現(xiàn)):
public interface PlugWithTwoItems {
public PlugWithTwoItems twoItems();
}
public class PlugWithThreeItems {
public String a;
}
public class ThreItemsToTwoItems extends PlugWithThreeItems implements PlugWithTwoItems {
public PlugWithTwoItems twoItems() {
//進(jìn)行三項(xiàng)轉(zhuǎn)兩項(xiàng)的工作
return twoItems;
}
}
代碼示例2(包含+實(shí)現(xiàn)):
public interface PlugWithTwoItems {
public PlugWithTwoItems twoItems();
}
public class PlugWithThreeItems {
public String a;
}
public class ThreItemsToTwoItems implements PlugWithTwoItems {
PlugWithThreeItems threeItems = new PlugWithThreeItems();
public PlugWithTwoItems twoItems() {
//進(jìn)行三項(xiàng)轉(zhuǎn)兩項(xiàng)的工作
return twoItems;
}
}
代碼示例3(內(nèi)部類):
public interface PlugWithTwoItems {
public PlugWithTwoItems twoItems();
}
public class PlugWithThreeItems {
public String a;
public class ThreItemsToTwoItems implements PlugWithTwoItems{
public PlugWithTwoItems twoItems() {
//進(jìn)行三項(xiàng)轉(zhuǎn)兩項(xiàng)的工作
return twoItems;
}
}
}
使用內(nèi)部類的好處
示例1:
public class TestInnerClass{
public static void main(String args[]){
Goods good = new Goods();
Contents content = good.dest();
System.out.println(content.value());
Destination destination = good.cont("BeiJing");
System.out.println(destination.readLabel());
}
}
interface Contents{
int value();
}
interface Destination{
String readLabel();
}
class Goods{
private class Content implements Contents{
private int i = 11;
public int value(){
return i;
}
}
protected class GDestination implements Destination{
private String label;
private GDestination(String whereTo){
label = whereTo;
}
public String readLabel(){
return label;
}
}
public Content dest(){
return new Content();
}
public GDestination cont(String s){
return new GDestination(s);
}
}
在這個例子里,類Content和GDestination被定義在了Goods類內(nèi)部,并且分別有著private和protected修飾符來控制訪問級別。Content代表著Goods的內(nèi)容,而GDestination代表著Goods的目的地。在后面的main()方法里面,直接用Contents content 和Destination destination進(jìn)行操作,你甚至連這倆個內(nèi)部類的名字都沒有見過,這樣,內(nèi)部類的好處就體現(xiàn)出來了,隱藏你不想讓別人知道的操作,即封裝性。
示例2:
public class TestInnerClass{
public static void main(String args[]){
Goods good = new Goods();
Contents content = good.dest();
System.out.println(content.value());
}
}
interface Contents{
int value();
}
interface Destination{
String readLabel();
}
class Goods{
private int valueRate = 2;
private class Content implements Contents{
private int i = 11 * valueRate;
public int value(){
return i;
}
}
protected class GDestination implements Destination{
private String label;
public GDestination(String whereTo){
label = whereTo;
}
public String readLabel(){
return label;
}
}
public Content dest(){
return new Content();
}
public GDestination cont(String s){
return new GDestination(s);
}
}
在這里我們給Goods添加了一個新的private 成員變量 valueRate,意義是貨物的價(jià)值系數(shù),在內(nèi)部類Content的方法value()計(jì)算價(jià)值時(shí)把他乘上。我們發(fā)現(xiàn),value()可以訪問valueRate,這也是內(nèi)部類的第二個好處,一個內(nèi)部類對象可以訪問創(chuàng)建他的外部類對象的內(nèi)容,甚至包括私有變量!這是一個非常有用的特性,為我們在設(shè)計(jì)時(shí)提供了更多的思路跟捷徑。要想實(shí)現(xiàn)這個功能,內(nèi)部類對象必須有指向外部類對象的引用。
靜態(tài)成員類與非靜態(tài)成員類的區(qū)別
- 靜態(tài)內(nèi)部類沒有了指向外部類的引用,非靜態(tài)內(nèi)部類對象有著指向其外部類對象的引用。
- 靜態(tài)內(nèi)部類跟靜態(tài)方法一樣,只能訪問靜態(tài)的成員變量和方法,不能訪問非靜態(tài)的方法和屬性,但在普通的內(nèi)部類可以訪問任意外部類的成員變量和方法。

靜態(tài)內(nèi)部類可以聲明普通成員變量和方法,而普通內(nèi)部類不能聲明static成員變量和方法。
靜態(tài)內(nèi)部類可以單獨(dú)初始化
Inner i = new Outer.Inner();
普通內(nèi)部類初始化:
Outer o = new Outer();
Outer.Inner i = o.new Inner();
匿名類
匿名類不同于Java程序設(shè)計(jì)語言中的其他任何語法單元。正如你所想象的,匿名類沒有名字。它不是外圍類的一個成員。它并不與其他的成員一起被聲明,而是在使用的同時(shí)被聲明和實(shí)例化。匿名類可以出現(xiàn)在代碼中任何允許存在表達(dá)式的地方。當(dāng)且僅當(dāng)匿名類出現(xiàn)在非靜態(tài)的環(huán)境中時(shí),它才有外圍實(shí)例。但是即使它們出現(xiàn)在靜態(tài)的環(huán)境中,也不可能擁有任何靜態(tài)成員。
匿名類適用性限制
匿名類的適用性受到諸多的限制。
- 除了在它們被聲明的時(shí)候之外,是無法將它們實(shí)例化的,你不能執(zhí)行instanceof測試,或者做任何需要命名類的其他事情。
- 你無法聲明一個匿名類來實(shí)現(xiàn)多個接口,或者擴(kuò)展一個類,并同時(shí)擴(kuò)展類和實(shí)現(xiàn)接口。
- 匿名類的客戶端無法調(diào)用任何成員,除了從它的超類型中繼承得到之外。
- 由于匿名類出現(xiàn)在表達(dá)式當(dāng)中,它們必須保持簡短——大約10行或者更少些——否則會影響程序的可讀性。
匿名類常見用法
- 第一種常見用法就是動態(tài)的創(chuàng)建函數(shù)對象(function object,見21條)。例如,第21條中Arrays.sort方法調(diào)用,利用匿名的Comparator實(shí)例,根據(jù)一組字符串的長度對它們進(jìn)行排序。
- 第二種常見的用法是創(chuàng)建過程對象(process object),比如Runnable、Thread或者TimerTask實(shí)例。
- 第三種常見的用法是在靜態(tài)工廠內(nèi)部(參見第18條中部分的intArrayAsList方法)。
Arrays.sort(stringArray, new Comparator<String>() {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
public static void runSomeThing(){
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("I am running");
}
};
new Thread(runnable).start();
}
// Concrete implementation built atop skeletal implementation
static List<Integer> intArrayAsList(final int[] a) {
if (a == null)
throw new NullPointerException();
return new AbstractList<Integer>() {
public Integer get(int i) {
return a[i];
}
@Override
public Integer set(int i, Integer val) {
int oldVal = a[i];
a[i] = val;
return oldVal;
}
public int size() {
return a.length;
}
};
}
額外介紹
局部類
局部類是四種嵌套類中用的最少的類。在任何“可以聲明局部變量”的地方,都可以聲明局部類,并且局部類也遵守同樣的作用域規(guī)則。局部類與其他三種嵌套類中的每一種都有一些共同的屬性。與成員類一樣,局部類有名字,可以被重復(fù)使用。與匿名類一樣,只有當(dāng)局部類實(shí)在非靜態(tài)環(huán)境中定義的時(shí)候,才有外圍實(shí)例,它們也不能包含靜態(tài)成員。與匿名類一樣,它們必須簡短以便不會影響到可讀性。
示例:
public class outerTolocal {
public String string;
public int localInt;
public void OtoLocal() {}
public void localMthod(final int m, int n) {
class local {
//此類為局部類
//局部類不需要加public 修飾符,因?yàn)檫@方法執(zhí)行完 這類就消失了
int methodInt = m;
/**
* 局部類的變量如果要等于外部類的方法的變量,
* 此時(shí)外部類的方法變量必須用final 修飾符
* 如:
*/
final int m;
void localInner() {
System.out.println("local method");
}
}
new local().localInner();//在另外的一個類的中不可以創(chuàng)建局部內(nèi)部類的實(shí)例,只能在局部內(nèi)部類中來創(chuàng)建。
}
}
小結(jié)
簡而言之,共有四種不同的嵌套類,每一種都有自己的用途。
- 如果一個嵌套類需要在單個方法之外仍然可見的,或者它太長了,不適合于放在方法內(nèi)部,就應(yīng)該使用成員類。
- 如果成員類的每個示例都需要一個指向外圍實(shí)例的引用,就要把成員類做成非靜態(tài)的;否則,就做成靜態(tài)的。
- 假設(shè)這個嵌套類屬于一個方法的內(nèi)部,如果你只需要在一個地方創(chuàng)建實(shí)例,并且已經(jīng)有了一個預(yù)置的類型可以說明這個類的特征,就要把它做成匿名類;否則,就做成局部類。