【Java】 內部類

【Java】內部類

能夠將一個類的定義放在另外一個類的定義內部,這就是內部類。
使用內部類的的緣由主要有三點:java

  • 內部類方法能夠訪問該類定義所在的做用域中的數據,包括私有的數據。
  • 內部類能夠對同一個包中的其餘類隱藏起來。
  • 當想要定義一個回調函數且不想編寫大量代碼時,使用匿名(anonymous)內部類比較便捷

使用內部類訪問對象狀態

public class TalkingClock {
    private int interval;
    private boolean beep;

    public TalkingClock(int interval, boolean beep) {...}
    public void start() {...}
    
    public class TimePrinter implements ActionListener {
        Date now = new Date();
        System.out.println("At the tone, this time is" + now);
        if (beep) Toolkit.getDefaultToolkit().beep();
    }
}

內部類既能夠訪問自身的數據域,也能夠訪問建立它的外圍類對象的數據域。安全

外圍類的引用在構造器中設置。編譯器修改了全部的內部類的構造器,添加一個外圍類引用的參數。由於TimePrinter類沒有定義構造器,因此編譯器爲這個類生成了一個默認的構造器,其代碼以下:閉包

public TimePrinter(TalkingClock clock) {
    outer = clock;
}

請再注意一下,outer不是Java的關鍵字。咱們只是用它說明內部類中的機制。
當在start方法中建立了TimePrinter對象後,編輯器就會將this引用傳遞給當前的語音始終的構造器。app

ActionListener listener = new TimePinter(this); //parameter automatically added

內部類的特殊語法規則

使用外圍類引用的正規語法以下。表達式:
OuterClass.this
表示外圍類引用。例如,能夠像下面這樣編寫TimePrinter內部類的actionPerformed方法:編輯器

public void actionPerformed(ActionEvent event) {
    if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep();
}

反過來,能夠採用下列語法格式更加明確地編寫內部對象的構造器:ide

outerClass.new InnerClass(construction parameters)
例如
Actionlistner listener = this.new TimePrinter();

在這裏,最新構造的TimePrinter對象的外圍類引用被設置爲建立內部類對象的方法中的this引用。這是一種最多見的狀況。一般,this限定詞是多餘的。不過,能夠經過顯式地命名將外圍類引用設置爲其餘的對象。例如,若是TimePrinter是一個共有內部類,對於任意的語音時鐘均可以構造一個TimePrinter:函數

TalkingClock jabberer = new TalkingClock(1000, true);
Talking.TimePrinter listener = jabber.new TimePrinter();

須要注意,在外圍類的做用域以外,能夠這樣引用內部類:
OuterClass.InnerClassthis

要想直接建立內部類的對象,你不能按照你想象的方式,去引用外部類的名字,而必須使用外部類的對象來建立該內部類對象。在擁有外部類對象以前是不可能建立內部類對象的。這是由於內部類對象會暗暗地鏈接到建立它的外部類對象上。翻譯


內部類是否有用、必要和安全

  • 內部類是一種編譯器現象,與虛擬機無關。編譯器會把內部類翻譯成用$(美圓符號)分隔外部類與內部類名的常規類文件,而虛擬機一無所知。例如,在TalkingClock類內部的TimePrinter類將被翻譯成TalkingClock$TimePrineter.class。
  • 若是一個類是匿名內部類,那麼clas文件名稱是OuterClass$(1,2,3).class
  • 編譯器爲了引用外部類,生成了一個附加的實例域this$0(名字this$0是由編譯器合成的,在本身編寫的代碼中不可以引用)。

局部內部類

假設TimePrinter這個類名字只在start方法中建立這個類型的對象時使用了一次,那麼能夠像下面這樣使用:code

public void start() {
    class TimePrinter implements ActionListner {
        public void actionPerformed(ActionEvent event) {
            Date now = new Date();
            System.out.println("At the tone, the time is " + now);
            if (beep) Toolkit.getDefaultToolkit().beep();
        }
    }
    ActionListener listener = new TimePrinter();
    Timer t = new Timer(interval, listner);
    t.start();
}

局部類不能用public或private訪問說明符進行聲明。它的做用域被限定在聲明這個局部類的塊中。
局部類有一個優點,即對外部世界能夠徹底地隱藏起來。即便TaklingClock類中的其餘代碼也不能訪問。除start方法以外,沒有任何方法知道TimePrinter類的存在。


由外部方法訪問final變量

與其餘內部類比較,局部類還有一個優勢。它們不只可以訪問包含它們的外部類,還能夠訪問局部變量。不過,那些局部變量必須被聲明爲final。如:

public void start(int interval, final bolean beep) {
    class TimePrinter implements ActionListner {
        public void actionPerformed(ActionEvent event) {
            Date now = new Date();
            System.out.println("At the tone, the time is " + now());
            if (beep) Toolkit.getDefaultToolkit().beep();
        }
     }
}

編譯器實現內部類訪問局部變量的方式是這樣的:在內部類中爲每個要訪問的局部變量設置數據域,而後在構造函數中將這些數據域初始化爲要訪問的局部變量值。

匿名內部類

將局部內部類的使用再深刻一步。假如只建立這個類的一個對象,就沒必要命名了。這種類被稱爲匿名內部類(annoymous inner class)

public void start(int interval, final boolean beep) {
    ActionListener listener = new ActionListener() {
        public void actionPerformed(ActionEvent event) {
            Date now = new Date();
            System.out.println("At the tone, the time is" + now());
            if (beep) Toolkit.getDefaultToolkit().beep();
        }
    }
   Timer t = new Timer(interval, listner);
   t.start();
}

它的含義是:建立一個實現ActionListner接口的類的新對象,須要實現的方法actionPerformed定義在括號{}內。
一般的語法格式爲:

new SuperType(construction parameters) {
        inner class methods and data
    }

其中,SuperType能夠是ActionListner這樣的接口,因而內部類就要實現這個接口。SuperType也能夠是一個類,因而內部類就要擴展它。
若是你的基類須要一個有參數的構造器,應該怎麼辦:

public classs Parcel8 {
    public Wrapping wrapping(int x) {
        //Base constructor call
        return new Wrapping(x) {
            public int value() {
                return super.value() * 47;
            }
        };
    public static void main(String[] args) {
        Parcel8 p = new Parcel8();
        Wrapping w = p.wrapping(10);
    }
}

只須要簡單地傳遞合適的參數給基類的構造器便可,這裏是將x傳進new Wrapping(x)。儘管Wrapping 只是一個具備具體實現的普通類,但它仍是被其導出類當作公共「接口」來使用

public class Wrapping {
        private int i;
        public Wrapping (int x) { i = x;}
        public int value() { return i; }
    }

你會注意到, Wrapping擁有一個要求傳遞一個參數的構造器,這使得事情變的更有趣了。
在匿名類中定義定義字段時,還可以對其執行初始化操做:

public class Parcel9 {
        public Destination destination(final String dest) {
            return new Destination() {
                private String label = dest;
                public String readLabel() { return label; }
            };
        }
    public static void main(String[] args) {
        Parcel9 p = new Parcel9();
        Destination d = p.destination(「Tesmania」);
    }
}

若是定義一個匿名內部類,而且但願它使用一個在其外部定義的對象,name編譯器會要求其參數引用是final的,就像你在destinaion()的參數中看到的那樣。若是你忘記了,將會獲得一個錯誤信息。
若是隻是簡單地給一個字段賦值,那麼此例中的方法是很好的。可是,若是想作一些相似構造器的行爲。在匿名類中不可能有命名的構造器,但經過實例初始化,就可以達到爲匿名內部類建立一個構造器的效果,就像這樣:

abstract class Base {
    public Base(int i) {
        print("Base conctructor, i=" + 1);
    }
}

public class AnonymousConstructor {
    public class Base getBase(int i){
        return new Base(i){
            print("Inside instance initializer");
            
            public void f() {
                print("In anonymous f()");
            }
        }
    }
    
    public static void main(String[] args) {
        Base base = getBase(47);
        base.f();
    }
}/*
Base constructor, i = 47;
Inside instance initializer
In  anonymous f()

在此例中,不要求變量i必定是final的。覺得i被傳遞給匿名內部類的積累的構造器,它並不會在你大家內部類被直接使用。
下例是帶實例化的「parcel」形式。注意destination()參數必須是final的,由於它們是在匿名內部類使用的:

public class Parcel10 {
    public Destination destination(final String dest, final float price) {
        return new Destination(){
            private int cost;
            //Instance initialization for each object
            {
                cost = Math.round(price);
                if (cost > 100) {
                    System.out.println("Over budget");
                }
            }
            private String label = dest;
    
            public String readLabel() {
                return label;
            }
        };
        
        
    }
    
    public static void main(String[] args) {
        Parcel10 p = new Parcel10();
        Destination d = p.destination("Tasmania", 101.395.F);
    }
}
//
Over budget;
匿名內部類的重點

1.使用匿名內部類時,必須繼承或者實現一個接口,可是二者不可兼得,同時也只能繼承一個類或者實現一個接口

  1. 匿名內部類是不能定義構造函數的
  2. 匿名內部類是不能存在任何的靜態成員變量和靜態方法
  3. 匿名內部類爲局部內部類,全部局部內部類的全部限制對匿名內部類生效
  4. 匿名內部類不能使抽象的,它必需要實現繼承的類或者實現接口的全部抽象方法

嵌套類

  • 若是不須要內部類對象與其外圍類對象之間有聯繫,那麼能夠將內部類聲明爲staic。這一般稱爲嵌套類
  • 普通的內部類對象隱式地保存了一個引用,指向建立它的外圍類對象。然而,當內部類是static的時,就不是這樣了,嵌套類意味着:
    1)要建立嵌套類的對象,不須要其外圍類的對象。
    2)不能從嵌套類的對象中訪問非靜態的外圍類對象。

  • 嵌套類和普通的內部類還有一個區別。普通內部類的字段與方法,只能放在類的外部層次,因此普通的內部類不能有static數據和static字段,也不能包含嵌套類。可是嵌套類能夠包含這些東西。

閉包和回調

  • 閉包是一個可調用的對象,它記錄了一些信息,這些信息來自於建立它的做用域。經過這個定義,能夠看出內部類是面向對象的閉包,由於它不只包含外圍類的對象的信息,還自動擁有一個指向此外圍類對象的引用,在此做用域內,內部類有權操做全部的成員,包括private成員。

內部類的重點

  1. 非靜態內部類能夠訪問外部類的數據域,包括私有的
  2. 局部內部類和匿名內部類能夠訪問方法中的參數,不過參數必須爲final
  3. 匿名內部類,必需要繼承一個類或者實現一個接口,可是二者不可兼得,同時也只能繼承一個類或實現一個接口。
  4. 匿名內部類不能定義構造函數
  5. 匿名內部類中不能存在任何的靜態成員變量和靜態方法。
相關文章
相關標籤/搜索