Java內部類

一、內部類基礎

  1. 成員內部類
    先看一段代碼
public class Tigger{
    String name;
    public static void main(String[] args){
        Fox fox = new Fox();
        Tigger tigger = new Tigger();
        tigger.name = "大哥";
        Tigger.PersianCat cat = tigger.new PersianCat();

        fox.speak(cat.name);
        cat.speak(fox.name);

    }
    public void speak(String name){
        System.out.println("if you touch him,I will kill you,"+name);
    }
    class PersianCat extends Cat{
        String name = "little cat";
        public void speak(String who){
            miao();
            System.out.println("Hi,it is you "+name+"? Talk to "+who);
            Tigger.this.speak(who);
        }
    }
}
class Fox{
    String name = "fox";
    public void speak(String to){
        System.out.println("I am a fox,I will eat you,"+to);
    }
}
class Cat{
    public void miao(){
        System.out.println("喵喵~~");
    }
}

執(zhí)行結果

I am a fox,I will eat you,little cat
喵喵~~
Hi,it is you little cat? Talk to fox
if you touch him,I will kill you,fox

我們看到,成員內部類可以訪問外部類的一切成員變量和方法(包括static變量和static方法)。如果內部類出現(xiàn)和外部類相同簽名的方法或者同名同類型的成員變量,那么內部類的相關數據的訪問會覆蓋外部類的相關數據。實際上這個過程有點像繼承,并且內部類還可以繼承自其他類,這就彌補了java不能多繼承的缺點。

  1. 為什么成員內部類可以訪問外部類的一切變量和方法?
    我們看下javac Tigger.java生成的class文件
Tigger$PersianCat.class
Tigger.class
Fox.class
Cat.class

我們用javap命令看下Tigger$PersianCat.class文件

class Tigger$PersianCat extends Cat {
  java.lang.String name;

  final Tigger this$0;

  Tigger$PersianCat(Tigger);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LTigger;
       5: aload_0
       6: invokespecial #2                  // Method Cat."<init>":()V
       9: aload_0
      10: ldc           #3                  // String little cat
      12: putfield      #4                  // Field name:Ljava/lang/String;
      15: return

  public void speak(java.lang.String);
    Code:
       0: aload_0
       1: invokevirtual #5                  // Method miao:()V
       4: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: aload_0
       8: getfield      #4                  // Field name:Ljava/lang/String;
      11: aload_1
      12: invokedynamic #7,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
      17: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      20: aload_0
      21: getfield      #1                  // Field this$0:LTigger;
      24: aload_1
      25: invokevirtual #9                  // Method Tigger.speak:(Ljava/lang/String;)V
      28: return
}

我們看到有這樣一行代碼

  final Tigger this$0;

這行代碼的意思是,內部類需要持有外部類的一個引用,因此內部類可以訪問外部類的一切成員變量和方法。也正因為如此,當我們在外部類以外想new一個內部類對象出來的時候,需要顯示的將外部類對象綁定到內部類對象上

Tigger.PersianCat cat = tigger.new PersianCat();
  1. 內部類可能存在的隱患
    通過上面分析,我們知道了,內部類持有了一個外部類的引用,因此容易引發(fā)內存泄漏。我們舉個例子
public class TestActivity extends Activity {
    private static Dog dog ;
    class Dog {
        public void say(){
            System.out.println("I am lovely dog!");
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        dog = new Dog();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

一旦TestActivity調用了onCreate方法,那么即使Activity退出,內存也將無法回收。因為靜態(tài)變量dog持有了Activity的一個引用,除非手動將dog置null,堆上的dog對象才會被回收,進而Activity被回收。

二、靜態(tài)內部類

  1. 我們稍微把上面的代碼改下
public class Tigger{
    String name;
    public static void main(String[] args){
        Fox fox = new Fox();
        Tigger tigger = new Tigger();
        tigger.name = "大哥";
        Tigger.PersianCat cat = new Tigger.PersianCat();

        fox.speak(cat.name);
        cat.speak(fox.name);

    }
    static String elderBrother(){
        return "老虎";
    }
    public void speak(String name){
        System.out.println("if you touch him,I will kill you,"+name);
    }
    static class PersianCat extends Cat{
        String name = "little cat";
        public void speak(String who){
            miao();
            System.out.println("if you thouch me,I will call my 大哥 "+elderBrother());
        }
    }
}
class Fox{
    String name = "fox";
    public void speak(String to){
        System.out.println("I am a fox,I will eat you,"+to);
    }
}
class Cat{
    public void miao(){
        System.out.println("喵喵~~");
    }
}
----------------------------------
I am a fox,I will eat you,little cat
喵喵~~
if you thouch me,I will call my 大哥 老虎

我們看到,外部類對靜態(tài)內部類而言,更多的只像一個命名空間

Compiled from "Tigger.java"
class Tigger$PersianCat extends Cat {
  java.lang.String name;

  Tigger$PersianCat();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method Cat."<init>":()V
       4: aload_0
       5: ldc           #2                  // String little cat
       7: putfield      #3                  // Field name:Ljava/lang/String;
      10: return

  public void speak(java.lang.String);
    Code:
       0: aload_0
       1: invokevirtual #4                  // Method miao:()V
       4: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: invokestatic  #6                  // Method Tigger.elderBrother:()Ljava/lang/String;
      10: invokedynamic #7,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
      15: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      18: return
}

靜態(tài)內部類不持有外部類的引用。靜態(tài)內部類可以訪問外部類的一切靜態(tài)變量和方法。

三、匿名內部類

  1. 看代碼

public  class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    public Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mHandler = new Handler(){
           @Override
           public void handleMessage(Message message){
               test();
           }
        };
    }
    public void test(){
    }
}

mHandler所指向的對象的類就是一個匿名類。為什么這么說呢,我們看到編譯生成的class文件列表里有這樣一個class文件

MainActivity$1.class

我們反編譯看下

Compiled from "MainActivity.java"
class com.debug.pluginhost.MainActivity$1 extends android.os.Handler {
  final com.debug.pluginhost.MainActivity this$0;

  com.debug.pluginhost.MainActivity$1(com.debug.pluginhost.MainActivity);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Lcom/debug/pluginhost/MainActivity;
       5: aload_0
       6: invokespecial #2                  // Method android/os/Handler."<init>":()V
       9: return

  public void handleMessage(android.os.Message);
    Code:
       0: aload_0
       1: getfield      #1                  // Field this$0:Lcom/debug/pluginhost/MainActivity;
       4: invokevirtual #3                  // Method com/debug/pluginhost/MainActivity.test:()V
       7: return
}

我們看到,mHandler所指向的對象的類是com.debug.pluginhost.MainActivity$1它繼承自android.os.Handler。所以一般說來,匿名內部類用于繼承其他類或者實現(xiàn)接口,并不需要增加額外的方法,只是重寫父類方法或者實現(xiàn)接口。這里需要注意的地方是,匿名內部類也持有了外部類的一個引用,因此也是存在內存泄漏的安全隱患的。比如常見的Handler引起的內存泄漏。

public class TestActivity extends Activity {
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            System.out.println("===== handle message ====");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        // 會導致內存泄露,非靜態(tài)內部類(包括匿名內部類,比如這個 Handler 匿名內部類)會引用外部類對象 this(比如 Activity)
        // 當它使用了 postDelayed 的時候,如果 Activity 已經 finish 了,而這個 handler 仍然引用著這個 Activity 就會致使內存泄漏
        // 因為這個 handler 會在一段時間內繼續(xù)被 main Looper 持有,導致引用仍然存在.
        handler.sendEmptyMessageDelayed(0x123, 12000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

如果進入TestActivity后立即退出,這段代碼肯定會引起內存泄漏。原因是變量handler持有了TestActivity的一個引用,調用

 handler.sendEmptyMessageDelayed(0x123, 12000);

后,一個Message消息message又持有了handler的一個引用,message又被MessageQueue對象queue持有,queue又被當前線程的Looper對象持有,Looper對象又被當前線程持有,從而引發(fā)內存泄漏。解決的辦法之一是

   @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
    }

在Activit退出的時候,清除所有未處理的消息和回調。
或者

  private Handler handler = new MyHandler(this);
  static class MyHandler extends Handler{
        WeakReference<TestActivity> reference = null;
        @Override
        public void handleMessage(Message message){
            Log.d("TestActivity","--------------");
            reference.get().test();

        }
        public MyHandler(TestActivity testActivity){
            reference = new WeakReference<TestActivity>(testActivity);
        }
    }

第二種方法有個問題,就是當

handler.sendEmptyMessageDelayed(0x123, 12000);

回調的時候,TestActivity對象可能已經被回收了。如果不使用靜態(tài)內部了,也不調用

handler.removeCallbacksAndMessages(null);

那么在消息回調之前TestActivity對象是絕對不會被回收的。

四、局部內部類

局部內部類比較少用,我就不說了。

五、總結

關于內部類,我們盡可能使用靜態(tài)內部類,因為這樣可以減少不必要的麻煩。如果靜態(tài)內部類想訪問外部類的數據,我們可以通過持有外部類的一個弱引用對象來達到目的。如下:

public class TestActivity extends Activity {
    private MyHandler myHandler = new MyHandler(this);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        
    }

    static class MyHandler extends Handler {
        private WeakReference<Activity> mWeakReference;

        public MyHandler(Activity activity){
            mWeakReference = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Toast.makeText(mWeakReference.get(), "xxxx", Toast.LENGTH_LONG).show();
            Log.d("xx", mWeakReference.get().getPackageName());
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

    }
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 轉載:https://juejin.im/post/5a903ef96fb9a063435ef0c8 本文將會從以...
    福later閱讀 478評論 0 3
  • 搞懂 JAVA 內部類 前些天寫了一篇關于 2018 年奮斗計劃的文章,其實做 Android 開發(fā)也有一段時間了...
    醒著的碼者閱讀 647評論 0 0
  • 在Java種類的使用是十分基礎的,但是常規(guī)類往往無法完全滿足我們的需求,因此發(fā)展出更加豐富特性類,其中最常見的...
    熠閑閱讀 911評論 0 0
  • 問:Java 常見的內部類有哪幾種,簡單說說其特征? 答:靜態(tài)內部類、成員內部類、方法內部類(局部內部類)、匿名內...
    Little丶Jerry閱讀 2,231評論 0 1
  • 當我和夜色融為一體的時候, 你會不會依然記得我? 這次還是用到了熒光筆噠! 如果沒有那個金色貴貴金粉的親們,依然可...
    小布點簡閱讀 553評論 13 15

友情鏈接更多精彩內容