Java程式設計思想學習筆記1 - 內部類

zhuyiquan90發表於2018-06-28

內部類簡單來說就是定義在一個類內部的類。一直很難理解為什麼要使用內部類,對內部類的理解始終停留在表明。今天詳細學習了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
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

內部類其實是一個編譯時的概念(內部類與普通類的不同體現在編譯上),如上例所示,編譯完成後,分別生成三個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(){}; }
    }
}
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

也看了網上各位大神的文章,總結歸納,自己對為什麼使用內部類的理解是這樣的:

使用內部類會破壞良好的程式碼結構(第一次看到會覺得怪怪的),但為類的設計者提供了一種途徑來隱藏類的實現細節(這些往往是客戶端程式設計師所不關注的),同時也是程式碼變的更加靈活。


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 
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

區域性內部類

當你要解決一個複雜的問題,想建立一個類來輔助你的解決方案,但又不希望這個類是公共可用的時,可以透過以下方式實現:

  • 一個定義在方法中的類
  • 一個定義在作用域內的類
  • 一個實現了介面的匿名類
  • 一個擴充套件了非預設構造器的匿名類
  • 執行欄位初始化的匿名類

定義在方法中的內部類:

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
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

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(); 
    } 
}
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

匿名內部類

一個匿名內部類的例子如下,匿名類是內部類比較常用的方式,簡化了程式碼,更加靈活:

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

需要注意: 
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);
    }
}
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

透過匿名內部類,可以寫出一個完美的工廠模式:

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
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

巢狀類

如果不需要內部類物件與其外部類物件之間有聯絡,那麼可以將內部類宣告為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 
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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 {  
    } 
}
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

可以看到子類的建構函式里面要使用父類的外部類物件.super();而這個物件需要從外面建立並傳給形參。


參考資料

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31541037/viewspace-2157004/,如需轉載,請註明出處,否則將追究法律責任。

相關文章