1. 程式人生 > >Java程式設計思想閱讀筆記(第10章內部類)

Java程式設計思想閱讀筆記(第10章內部類)

內部類

  • 內部類是指在一個外部類的內部再定義一個類。內部類作為外部類的一個成員,並且依附於外部類而存在的
  • 可以將一個類的定義放在另一個類定義內部,這就是內部類
  • 內部類自動擁有對包裹它的基類所有成員的訪問許可權
  • 內部類可為靜態,可用protected和private修飾(而外部類只能使用public和預設的包訪問許可權)
  • 內部類主要有以下幾類:成員內部類、區域性內部類、靜態內部類、匿名內部類

內部類的共性

  • 內部類仍然是一個獨立的類,在編譯之後內部類會被編譯成獨立的.class檔案,但是前面冠以外部類的類名和$符號 。
  • 內部類不能用普通的方式訪問。
  • 內部類宣告成靜態的,就不能隨便的訪問外部類的成員變量了,此時內部類只能訪問外部類的靜態成員變數 。
  • 外部類不能直接訪問內部類的的成員,但可以通過內部類物件來訪問

- 內部類是外部類的一個成員,因此內部類可以自由地訪問外部類的成員變數,無論是否是private的 。因為當某個外圍類的物件建立內部類的物件時,此內部類會捕獲一個隱式引用,它引用了例項化該內部物件的外圍類物件。通過這個指標,可以訪問外圍類物件的全部狀態。

通過反編譯內部類的位元組碼,分析之後主要是通過以下幾步做到的: 

1 編譯器自動為內部類新增一個成員變數,這個成員變數的型別和外部類的型別相同,這個成員變數就是指向外部類物件的引用 
2 編譯器自動為內部類的構造方法新增一個引數,引數的型別是外部類的型別, 在構造方法內部使用這個引數為1中新增的成員變數賦值; 
3 在呼叫內部類的建構函式初始化內部類物件時,會預設傳入外部類的引用。

在方法或者任意作用域定義內部類的兩個理由

  • 實現了某型別的介面,於是可以建立內部類並返回對其的引用
  • 想解決一個問題,需要一個類協助解決問題,但是不希望這個類是公共可用的

為什麼需要內部類?

  • 內部類方法可以訪問該類定義所在的作用域的資料,包括私有的資料
  • 內部類可以對同一個包中的其他類隱藏起來,一般的非內部類,是不允許有 private 與protected許可權的,但內部類可以
  • 可是實現多重繼承
  • 當想要定義一個回撥函式且不想編寫大量程式碼時,使用匿名內部類比較便捷
  • 每個內部類都能獨立地繼承自一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響。大家都知道Java只能繼承一個類,它的多重繼承在我們沒有學習內部類之前是用介面來實現的。但使用介面有時候有很多不方便的地方。比如我們實現一個介面就必須實現它裡面的所有方法。而有了內部類就不一樣了。它可以使我們的類繼承多個具體類或抽象類。
  • 內部類使用得多繼承的解決方案變得完整。介面解決了部分問題,而內部類有效地實現了多重繼承,也就是說,內部類允許繼承多個非介面型別(類或者抽象類)。

成員內部類:

即在一個類中直接定義的內部類, 成員內部類與普通的成員沒什麼區別,可以與普通成員一樣進行修飾和限制。成員內部類不能含有static的變數和方法

public class TestParcel {

        //每個內部類都能獨立地繼承自一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響
        private  class  PContents implements Contents{
//          private static int i=11;
            private  int i=11;
            @Override
            public int value(){
                return i;
            }   
        }

    public static void main(String[] args) {
        TestParcel t=new TestParcel();
        //內部類不能用普通的方式訪問
        TestParcel.PContents p=t.new PContents();


    }
}       

區域性內部類:

  • 在方法中定義的內部類稱為區域性內部類。與區域性變數類似,區域性內部類不能有訪問說明符,因為它不是外圍類的一部分,但是它可以訪問當前程式碼塊內的常量,和此外圍類所有的成員。
需要注意的是:
  • (1)、方法內部類只能在定義該內部類的方法內例項化,不可以在此方法外對其例項化。
  • (2)、方法內部類物件不能使用該內部類所在方法的非final區域性變數。
public class Outer {
      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;

            // 定義在方法內部
            class Inner {
                 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);
                     // 可以訪問外部類的區域性變數(即方法內的變數)
                     System.out.println(j);
                      System.out.println(i);
                     // 如果內部類中有與外部類同名的變數,直接用變數名訪問的是內部類的變數
                     System.out.println(s);
                     // 用this.變數名訪問的也是內部類變數
                     System.out.println(this.s);
                     // 用外部類名.this.內部類變數名訪問的是外部類變數
                     System.out.println(Outer.this.s);
                 }
            }
            new Inner(k);
      }
      public static void main(String[] args) {
            // 訪問區域性內部類必須先有外部類物件
            Outer out = new Outer();
            out.f(3);
        }
}

靜態內部類(巢狀類):

  • 如果你不需要內部類物件與其外圍類物件之間有聯絡,那你可以將內部類宣告為static。這通常稱為巢狀類(nested class)。想要理解static應用於內部類時的含義,你就必須記住,普通的內部類物件隱含地儲存了一個引用,指向建立它的外圍類物件。然而,當內部類是static的時,就不是這樣了。
巢狀類意味著:
  • 要建立巢狀類的物件,並不需要其外圍類的物件。

- 不能從巢狀類的物件中訪問非靜態的外圍類物件。

public class Outer {
        private static int i = 1;
        private int j = 10;

        public static void outer_f1() {}

        public void outer_f2() {}

        // 靜態內部類可以用public,protected,private修飾
        // 靜態內部類中可以定義靜態或者非靜態的成員
        private static class Inner {
            static int inner_i = 100;
            int inner_j = 200;

            static void inner_f1() {
                // 靜態內部類只能訪問外部類的靜態成員(包括靜態變數和靜態方法)
                System.out.println("Outer.i" + i);
                outer_f1();
            }

            void inner_f2() {
                // 靜態內部類不能訪問外部類的非靜態成員(包括非靜態變數和非靜態方法)
                // System.out.println("Outer.i"+j);
//               outer_f2();
            }
        }

        public void outer_f3() {
            // 外部類訪問內部類的靜態成員:內部類.靜態成員
            System.out.println(Inner.inner_i);
            Inner.inner_f1();
            // 外部類訪問內部類的非靜態成員:例項化內部類即可
            Inner inner = new Inner();
            inner.inner_f2();
        }

        public static void main(String[] args) {
            new Outer().outer_f3();
        }
}

匿名內部類:

簡單地說:匿名內部類就是沒有名字的內部類

什麼情況下需要使用匿名內部類?如果滿足下面的一些條件,使用匿名內部類是比較合適的:
  • 只用到類的一個例項。
  • 類在定義後馬上用到。
  • 類非常小(SUN推薦是在4行程式碼以下)
  • 給類命名並不會導致你的程式碼更容易被理解。
在使用匿名內部類時,要記住以下幾個原則:
  • 匿名內部類不能有構造方法。
  • 匿名內部類不能定義任何靜態成員、方法和類。
  • 匿名內部類不能是public,protected,private,static。
  • 只能建立匿名內部類的一個例項。
  • 一個匿名內部類一定是在new的後面,用其隱含實現一個介面或實現一個類。
  • 因匿名內部類為區域性內部類,所以區域性內部類的所有限制都對其生效。
public class Parcel8 {
      public Destination dest(final String name, String city) {
          return new Destination(name, city) {
                private String label = name;

                public String getName() {
                    return label;
                }
            };
      }
      public static void main(String[] args) {
            Parcel8 p = new Parcel8();
            Destination d = p.dest("Tanzania", "gz");
        }

        abstract class Destination {
            Destination(String name, String city) {
                System.out.println(city);
            }

            abstract String getName();
        }
}