歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

深入理解Java:內部類

什麼是內部類?

內部類是指在一個外部類的內部再定義一個類。內部類作為外部類的一個成員,並且依附於外部類而存在的。內部類可為靜態,可用protected和private修飾(而外部類只能使用public和缺省的包訪問權限)。內部類主要有以下幾類:成員內部類、局部內部類、靜態內部類、匿名內部類

內部類的共性

(1)、內部類仍然是一個獨立的類,在編譯之後內部類會被編譯成獨立的.class文件,但是前面冠以外部類的類名和$符號 。

(2)、內部類不能用普通的方式訪問。

(3)、內部類聲明成靜態的,就不能隨便的訪問外部類的成員變量了,此時內部類只能訪問外部類的靜態成員變量 。

(4)、外部類不能直接訪問內部類的的成員,但可以通過內部類對象來訪問

內部類是外部類的一個成員,因此內部類可以自由地訪問外部類的成員變量,無論是否是private的 。

因為當某個外圍類的對象創建內部類的對象時,此內部類會捕獲一個隱式引用,它引用了實例化該內部對象的外圍類對象。通過這個指針,可以訪問外圍類對象的全部狀態。

通過反編譯內部類的字節碼, 分析之後主要是通過以下幾步做到的:
1 編譯器自動為內部類添加一個成員變量, 這個成員變量的類型和外部類的類型相同, 這個成員變量就是指向外部類對象的引用;
2 編譯器自動為內部類的構造方法添加一個參數, 參數的類型是外部類的類型, 在構造方法內部使用這個參數為1中添加的成員變量賦值;
3 在調用內部類的構造函數初始化內部類對象時, 會默認傳入外部類的引用。

為什麼需要內部類?

其主要原因有以下幾點:

•內部類方法可以訪問該類定義所在的作用域的數據,包括私有的數據

•內部類可以對同一個包中的其他類隱藏起來,一般的非內部類,是不允許有 private 與protected權限的,但內部類可以

•可是實現多重繼承

•當想要定義一個回調函數且不想編寫大量代碼時,使用匿名內部類比較便捷

使用內部類最吸引人的原因是:

每個內部類都能獨立地繼承自一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。大家都知道Java只能繼承一個類,它的多重繼承在我們沒有學習內部類之前是用接口來實現的。但使用接口有時候有很多不方便的地方。比如我們實現一個接口就必須實現它裡面的所有方法。而有了內部類就不一樣了。它可以使我們的類繼承多個具體類或抽象類。

大家看下面的例子:

public class Example1 {
    public String name(){
      return "liutao";
  }
}

public class Example2 {
    public int age(){
        return 25;
    }
}

public class MainExample {

  private class test1 extends Example1{
        public String name(){
          return super.name();
        }
    }

    private class test2 extends Example2 {
      public int age(){
        return super.age();
      }
    }

  public String name(){
    return new test1().name();
  }

  public int age(){
      return new test2().age();
  }

  public static void main(String args[]){
      MainExample mi=new MainExample();
      System.out.println("姓名:"+mi.name());
      System.out.println("年齡:"+mi.age());
  }
}

成員內部類:

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

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;//內部類中不允許定義靜態變量
        int j = 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 訪問內部類的方法
        inner.inner_f1();
    }

    public static void main(String[] args) {
        //outer_f4();//該語句的輸出結果和下面三條語句的輸出結果一樣
        // 如果要直接創建內部類的對象,不能想當然地認為只需加上外圍類Outer的名字,
        // 就可以按照通常的樣子生成內部類的對象,而是必須使用此外圍類的一個對象來
        // 創建其內部類的一個對象:
        // Outer.Inner outin = out.new Inner()
        // 因此,除非你已經有了外圍類的一個對象,否則不可能生成內部類的對象。因為此
        // 內部類的對象會悄悄地鏈接到創建它的外圍類的對象。如果你用的是靜態的內部類,
        // 那就不需要對其外圍類對象的引用。
        Outer out = new Outer();
        Outer.Inner outin = out.new Inner();
        outin.inner_f1();
    }
}

局部內部類:

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

需要注意的是:

(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);
                // 可以訪問外部類的局部變量(即方法內的變量),但是變量必須是final的
                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的時,就不是這樣了。嵌套類意味著:

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

2. 不能從嵌套類的對象中訪問非靜態的外圍類對象。
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();
    }
}

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

匿名內部類:

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

•只用到類的一個實例。
•類在定義後馬上用到。
•類非常小(SUN推薦是在4行代碼以下)
•給類命名並不會導致你的代碼更容易被理解。

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

•  匿名內部類不能有構造方法。

•  匿名內部類不能定義任何靜態成員、方法和類。

•  匿名內部類不能是public,protected,private,static。

•  只能創建匿名內部類的一個實例。

•    一個匿名內部類一定是在new的後面,用其隱含實現一個接口或實現一個類。

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

 

下面的代碼展示的是,如果你的基類需要一個有參數的構造器,應該怎麼辦:

public class Parcel7 {
    public Wrapping wrap(int x) {
        // Base constructor call:
        return new Wrapping(x) { // Pass constructor argument.
            public int value() {
                return super.value() * 47;
            }
        }; // Semicolon required
    }
    public static void main(String[] args) {
        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 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();
    }
}

注意這裡的形參city,由於它沒有被匿名內部類直接使用,而是被抽象類Inner的構造函數所使用,所以不必定義為final。

內部類的重載問題

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

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 {
              public Yolk() {
                    System.out.println("BigEgg.Yolk()");
              }
      }
 
      public static void main(String[] args) {
              new BigEgg();
      }
}

輸出結果為:

New Egg()

Egg.Yolk()

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

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

class Egg2 {
      protected class Yolk {
              public Yolk() {
                    System.out.println("Egg2.Yolk()");
              }
 
              public void f() {
                    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 {
              public Yolk() {
                    System.out.println("BigEgg2.Yolk()");
              }
 
              public void f() {
                    System.out.println("BigEgg2.Yolk.f()");
              }
      }
 
      public BigEgg2() {
              insertYolk(new Yolk());
      }
 
      public static void main(String[] args) {
              Egg2 e2 = new BigEgg2();
              e2.g();
      }
}

輸出結果為:

Egg2.Yolk()

New Egg2()

Egg2.Yolk()

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()被調用了。

內部類的繼承問題

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

class WithInner {
        class Inner {
                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");
        }
 
        public static void main(String[] args) {
                WithInner wi = new WithInner();
                InheritInner ii = new InheritInner(wi);
        }
}

輸出結果為:

this is a constructor in WithInner.Inner

this is a constructor in InheritInner

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

enclosingClassReference.super();

這樣才提供了必要的引用,然後程序才能編譯通過。

為什麼非靜態內部類中不能有static修飾的屬性,但卻可以有常量?

如:
public class InnerClassDemo{
    int x;
    class A{
        static  int a = 0;//這樣寫是不合法的.
        static final int b=0;//這樣寫是合法的

    }
}

定義一個靜態的域或者方法,要求在靜態環境或者頂層環境,即如果加上 static class A變成靜態內部類就ok非靜態內部類 依賴於一個外部類對象,而靜態域/方法是不依賴與對象——僅與類相關(細說了,就是加載靜態域時,根本沒有外部類對象)因此,非靜態內部類中不能定義靜態域/方法,編譯過不了。

而常量之所以可以(不論有無static),因為java在編譯期就確定所有常量,放到所謂的常量池當中。常量的機制和普通變量不一樣

為什麼匿名內部類和局部內部類只能訪問final變量

參考:

關於Java內部類的“內部”  http://www.linuxidc.com/Linux/2015-06/118827.htm

Java內部類總結  http://www.linuxidc.com/Linux/2015-06/118826.htm

Copyright © Linux教程網 All Rights Reserved