JAVA 内部类的总结

前言

随着工作经验的增加,越发感觉到基础的重要性,所以最近上下班地铁上都在看一些基础的东西,前几天看了拭心大哥讲的内部类,由于自己对概念的理解有误,造成了一些困惑,自己查了一上午才弄明白(正好早上开无聊的周例会,我就在那自己查资料,还拉着大学同学一起讨论),所以决定抽空把这部分再梳理一下,做个分享,也可以加深自己对这部分的理解。

正文

定义在一个类中或者方法中的类称为内部类。

内部类可以分为以下四类:

  1. 成员内部类
  2. 局部内部类
  3. 静态内部类
  4. 匿名内部类

我们来逐个分解:

  • 成员内部类
    最普通的内部类,它定义在一个类的内部中,就如同一个成员变量一样。
    代码形式如下:
public class OuterClass {

    class InnerClass {
        private void method() {
            System.out.println("innerMethod");
        }
    }
}

调用方式:

    OuterClass out = new OuterClass();
    OuterClass.InnerClass in = out.new InnerClass();
    in.method();

注意:不是直接 new outClass.InnerClass()
成员内部类可以无条件的访问外部类的成员属性和成员方法(包括 private 和 static 类型的成员),这是因为在成员内部类中,隐式地持有了外部类的引用。

使用场景:
当类 A 需要使用类 B ,同时 B 需要访问 A 的成员/方法时,可以将 B 作为 A 的成员内部类。同时我们可以利用 private 内部类禁止其他类访问该内部类,从而做到将具体的实现细节完全隐藏。

  • 局部内部类
    在代码块或者方法中创建的类。局部内部类的作用域只能在其所在的代码块或者方法内,在其它地方无法创建该类的对象。可以把局部内部类理解为作用域很小的成员内部类。
    代码形式如下:
    public void test() {
        class PartInnerClass {
            private void method() {
                System.out.println("partInnerMethod");
            }
        }
        new PartInnerClass().method();
    }

使用场景:
局部内部类只用于当前方法或者代码块中创建、使用,属于一次性产品,使用场景比较少。

  • 静态内部类
    使用 static 关键字修饰的内部类就是静态内部类,静态内部类不持有外部类的引用,可以看作是和外部类平级的类。
    代码形式如下:
public class OuterClass {

    static class StaticInnerClass {
        private void method() {
            System.out.println("staticInnerMethod");
        }
    }
}

我们想在静态内部类中访问外部类的成员只能 new 一个外部类的对象,否则只能访问外部类的静态属性和静态方法。

使用场景:
1.当类 A 需要使用类 B,而 B 不需要直接访问外部类 A 的成员变量和方法时,可以将 B 作为 A 的静态内部类。

2.单例的实现:

public class Singleton {

    private Singleton() {}

    private static class InnerClass {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return InnerClass.INSTANCE;
    }
}

由于InnerClass是一个内部类,只在外部类Singleton的getInstance()中被使用,所以它被加载的时机也就是在getInstance()方法第一次被调用的时候。初始化的时候由ClassLoader来保证同步,确保 INSTANCE 变量只能初始化一次,使INSTANCE成为一个真·单例。

  • 匿名内部类
    匿名内部类需要重点说一下,之前就是因为对它理解不到位,才造成了困惑。
    概括一下匿名内部类的特点:
    1.必须继承一个父类或实现一个接口,并不需要增加额外的方法,只是对继承方法的实现或是覆盖。
    2.只是为了获得一个对象实例,并不需要知道其实际的类型。
    3.匿名内部类的匿名指的是没有类名,而不是没有引用指向它。
    4.匿名内部类不能有构造方法,只能有初始化代码块。因为编译器会帮我们生成一个构造方法然后调用。
    5.匿名内部类中使用到的参数是需要声明为 final 的,否则编译器会报错。
    我之前理解出现偏差就是在第3条,好吧,其实第3条是我自己补充的,为了避免大家出现跟我一样的错误(之前和几个同学讨论的时候,大家对这一点的理解都很模糊)。
    代码形式如下:
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            
        }
    });

我们常用的按钮点击监听用到的就是匿名内部类。
上面这个例子很好理解,我们再来看看下面这个:

    @SuppressLint("HandlerLeak")
    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            ...
        }
    };

没错,这个new出来的 Handler 也是一个匿名内部类,我之前看到这个的时候懵逼了,判断不出它究竟是哪种内部类。所以记住,匿名内部类的匿名指的是没有类名,而不是没有引用指向它。你可别告诉我它的类型就是Handler啊,它只是Handler的一个子类,一个没有类名的子类。
顺便再说一下,这样的写法可能会造成内存泄露,因为这个匿名内部类会隐式地持有外部类的引用。举个例子:

    Message message = Message.obtain();
    message.what = 233;
    mHandler.sendMessageDelayed(message, 1000 * 60 * 10);
    finish();

我们发送了一个延迟10分钟的Message,在Message发送出去之前,我们结束了当前Activity。Message在消息队列中延迟了10分钟,然后才处理该消息。而这个Message引用了Handler,Handler又隐性地持有了Activity的引用,当发生GC时因为 Message – Handler – Acitivity 的引用链导致Activity无法被回收,所以发生了内存泄露。
解决办法就是使用弱引用WeakReference或者干脆将 Handler 设计为静态内部类

那么为什么匿名内部类中使用参数需要声明为final呢?
因为匿名内部类是创建一个对象并返回,这个对象的方法被调用的时机不确定,方法中有修改参数的可能,如果在匿名内部类中修改了参数,外部类中的参数是否需要同步修改呢?Java 为了避免这种问题,限制匿名内部类访问的变量需要使用 final 修饰,这样可以保证访问的变量不可变。

使用场景:
需要实现一个接口,但不需要持有它的引用。

补充

除了静态内部类,剩下的成员内部类、局部内部类、匿名内部类 都会默认隐式地持有外部类的引用。

再来两道典型题目融会贯通一下:

在?处填入合适的代码,使程序在控制台输出30,20,10。

public class OuterClass {

    private int num = 10;

    class InnerClass {
        int num = 20;

        void show() {
            int num = 30;
            System.out.println(?);
            System.out.println(?);
            System.out.println(?);
        }
    }

    public static void main(String[] args) {
        InnerClass in = new OuterClass().new InnerClass();
        in.show();
    }
}

.
.
.
.
.
答案:
1.num
2.this.num
3.OuterClass.this.num

补全代码 ,要求在控制台输出"HelloWorld"

interface InterfaceTest {
    void show();
}

public class TestClass {

    //补全代码

    public static void main(String[] args) {
        TestClass.method().show();
    }
}

.
.
.
.
.
答案:

interface InterfaceTest {
    void show();
}

public class TestClass {

    //补全代码
    public static InterfaceTest method() {
        return new InterfaceTest() {
            @Override
            public void show() {
                System.out.println("HelloWorld");
            }
        };
    }

    public static void main(String[] args) {
        TestClass.method().show();
    }
}

结语

学海无涯,我们不仅要向前迈进,还得时常回过头去,看看来时的路。

文中多有借鉴拭心大哥的文章,附上链接:
http://blog.csdn.net/d29h1jqy3akvx

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Java 内部类 分四种:成员内部类、局部内部类、静态内部类和匿名内部类。 1、成员内部类: 即作为外部类的一个成...
    ikaroskun阅读 5,020评论 0 13
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,954评论 18 399
  • 问:Java 常见的内部类有哪几种,简单说说其特征? 答:静态内部类、成员内部类、方法内部类(局部内部类)、匿名内...
    Little丶Jerry阅读 6,276评论 0 1
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 7,593评论 0 11
  • 连日绵绵阴雨洗,温阳初晴,苍寥云空里。 紫色杜鹃瑶笛起,层林绿染春之力。 先祖遥思应孝礼,碎步而行,峭径攀崖去。 ...
    竹影灯阅读 1,521评论 3 2