Java內部類是如何實現的


內部類(inner class)是定義在另一個類中的類。

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

以下簡單舉例:

一個時鍾需要兩個參數:發布通告的時間間隔以及開關鈴聲的標志。

public class TalkingClock {

    private int interval;
    private boolean beep;

    public TalkingClock(int interval, boolean beep){
        this.interval = interval;
        this.beep = beep;
    }

    public void start(){

        ActionListener listener = new TimePrinter();
        Timer timer = new Timer(interval, listener);
        timer.start();
    }

    public class TimePrinter implements ActionListener{

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("At the tone, the time is " + new Date());
            if (beep) {
                Toolkit.getDefaultToolkit().beep();
            }
        }
    }

    public static void main(String[] args) {
        TalkingClock clock = new TalkingClock(1000, true);
        clock.start();

        //keep program running until user selects "OK"
        JOptionPane.showMessageDialog(null, "Quit program?");
        System.exit(0);
    }
}

本代碼主要想說明的是作為內部類的TimePrinter可以直接訪問外部類TalkingClock的私有成員變量beep。

語法和用法大家很容易就學會了,那么內部類是如何實現直接訪問其他類的私有成員變量呢?我們知道Java的私有成員變量只有類的實例本身能訪問。

在最初學習Java語法的時候,是把內部類當做該類的一個屬性去記憶和理解的,因此,自然可以訪問私有變量。這個理解,現在看來是錯誤的。

內部類是一種編譯器現象,與虛擬機無關。編譯器將會把內部類翻譯成用$分隔外部類名與內部類名的常規類文件,而虛擬機對此一無所知。

上述內部類被編譯成TalkingClock$TimePrinter :

 javap -private TalkingClock\$TimePrinter.class 
Compiled from "TalkingClock.java"
public class com.test.java.clazz.innerclass.TalkingClock$TimePrinter implements java.awt.event.ActionListener {
  final com.test.java.clazz.innerclass.TalkingClock this$0;
  public com.test.java.clazz.innerclass.TalkingClock$TimePrinter(com.test.java.clazz.innerclass.TalkingClock);
  public void actionPerformed(java.awt.event.ActionEvent);
}

可以看到,多了一個成員變量 final com.test.java.clazz.innerclass.TalkingClock this$0;,以及構造函數。內部類將通過這種方式持有外部類。

內部類的一個特性是可以訪問外部的私有成員變量,這又是如何做到的呢。

查看外部類:

javap -private TalkingClock.class 
Compiled from "TalkingClock.java"
public class com.test.java.clazz.innerclass.TalkingClock {
  private int interval;
  private boolean beep;
  public com.test.java.clazz.innerclass.TalkingClock(int, boolean);
  public void start();
  public static void main(java.lang.String[]);
  static boolean access$000(com.test.java.clazz.innerclass.TalkingClock);
}

可以發現,編譯器給添加了新的靜態方法static boolean access$000(com.test.java.clazz.innerclass.TalkingClock);,顯然,我們可以通過類名調用這個方法來獲取私有變量,相當於給添加了getter方法暴露私有變量。

查看變異后的內部類的actionPerformed方法:

public void actionPerformed(ActionEvent e) {
        System.out.println("At the tone, the time is " + new Date());
        if (TalkingClock.access$000(this.this$0)) {
            Toolkit.getDefaultToolkit().beep();
        }

    }

綜上,內部類通過編譯器修改代碼,實現了對類的訪問。如果黑客在同包名下調用外部類的access$000,則是可以訪問到私有變量的,這算是一個危險的地方吧。

局部內部類

仔細觀察示例代碼,TimePrinter只在start方法里調用了,我們可以把這個類縮小為局部內部類,即在方法內部聲明內部類。

局部內部類可以訪問局部變量

我們將上述demo修改下:

public void start(int interval, boolean beep) {
        class TimePrinter implements ActionListener {

            @Override
            public void actionPerformed(ActionEvent e) {

                System.out.println("Every "+interval+", At the tone, the time is " + new Date());
                if (beep) {
                    Toolkit.getDefaultToolkit().beep();
                }
            }
        }

        ActionListener listener = new TimePrinter();
        Timer timer = new Timer(interval, listener);
        timer.start();
    }

局部類不能用public或private訪問說明符進行說明,它的作用被限定在聲明這個局部類的塊中。

上述demo,局部內部類TimePrinter會訪問局部變量interval和beep。同理,依舊是將這兩個變量轉換程內部類的成員變量,實現值的傳遞。

匿名內部類

public class AnonymousInnerClassTest {

    @Data
    static class Animal {
        private String name;

        public void sayHi(){
            System.out.println("Hi " + name);
        }
    }

    public static void main(String[] args) {
        Animal animal = new Animal(){
            @Override
            public void sayHi() {
                System.out.println("Wang");
            }
        };
        animal.sayHi();
    }
}

上述簡單模擬一種Animal匿名類。編譯后的結果為:

'AnonymousInnerClassTest$1.class'  'AnonymousInnerClassTest$Animal.class'   AnonymousInnerClassTest.class 

編譯器生成了3個編譯類。

AnonymousInnerClassTest

public class AnonymousInnerClassTest {
    public AnonymousInnerClassTest() {
    }

    public static void main(String[] args) {
        com.test.java.clazz.innerclass.AnonymousInnerClassTest.Animal animal = new 1();
        animal.sayHi();
    }
}

AnonymousInnerClassTest$1

import com.test.java.clazz.innerclass.AnonymousInnerClassTest.Animal;

final class AnonymousInnerClassTest$1 extends Animal {
    AnonymousInnerClassTest$1() {
    }

    public void sayHi() {
        System.out.println("Wang");
    }
}

AnonymousInnerClassTest$Animal

class AnonymousInnerClassTest$Animal {
    private String name;

    public void sayHi() {
        System.out.println("Hi " + this.name);
    }

    public AnonymousInnerClassTest$Animal() {
    }

    //getter setter equals toString
}

即編譯器生成一個包訪問權限的內部類AnonymousInnerClassTest$Animal, 對於其匿名子類,又生成了final的AnonymousInnerClassTest$1 ,如果還有第二個匿名類,則命名AnonymousInnerClassTest$2.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM