专栏首页Java知其所以然从字节码深刻理解内部类

从字节码深刻理解内部类

嵌套类有四种:

  • 静态成员类
  • 非静态成员类
  • 匿名类
  • 局部类

除了第一种之外,其他三种都被称为内部类。

匿名类的缺陷

  1. 除了在他们声明的时候之外,是无法将他们实例化的。
  2. 不能执行 instanceof 测试,或者做任何需要命名类的其他事情。
  3. 无法实现多个接口,或者扩展一个类。
  4. 匿名类的客户端除了从它的超类型中继承得到之外,无法调用任何成员。

匿名类的常用用法

  1. 可以出现在表达式当中,但是必须保持简短。(大约 10 行或者更少,否则会影响程序的可读性)
  2. 创建过程对象,比如 Runnable,Thread。
  3. 静态工厂的内部

静态内部类

在类中再定义类,并且这个类用 static 修饰

使用静态内部类实现的单例

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

    private Singleton(){
    
    }

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

用法

1、作为共有的辅助类,仅当与它的外部类一起使用时才有意义。

2、代表外部类所代表的对象的组件。

例如,Map 中的 Entry,每个 entry 都与一个 Map 关联,但是 entry 上的方法(getValue 和 setValue)并不需要访问该 Map,所以就不需要去引入不必要的耦合。用静态内部类来表示 entry,可以节省空间和时间。(如果 entry 使用内部类,每个 entry 中将会包含一个指向该 Map 的引用,这样就浪费了空间和时间)

思考

内部类是如何获取到外部类的属性?

静态内部类没有调用外部类属性时

静态内部类调用外部类属性时

public static access$000(Demo demo)

0 aload_0
1 getfield #2 <com/ypf/jun/Demo.age>
4 ireturn

上面的字节码指令等价于

return demo.age;

public static access$100()

0 getstatic #1 <com/ypf/jun/Demo.name>
3 areturn

等价于

return Demo.name;

可以看出来在 javac 编译后,java 编译器会为你自动生成访问外部类的 static 的 accessXXX() 方法。

对于内部类访问外部类的属性:

  • 静态变量
  • 实例变量

不同在于:访问实例变量的时候需要通过外部类的对象来进行访问

那怎么去调用呢

看下面这个静态内部类的常量池,它持有外部类 Demo 的 Class 引用。

然后在静态内部类中通过外部类的类名调用即可 accessXXX() 方法即可获取外部类的属性。

静态内部类好处

  1. 延迟加载,当真正调用静态内部类时才会去加载。
  2. 可以访问外部类的私有变量。
  3. 可以作为辅助类,但其实可以把它当成一个普通的类来看。
  4. 防止内存泄漏

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。 (百度百科)

非静态内部类

下面是一个非静态内部类的截图

在构造普通内部类的对象时,默认先 new 出一个外部类的对象,然后在调用内部类构造器时通过参数传入普通内部类,这样的话普通内部类就可以通过外部类的对象来访问外部类的成员变量。

现在我们考虑一下我图中的那个 ?

为什么要去调用一个 Object 的 getClass 方法呢?

其实就是为了做一个空指针检查,如果外部类的对象引用为 null,就会抛出NullPointerException 异常。

在Java语言规范第三版 15.9.4 Run-time Evaluation of Class Instance Creation Expressions 该小节规定了上面提到的空指针检查的行为 getClass() 方法是在 Object 上声明的(因此所有对象上必然存在),而且是 final 的(保证了它有确定的行为),而且运行开销比较低。 同样是 Object 上声明的方法,toString()、hashCode() 之类其实也可以用,但它们都不是 final 的,有潜在可能性会引发较大的运行开销;这么分析一圈下来,Object 上最好用的就剩下 getClass() 了。 (原文链接 http://rednaxelafx.iteye.com/blog/1089554)

匿名内部类和普通内部类的处理方式是一样的,也就是把外部类的对象传入到内部类中,而且对于外部类创建的变量,都会自动生成一个 access 访问方法供内部类进行访问。

非静态内部类常见用法

定义一个视图。

例如,Map 接口的实现往往使用非静态内部类来实现它们的集合视图。Set 和 List 这种集合接口的实现往往也使用非静态内部类来实现他们的迭代器。(这里由于视图需要依赖外部类,因此选择非静态内部类更方便一些)

静态内部类和非静态内部类的区别

  1. 静态内部类的声明中包含修饰符 static。
  2. 非静态内部类的每个实例都隐含着与外部类的一个外部实例相关联。在非静态内部类的实例方法内,可以调用外部实例上的方法,或者利用修饰过的 this 构造获得外部实例的引用。

非静态内部类、匿名内部类引起的内存泄漏

非静态内部类、匿名内部类创建对象后内部会持有外部类对象的引用,因此外部类对象的生命周期就和它们绑定在了一起。当内部类的对象引用被一直持有时,外部类的对象将不能被垃圾机制回收,从而导致了内存泄漏。

如何正确使用嵌套类呢

如果声明一个嵌套类不要求访问外部类实例,就要使用静态内部类。否则每个实例将包含一个额外的指向外部类对象的引用,保存这份引用要消耗时间和空间,并且会导致外部类对象在符合垃圾回收时仍然得以保留。

参考资料 :<Effective Java 中文版 第二版>

本文分享自微信公众号 - Java知其所以然(gh_37a1335e2608),作者:帅飞

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-10-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 内部类

    一般情况,我们把类定义成独立的单元。有些情况下,我们把一个类放在另一个类的内部定义,称为内部类。

    帅飞
  • JDK1.6 对 synchronized 的锁优化

    在看下面的内容之间,希望大家对 Mark Word 有个大体的理解。Java 中一个对象在堆中的内存结构是这样的:

    帅飞
  • Java 程序执行过程的内存分析

    注:常量池具有共享的机制,不同类的常量池之间共享数据。比如:Student 类常量池下有个 "老叶"字符串常量,那么当 Test 类中用到"老叶"这个字符串常量...

    帅飞
  • 内部类

    一般情况,我们把类定义成独立的单元。有些情况下,我们把一个类放在另一个类的内部定义,称为内部类。

    帅飞
  • 静态内部类

    定义:静态内部类,定义在类中,任何方法外,用static定义;静态内部类只能访问外部类的静态成员。 注意点: 一般情况下,如果一个内部类不是被定义成静态内部类,...

    lwen
  • Java中,成员内部类的常见修饰符及应用 && 成员内部类不是静态的,访问的格式

    成员内部类的常见修饰符及应用:   private    为了保证数据的安全性   static      为了方便访问数据   注意:静态的内...

    黑泽君
  • JAVA程序第三期

    周末让我们动起来!超越他人的好时光怎么能这样虚度!小编继续刷起来,啦啦啦。今天又为大家带来两个例子,let's go!

    聚沙成塔
  • Java内部类详解

    内部类(nested classes),面向对象程序设计中,可以在一个类的内部定义另一个类。嵌套类分为两种,即静态嵌套类和非静态嵌套类。

    用户5224393
  • 【小家java】静态类、静态方法、内部类、匿名内部类、非静态类的一些实践

    静态内部类的作用:只是为了降低包的深度,方便类的使用,实现高内聚。静态内部类适用于不依赖于外部类,不用使用外在类的非静态属性和方法,只是为了方便管理类结构而定义...

    YourBatman
  • Java:关于Static静态关键字的那些小事

    Carson.Ho

扫码关注云+社区

领取腾讯云代金券