程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java中的內部類

Java中的內部類

編輯:JAVA綜合教程

Java中的內部類


Java中的內部類

總結一下內部類,如有錯誤或者不足,歡迎交流討論。

內部類的定義,作用 內部類的分類 和內部類相關的幾個問題

1、什麼是內部類,為什麼需要內部類,它有什麼用?

在一個類的內部定義一個類,就是內部類。內部類提供了某種進入外圍類的窗口;每個內部類都能獨立繼承子一個實現(接口或抽象類),無論其外圍類是否實現,對內部類都沒有影響;實現多繼承,Java的中繼承是單根繼承,只能繼承自一個類,雖然可以實現多個接口,但是如果要繼承自多個類,那麼只有內部類才可以解決,思路是外圍類繼承一個類,內部類繼承另外一個類,雖然內部類只繼承了一個類,但是因為它有一個隱式的指向外圍類對象的引用(非靜態內部類才可以),它可以訪問外圍類的成員,若是要訪問外圍類的方法,可以定義一個生成對外圍類對象的引用的方法,這樣就是實現了“多繼承”。

2、內部類的分類

2.1 普通內部類

直接在一個類裡面再定義一個類,不在某個方法裡面。創建內部類的對象不能像普通類一樣,由於隱含有指向外圍類對象的引用,必須先創建外圍類。如下所示(還實現了繼承自兩個類):

package innerclass;
/**
 * 普通內部類,並實現“多繼承”
 * @author Administrator @date 2016年3月28日
 * http://blog.csdn.net/xiaoguobaf
 */
class A{
    public A(){
        System.out.println("class A constructor");
    }

    public void f(){
        System.out.println("class A f()");
    }
}

class B{
    public B(){
        System.out.println("class B constructor");
    }

    public void f(){
        System.out.println("class B f()");
    }

    class InnerClass extends A{
        public InnerClass(){
            System.out.println("class InnerClass constructor");
        }
        //返回生成內部類的外圍類對象的引用
        public B outer(){
            return B.this;
        }
    }

    //典型新建內部類情況,外圍類有一個返回內部類對象的引用的方法
    public InnerClass inner(){
        return new InnerClass();
    }
}

public class CommonInnerClass {

    public static void main(String[] args) {
        B b = new B();
        //通過外圍類創建內部類
        B.InnerClass ic1 = b.new InnerClass();
        //通過外圍類的方法創建內部類,典型方法
        B.InnerClass ic2 = b.inner();
        ic1.f();
        //使用內部類訪問外圍類的f()
        ic2.outer().f();
        //驗證outer()方法返回的引用就是創建內部類的外圍類的引用,打印其hashcode
        System.out.println(b);
        System.out.println(ic2.outer());
    }
}
/**
 輸出:
class B constructor
class A constructor
class InnerClass constructor
class A constructor
class InnerClass constructor
class A f()
class B f()
innerclass.B@2a139a55
innerclass.B@2a139a55
 */

2.2 局部內部類

在方法中定義的類,它不能有訪問說明符,因為不是外圍類的一部分,它屬於包含它的方法。另外,如果要在內部類中使用在外部定義的對象,為了防止在外圍類中被修改,應將該對象引用修飾為final。

package innerclass;
/**
 * 局部類
 * @author Administrator @date 2016年3月28日
 * http://blog.csdn.net/xiaoguobaf
 */
interface Counter{
    int next();
}
public class LocalInnerClass {
    private int count = 0;

    //內部類要使用name,定義為final是為了防止外圍類對name進行了修改
    Counter getCounter(final String name){
        class LocalCounter implements Counter{

            public LocalCounter(){
                System.out.println("LocalCounter constructor");
            }

            public int next(){//繼承、實現不能縮小訪問范圍
                System.out.print(name);
                return count++;
            }

        }
        return new LocalCounter();
    }
    public static void main(String[] args) {
        LocalInnerClass l = new LocalInnerClass();
        Counter c  = l.getCounter(" LocalInnerClass ");
        for (int i = 0; i < 4; i++)
            System.out.println(c.next());
    }
}
/**
 * 輸出:
 LocalCounter constructor
 LocalInnerClass 0
 LocalInnerClass 1
 LocalInnerClass 2
 LocalInnerClass 3
 */

2.3匿名內部類

匿名內部類看起來就像在創建一個類的對象時,突然插入了一個類的定義。因為沒有名字,所以沒有構造器。沒有構造器也帶來了一個有趣的問題,如何初始化呢?在初始化的博客中,我寫到,實例初始化在構造器之前,就是因為匿名內部類的存在,它沒有構造器,可以利用實例初始化當做構造器使用,所以,實例初始化是在構造器之前執行。

如果某一內部類只是需要一個對象,那麼定義為匿名內部類最合適不過了。不過也有缺點,匿名內部類只能實現一個接口或繼承自一個類,不能實現兩個接口,或者一個接口並繼承自一個類,因為是匿名的,若可以兩個,那麼返回類型(通過new)無法確定。

package innerclass;
/**
 * 匿名內部類,在一個包內,Counter接口在上面已經定義了
 * @author Administrator @date 2016年3月28日
 * http://blog.csdn.net/xiaoguobaf
 */
public class AnonymousInnerClass {
    private int count = 0;
    Counter getCounter(final String name){
        return new Counter(){
            //實例初始化,當做構造器使用
            {
                System.out.println("Instance initialization");
            }
            public int next(){
                System.out.print(name);
                return count++;
            }
        };
    }
    public static void main(String[] args) {
        AnonymousInnerClass a = new AnonymousInnerClass();
        Counter c2 = a.getCounter(" Anonymous inner ");
        for(int i = 0 ; i < 4;i++)
            System.out.println(c2.next());
    }
}
/**
 * 輸出:
Instance initialization
 Anonymous inner 0
 Anonymous inner 1
 Anonymous inner 2
 Anonymous inner 3
 */

2.4 靜態內部類

又名嵌套類(注意不是嵌套的類)。如其名,內部類的定義前加了static修飾符,這時靜態內部類與外圍類無聯系,創建靜態內部類的對象不需要先創建外圍類的對象,也不能訪問非靜態的外圍類對象,也稱為嵌套類。

靜態內部類與普通內部類的區別除了上述外,普通內部類的成員或方法無法被static修飾。這是一個編譯問題,先從靜態成員變量說起,靜態成員變量是在類加載時初始化的,也就是第一次創建類的對象或者訪問靜態成員變量,它在類的內存內存分布中是有確切的位置的,對於內部類來說,其內存的分配和非靜態的成員變量是類似的,不創建外圍類的對象,那麼成員變量的值就不確定,從而其內存位置也不確定,且外圍類對象的內存位置也不確定,其引用的內存位置也就不能確定,而普通內部類的創建依賴外圍類的對象的創建,所以,普通的內部類是不能有static修飾的成員變量和方法的。

package innerclass;
/**
 * 靜態內部類
 * @author Administrator @date 2016年3月28日
 * http://blog.csdn.net/xiaoguobaf
 */
interface Contents{
    int value();
}

interface Destination{
    String readLabel();
}

public class NestedClass {

    private static class ParcelContents implements Contents{
        private int i = 11;
        public int value(){
            return i;
        }

        void printf(){
            System.out.println("ParcelContents: "+i);
        }
    }   

    protected static class ParcelDestination implements Destination{
        private String label;

        private ParcelDestination(String label){
            this.label = label;
        }
        public String readLabel(){
            return label;
        }

        public static void f(){}
        static int x = 10;

        static class AnotherLevel{
            static int x = 10;
            public static void f(){}
        }
    }

    public static Destination destination(String s){
        return new ParcelDestination(s);
    }
    public static Contents contents(){
        return new ParcelContents();
    }

    public static void main(String[] args) {
        Contents c = contents();
        Destination d = destination("Yanni");
        c.value();
    }

}

這段例程例程有些地方還需要分析,在後面的內部類與向上轉型。

接口內部類,即在接口裡面定義的類,接口中的任何類都是隱式public和static的。例如:

package innerclass;
/**
 * 接口內部類
 * @author Administrator @date 2016年3月28日
 * http://blog.csdn.net/xiaoguobaf
 */
public interface ClassInsideInterface {
    void howdy();

    class Test implements ClassInsideInterface{
        @Override
        public void howdy() {
            System.out.println("howdy");
        }
        public static void main(String[] args){
            new Test().howdy();
        }
    }
}

編譯器不會報錯,接口內部類可用來實現接口的公共代碼,該代碼可以被不同的實現所公用。

為每個類編寫測試,可用嵌套類,就不用每個都編寫。

3、內部類的幾個問題

內部類與向上轉型
將內部類向上轉型為基類,尤其是接口,內部類的實現細節就隱藏了,並且不可見,若內部類是private的,那麼在非外圍類中還無法向下轉型,可以說實習細節完全隱藏了
package innerclass;
/**
 * 內部類和向上轉型
 * @author Administrator @date 2016年3月29日
 * http://blog.csdn.net/xiaoguobaf
 */
public class InnerClassAndUpcasting {

    private class ParcelContents implements Contents{
        private int i = 11;
        public int value(){
            return i;
        }

        //默認是private的,隨類,實際上沒什麼用,搭配public配合接口,向上轉型為接口類型,可實現完全隱藏實現細節
        void printf(){
            System.out.println("ParcelContents: "+i);
        }
        //只在InnerClassAndUpcasting中能訪問,無特色,在protected修飾的類中還可以訪問,但沒有必要,不如聲明為private的,
        public void callPrintf(){
            printf();
        }       
    }   

    protected class ParcelDestination implements Destination{
        private String label;

        private ParcelDestination(String label){
            this.label = label;
        }
        public String readLabel(){
            return label;
        }
    }

    public Destination destination(String s){
        return new ParcelDestination(s);
    }
    public Contents contents(){
        return new ParcelContents();
    }

    public static void main(String[] args) {
        InnerClassAndUpcasting inau = new InnerClassAndUpcasting();
        Contents c = inau.contents();
        Destination d = inau.destination("Yanni");
        //必須先要向下轉型才能訪問子類型的方法,因為繼承擴展信息,父類不能訪問子類中擴展的信息
//      c.printf();//提示方法沒有定義
        ((ParcelContents)c).printf();
        ((ParcelContents)c).callPrintf();
        d.readLabel();
    }
}
/**
 * 一個class文件裡面可以有兩個main函數,只是在執行時eclipse將提示你要執行哪個,我這裡是為了方便測試
 * @author Administrator @date 2016年3月29日
 * http://blog.csdn.net/xiaoguobaf
 */
class Test {
    public static void main(String[] args){
        InnerClassAndUpcasting inau1 = new InnerClassAndUpcasting();
        Contents c1 = inau1.contents();
        System.out.println(c1.value());
        //無法向下轉型,由於ParcelContents類是private,出了其外圍類InnerClassAndUpcasting,無法訪問
        //((ParcelContents)c).printf();
    }
}
/**
 * 輸出:
ParcelContents: 11
ParcelContents: 11

11
*/  

\

多層嵌套的內部類的訪問
多層嵌套的內部類可以透明的訪問所有它所嵌入的外圍類的所有成員,很好理解,因為有指向外圍類的引用(非static的),靜態內部類可以理解為和靜態成員變量一樣的,從內存角度來看的話。 內部類的繼承
內部類可以繼承,語法特殊些,因為要將指向外圍類的引用初始化(非static)。
package innerclass;
/**
 * 內部類的繼承
 * @author Administrator @date 2016年3月29日
 * http://blog.csdn.net/xiaoguobaf
 */
class WithInner{
    class Inner{}
}
public class InnerClassInheriting extends WithInner.Inner{
    InnerClassInheriting(WithInner wi){
        wi.super();
    }
    public static void main(String[] args){
        WithInner wi = new WithInner();
        InnerClassInheriting ic = new InnerClassInheriting(wi);
    }
}

內部類可以覆蓋麼
內部類不可以覆蓋,就像在父類和子類中定義同名同屬性的成員類似,是不同的。

內部類與閉包、回調
閉包,是一個可調用的對象,它的信息來自於創建它的作用域,內部類是面向對象的閉包,因為它包含外圍類對象的信息,還默認擁有(非static)一個指向外圍類對象的引用。
回調:簡單的來說,就是你調用我,我調用你,先寫到這裡。看TIJ我還覺得上面例子有點想策略模式,查的結果有的說想觀察者模式。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved