淺析總結 Java 內部類的一些使用與梳理

coffee801發表於2017-05-19

Java 內部類有許多需要注意的地方 ,相信大家平時都在用,但是有些地方是需要注意的,在這裡給大家總結一下。


內部類的位置

public class A {
    class B {
    }
    public void pint() {
        class C {
        }
        new C();
    }
    public void pint(boolean b) {
        if (b) {
            class D {
            }
            new D();
        }
    }
}


從程式碼中可以看出,內部類可以定義到很多地方,常用的是成員變數中(B),方法中也叫區域性內部類(C),作用域中(D)


從上面來看似乎沒有用到過在方法中和作用域中的情況啊,這就錯了;再來看看這個:


public interface AInterface {
    void show();
}
public class B {
    public void show() {
        class Man implements AInterface {
            @Override
            public void show() {
            }
        }
        Man man = new Man();
        man.show();
    }
}


其中我們定義了兩個檔案,一個檔案是一個介面類,一個是B檔案;在B類中,的show()方法中我們使用了區域性內部類的方式建立了類ManMan class繼承介面並實現方法,隨後使用該類。

如果你想學習java可以來這個群,首先是二二零,中間是一四二,最後是九零六,裡面有大量的學習資料可以下載。


內部類的許可權

為什麼要有內部類的存在?


在我看來類主要的就是封裝、繼承、多型;當然其中的回撥思想我認為是很重要的;而內部類的出現就是為了簡化多重繼承的問題;一個A類,並不能繼承多個其他類,但是在使用中又需要使用到其他類的方法,這個時候內部類就發揮作用了;典型的就是事件點選的回撥實現。


那麼內部類的許可權究竟有多大?


至於答案是什麼,程式碼上看看就知道了。


public class C {
    int a = 1;
    private int b = 2;
    protected int c = 3;
    public int d = 4;
    void a() {
        System.out.println("A:" + a);
    }
    private void b() {
        System.out.println("B:" + b);
    }
    protected void c() {
        System.out.println("C:" + c);
    }
    public void d() {
        System.out.println("D:" + d);
    }
    class D {
        void show() {
            int max = a + b + c + d;
            a();
            b();
            c();
            d();
            System.out.println("Max:" + max);
        }
    }
    public static void main(String[] args) {
        D d = new C().new D();
        d.show();
    }
}


執行結果:


img

可以看出,內部類 D 對類 C 具有完整的訪問許可權,等於全身脫光了給你看。


那要是反過來呢?


public class C {
    class D {
        private int a = 20;
        private void a(){
            System.out.println("D.A:" + a);
        }
    }
    void show(){
        D d = new D();
        d.a();
        System.out.println("D.A:" + d.a);
    }
    public static void main(String[] args) {
        new C().show();
    }
}


執行結果:


img

可見也是完全可行的,也能直接訪問私有屬性 私有方法,在這裡似乎私有的限制已經失效了一般,這個讓我想起了以前看見過一個面試:在 Java 中 private 修飾何時會失效。


這完全是兩個人互相脫光光了啊~


匿名內部類

這個非常常見,特別是在按鈕點選事件繫結中。


public class D {
    void initButton() {
        Button b1 = new Button();
        b1.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(Button v) {
            }
        });
        Button b2 = new Button();
        b2.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(Button v) {
            }
        });
    }
}


其中的:


    new OnClickListener() {
            @Override
            public void onClick(Button v) {
            }
        }


就是匿名內部類的使用方式,OnClickListener 是一個介面類,介面類是無法直接new 一個例項的;這裡也並不是那樣,而是new 了一個其他的類,該類是匿名的,也就是沒有名字,只不過該類實現了 OnClickListener介面類中的方法。


上面的新增回撥部分可等同於:


public class D {
    void initButton1() {
        Button b1 = new Button();
        b1.setOnClickListener(new Listener1());
        Button b2 = new Button();
        b2.setOnClickListener(new Listener2());
    }
    class Listener1 implements OnClickListener {
        @Override
        public void onClick(Button v) {
        }
    }
    class Listener2 implements OnClickListener {
        @Override
        public void onClick(Button v) {
        }
    }
}


這裡就是先建立類,繼承自介面;而後賦值到 Button 中。


要說兩者的區別與好處,這個其實看具體的使用情況吧;如果你的按鈕很多,但是為了避免建立太多類;那麼可以建立一個回撥類,然後都賦值給所有的按鈕,不過最後就是需要在 onClick方法中進行判斷是那個按鈕進行的點選。

匿名內部類的使用地方很多;具體的使用應視使用情況而定~如果你想學習java可以來這個群,首先是二二零,中間是一四二,最後是九零六,裡面有大量的學習資料可以下載。


靜態內部類/靜態巢狀類

這個其實並不應該叫做內部類了,因為其並不具備內部類的完全許可權,在使用上與一般的類基本一樣;那為什麼會有這個的存在?


在我看來這個類的存在是為其包括類服務;意思是可以單獨服務,不被外面的類所知曉;如這樣:


public class E {
    private void show(){
        new A();
    }
    private static class A{
    }
}


其中類 A 使用了 static ,所以是靜態巢狀類,在這裡使用private 修飾;那麼該類只能在 E類中進行例項化;無法在 其他檔案中例項化。


這樣的情況使用外面的類能行麼?不行吧?也許你會說在 E.java 資料夾中建立 A.java ,並使用protected修飾;但是在同樣的包下,或者繼承的類中同樣能訪問了;這也只是其中一個較為特殊的情況。


我們來看看許可權


public class E {
    int a1 = 0;
    private int a2 = 0;
    protected int a3 = 0;
    public int a4 = 0;
    private void show(){
        A a =new A();
        System.out.print("b1:"+a.b1);
        System.out.print("b2:"+a.b2);
        System.out.print("b3:"+a.b3);
        System.out.print("b4:"+a.b4);
    }
    private static class A{
        int b1 = 0;
        private int b2 = 0;
        protected int b3 = 0;
        public int b4 = 0;
        private void print(){
            System.out.print("a1:"+a1);
            System.out.print("a2:"+a2);
            System.out.print("a3:"+a3);
            System.out.print("a4:"+a4);
        }
    }
}


在這個中的結果是怎樣?


img

從圖片中可以看出,其許可權級別是單方向的;靜態巢狀類 A 對其包含類 E 完全透明;但 E 並不對 A 透明。


再來看看方法:


img

可以看出同樣的情況;這個是為什麼呢?為什麼就是多一個 static 的修飾就這麼完全不同?其是很好理解,兩個獨立的類;本來就無法直接使用,必須有引用才能呼叫其屬性與方法。


我們或許可以這麼調整一下就OK:


public class E {
    int a1 = 0;
    private int a2 = 0;
    protected int a3 = 0;
    public int a4 = 0;
    private void show() {
        A a = new A();
        System.out.print("b1:" + a.b1);
        System.out.print("b2:" + a.b2);
        System.out.print("b3:" + a.b3);
        System.out.print("b4:" + a.b4);
        a.b1();
        a.b2();
        a.b3();
        a.b4();
    }
    void a1() {
    }
    private void a2() {
    }
    protected void a3() {
    }
    public void a4() {
    }
    private static class A {
        int b1 = 0;
        private int b2 = 0;
        protected int b3 = 0;
        public int b4 = 0;
        void b1() {
        }
        private void b2() {
        }
        protected void b3() {
        }
        public void b4() {
        }
        private void print(E e) {
            System.out.print("a1:" + e.a1);
            System.out.print("a2:" + e.a2);
            System.out.print("a3:" + e.a3);
            System.out.print("a4:" + e.a4);
            e.a1();
            e.a2();
            e.a3();
            e.a4();
        }
    }
}


在其靜態類中傳遞一個 E 的引用進去就能解決問題了:


img

可以看出其中現在並沒有報錯了;能正常執行。

如果你想學習java可以來這個群,首先是二二零,中間是一四二,最後是九零六,裡面有大量的學習資料可以下載。
兩者之間的隱藏區別

但是最開始上面的內部類是怎麼回事?難道是鬧鬼了?上面的內部類沒有傳遞引用的啊;為啥加上一個 static 就不行了?


在這裡我們需要看看位元組碼,我們先建立一個簡單的內部類:


public class F {
    class A{
    }
}


這個夠簡單吧?別說這個都難了;汗~


然後我們找到 class 檔案,然後檢視位元組碼:


img


在這裡分別檢視了 F 類的位元組碼和 F$A 類的位元組碼。



其中有這樣的一句: final F this$0; 這句是很重要的一句,這句出現的地方在其內部類中,意思是當你 new 一個內部類的時候就同時傳遞了當前類進去;所以在內部類中能具有當前類的完全許可權,能直接使用所有的東西;就是因為在隱藏情況下已經傳遞了當前類進去。



那麼我們再看看一個簡單的靜態內部類:


public class G {
    static class A {
    }
}


與上面的區別唯一就是在於新增了一個 static 。此時我們看看位元組碼:


img



可以看出其中無論是 G 類,還是 G$A 類的初始化中都沒有其他多餘的部分,也沒有進行隱藏的傳遞進去當前類;所以這樣的情況下並不具備訪問許可權,需要我們傳遞引用進去,可以通過介面也可以完全傳遞進去,具體取決於個人。所以加了static類的內部類除了在許可權上比一般的類更加開放(與其包含類)外,與一般的類在使用上是一樣的;所以準確的說應該叫做靜態巢狀類。



初始化的區別

一個類中,同時包含了內部類與靜態內部類,那麼其初始化應該是怎麼樣的呢?


都是直接 new ?還是看看程式碼:


public class H {
    int a = 1;
    public class A {
        public void Show() {
            System.out.print("a:" + a);
        }
    }
    public static class B {
        public void Show(H h) {
            System.out.print("a:" + h.a);
        }
    }
    public static void main(String[] args) {
        H h = new H();
        //A a = new A();
        A a1 = h.new A();
        B b = new B();
        //B b1 = h.new B();
        B b3 = new H.B();
    }
}


其中註釋了的兩種方式是不允許的方式,也就是無法正常執行。


A 因為有一個隱藏的引用,所以必須是H 的例項才能進行初始化出A 類;而B類則是因為是在H 類中以靜態方式存在的類,所以需要 new H.B();之所以能直接使用new B(),與該 main 方法在 H 類中有關,因為本來就在 H類中,所以直接使用 H類的靜態屬性或者方法可以不加上:“H.”  在前面。


內部類的繼承

直接繼承的情況:


img


可以看出報錯了,為什麼?因為需要傳遞一個 H 類進去,所以我們在繼承的時候需要顯示的指明:


public class I extends H.A{
    public I(H h){
        h.super();
    }
}


也就是在構造方法中,傳遞一個 H 的引用進去,並呼叫 H 例項的 super() 方法,才能進行例項化。


使用的話應該這樣:


    public static void main(String[] args) {
        H h = new H();
        I i = new I(h);
    }


而,如果是繼承其靜態巢狀類,則不需要這樣:


public class J extends H.B{
}


就這樣就OK。


哎,差不多了~~整個內部類的東西差不多就是這些了,希望對大家有所幫助。


相關文章