歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java 內部類淺析

Java 內部類淺析

日期:2017/3/1 9:13:52   编辑:Linux編程

這篇文章主要講述Java 內部類的相關知識,主要講解下面的知識點。

  1. 內部類的概念
  2. 內部類的特點與使用
  3. 多種形式內部類
  4. 為什麼要使用內部類

內部類的概念

內部類是指在一個類的內部定義了另一個類。例如下面的代碼中例子,就是一個簡單的內部類。

public class A {
    private int a;
    
    public class B{
        private int b;
    }
}

在這個類中,我們可以看出內部類B就像A的成員一樣。所以我們對內部類的修飾也可以用很多種修飾符,比如我們是不可能對一個非內部類用private修飾符和protected修飾符,但是如果這個類是內部類,我們可以對他用private和protected訪問修飾符以及static和final修飾符等等,就像對一個成員添加修飾符一樣。當我們對B類添加多種修飾符以後,我們就可以把B類看成隱藏在A類中,像是一種代碼隱藏機制。這樣的內部類有什麼特點以及我們如何去使用他呢?

內部類的特點與使用

在上面的例子中,我們把B類看成是A類的一個成員,那麼這個成員與普通的成員變量和成員方法有什麼區別呢? 我們可以很容易的在B類訪問到A類中任何成員。但是如果我們想在A類中去訪問B類的成員就不這麼輕松了。如果B類是一個非靜態類,那我們就要在外部類中聲明一個B類的引用去調用B類中的成員。如下例子所示:

public class A {
    private int a;

    public B getB(){
        return new B();
    }

    public void show(){
        System.out.println(getB().b);
    }
    protected  class B{
        public int b;

        public void print(){
            System.out.println(a);
        }
    }

    public static void main(String[] args) {
        new A().show();
    }
}

在這個例子中,我們可以看到B類中,是很隨意就能訪問到A類中的private成員變量a,但是A類中要訪問B類中的public成員變量b也是要先定義一個B類的對象,通過這個對象去訪問b。這就是內部類一個典型的特點。外部類的一切對內部類都是透明的,但是內部類確可以對外部類隱藏實現的細節。造成這樣的原因是因為當某個外部類對象創建一個內部類對象的時候,此內部類的對象必定會秘密的捕獲一個指向外部類的引用。這些都是編譯器幫你做的。但是如果B類用static修飾也就是變成一個靜態類,那麼B類中也只能訪問A類中的靜態成員。
注意,如果上面的show()方法,如果show()是一個靜態的方法,那麼就不能直接new B()一個對象而是要用a的對象去創建B的對象。
那如果在B類中使用this指的是哪個對象的?按照this的定義顯然指向的當前對象B,如果我們要在B類中顯式調用A類的對象,我們就要用到一個特殊的語法 . this 顯式調用A的對象就是A.this
那如果我們要在一個其他類中的創建B類對象呢?那我們需要用到一個新的語法.new。代碼如下:

  A a = new A();
  A.B b = a.new B();

這樣就在其他類中創建了一個B的對象。在A類中就不需要這樣麻煩了,因為會默認省略一個this,所以看起來只是new B()這樣簡單。
當然,前面說了B類是可以用static修飾,這個時候情況就變了。首先在其他類中創建一個B的對象,我們就不能用A的對象去創建B了 因為B是一個靜態類不能用對象去引用,這是我們可以用如下代碼去創建B的對象

 A a = new A();
 A.B b = new A.B();

或者直接用A.B去調用B類中的靜態方法。A.B表示訪問A類中的B類
其次,如果我們在A類中寫一個靜態方法,那麼就不需要去A.B,直接用B去調用B類中靜態成員。代碼如下:

public class A {
    public static class B{
        public int a = 1;
        public int bb;
        public static String print(){
            return "1";
        }
    }

    public static void main(String[] args) {
        B.print();
    }
}

注意一點,只有B類是靜態類才能由靜態成員。

多種形式的內部類

我們上面已經了解到了內部類的特點以及如何使用內部類,接下來我們了解一些內部類的類型,Java語言並沒有規定內部類必須出現在哪裡。所以內部類可以出現在方法內部,出現在作用域中,還可能還是匿名內部類.

方法內部的內部類——局部內部類

我們可以在方法內部定義一個類,這在一定時候是有必要的。這個時候類不能有修飾符。例子如下:

public class A {
    public C getC(){
            class B extends C{
            @Override
            public void show() {
                System.out.println("我是B");
            }
        }
        return new B();
    }
    public static void main(String[] args) {
        A a = new A();
        a.getC().show();
    }
}
abstract class C{
    public abstract void show();
}

作用域中的內部類

這裡指的是內部類出現在方法中的一個作用域內,這樣說可能是難懂,比如方法中有一個if語句,內部類寫在這個if語句中,這就是內部類出現在一個作用域內。雖然這不常見,但是Java語言是支持這種語法的。

public class A {

    public C getC(boolean flag) {
        if (flag) {
            class B extends C {

                @Override
                public void show() {
                    System.out.println("我是B");
                }
            }

            return new B();
        }
        return null;
    }
    public static void main(String[] args) {
        A a = new A();
        a.getC(true).show();
    }
}

abstract class C{
    public abstract void show();
}

這個例子,不代表B類的生成是有條件的,相反,B類和A類在編譯的時候就一起生成了。

匿名內部類

接下來,我們改寫一下局部內部類的例子,讓他看上去是這樣的。

public class A {

    public C getC() {
        return new C() {
            @Override
            public void show() {
                System.out.println("我的匿名內部類");
            }
        };
    }
    public static void main(String[] args) {
        A a = new A();
        a.getC().show();
    }
}

abstract class C{
    public abstract void show();
}

這樣看上去就簡潔很多,也怪異一些。返回值的生成與表示這個返回值的類的定義結合在一起。這個類的定義還沒有名字。這個語法就叫做匿名內部類。

創建一個繼承自C的匿名類的對象。通過new表達式返回的引用被自動向上轉型為對C的引用。

匿名內部類,主要的功能就是創建一個類的子類,然後復寫父類中的方法,然後將子類的對象作為返回值返回。這個常見於接口的回調,也就是C類不在是抽象類而是一個接口。我們無法返回接口的對象,所以只能返回實現了接口的類的對象,這時用匿名內部類的形式來表示這個類的對象是再好不過了。但是這也有缺點,因為匿名內部類無法第二次使用。我們需要不止一個該類的對象的時候,就很尴尬了。我們接下來繼續了解匿名內部類的特點,當了解完我們就清楚什麼時候該用匿名內部類啦。
上面的代碼中是通過默認的構造器來new一個C的對象,我們如果需要一個有參數的構造器來創建C對象,我們就可以直接C類中定義一個有參數的構造器,通過new C的時候直接把參數傳入。

public class A {

    public C getC() {
        return new C(2) {
            @Override
            public void show() {
                System.out.println(a);
                System.out.println("我的匿名內部類");
            }
        };
    }
    public static void main(String[] args) {
        A a = new A();
        a.getC().show();
    }
}
abstract class C{
    int a;
    public C(int a){
        this.a = a;
    }
    public abstract void show();
}

有的時候我們的匿名內部類所在的方法需要一個形參,而這個形參則在匿名內部類中被使用,這個時候我們需要指定這個形參為final類型。直接看這個簡單的例子就可以。

public class OuterClass {  
    public void display(final String name,String age){  
        class InnerClass{  
            void display(){  
                System.out.println(name);  
            }  
        }  
    }  
}  

這種一定要加final的原因是因為內部類和外部類完全是兩個不同的類,也就是說我們在內部類改變了形參的東西,但是對於外部類而言,這個形參沒有改變,因為這個形參對於內部類和外部類是兩個東西。但是對於程序員而言這就是一個參數,所以為了避免這種尴尬的事情,就加上final來使形參不發生改變。還有另一種解釋,就是加上final 形參,則表示給了內部類一個該形參的副本,讓內部類可以對他進行任意操作。
有的時候,如果我們要給我們的匿名內部類加一個構造器來進行一些初始化的時候,就很尴尬了,因為我們並不能添加構造器,這時可以用初始化塊的形式來進行一些初始化,但是要注意一點,初始化塊不能是靜態的,而且匿名內部類也不能用靜態的成員。
所以到這裡,我們也可以發現不用匿名內部類的一個理由,當我們需要一個有名字的構造器的時候,我們就不能用匿名內部類。

為什麼使用內部類

如果只是需要實現一個接口或者繼承一個抽象類那麼直接用外部類就可以了,不需要內部類,但是如果我們需要多繼承呢? 內部類最引人的地方就是

每個內部類都能獨立地繼承一個類或者一個接口的實現,所以無論外部類是否已經繼承了類或者實現了一個接口,都不影響內部類。

所以內部類可以是多繼承變得更加完整。而且使用內部類還會有更多新特性。

  1. 內部類可以有多個實例,每個實例都有自己的狀態。與外部類互相獨立。
  2. 在一個外部類裡可以寫多個內部類去實現同一個接口或者繼承同一個類而有不同的實現。
  3. 在外部類中創建內部類的對象,並不依賴於外部類對象的創建。
  4. 在基於回調的事件的時候,匿名內部類是個很好的選擇。

Copyright © Linux教程網 All Rights Reserved