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("喵喵~~");
    }
}

执行结果

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方法)。如果内部类出现和外部类相同签名的方法或者同名同类型的成员变量,那么内部类的相关数据的访问会覆盖外部类的相关数据。实际上这个过程有点像继承,并且内部类还可以继承自其他类,这就弥补了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. 内部类可能存在的隐患
    通过上面分析,我们知道了,内部类持有了一个外部类的引用,因此容易引发内存泄漏。我们举个例子
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退出,内存也将无法回收。因为静态变量dog持有了Activity的一个引用,除非手动将dog置null,堆上的dog对象才会被回收,进而Activity被回收。

二、静态内部类

  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 大哥 老虎

我们看到,外部类对静态内部类而言,更多的只像一个命名空间

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
}

静态内部类不持有外部类的引用。静态内部类可以访问外部类的一切静态变量和方法。

三、匿名内部类

  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。所以一般说来,匿名内部类用于继承其他类或者实现接口,并不需要增加额外的方法,只是重写父类方法或者实现接口。这里需要注意的地方是,匿名内部类也持有了外部类的一个引用,因此也是存在内存泄漏的安全隐患的。比如常见的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);
        // 会导致内存泄露,非静态内部类(包括匿名内部类,比如这个 Handler 匿名内部类)会引用外部类对象 this(比如 Activity)
        // 当它使用了 postDelayed 的时候,如果 Activity 已经 finish 了,而这个 handler 仍然引用着这个 Activity 就会致使内存泄漏
        // 因为这个 handler 会在一段时间内继续被 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对象又被当前线程持有,从而引发内存泄漏。解决的办法之一是

   @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对象可能已经被回收了。如果不使用静态内部了,也不调用

handler.removeCallbacksAndMessages(null);

那么在消息回调之前TestActivity对象是绝对不会被回收的。

四、局部内部类

局部内部类比较少用,我就不说了。

五、总结

关于内部类,我们尽可能使用静态内部类,因为这样可以减少不必要的麻烦。如果静态内部类想访问外部类的数据,我们可以通过持有外部类的一个弱引用对象来达到目的。如下:

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();

    }
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 转载:https://juejin.im/post/5a903ef96fb9a063435ef0c8 本文将会从以...
    福later阅读 3,177评论 0 3
  • 搞懂 JAVA 内部类 前些天写了一篇关于 2018 年奋斗计划的文章,其实做 Android 开发也有一段时间了...
    醒着的码者阅读 3,749评论 0 0
  • 在Java种类的使用是十分基础的,但是常规类往往无法完全满足我们的需求,因此发展出更加丰富特性类,其中最常见的...
    熠闲阅读 4,304评论 0 0
  • 问:Java 常见的内部类有哪几种,简单说说其特征? 答:静态内部类、成员内部类、方法内部类(局部内部类)、匿名内...
    Little丶Jerry阅读 6,435评论 0 1
  • 当我和夜色融为一体的时候, 你会不会依然记得我? 这次还是用到了荧光笔哒! 如果没有那个金色贵贵金粉的亲们,依然可...
    小布点简阅读 3,449评论 13 15

友情链接更多精彩内容