前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从字节码深刻理解内部类

从字节码深刻理解内部类

作者头像
帅飞
发布2019-01-22 17:23:07
8950
发布2019-01-22 17:23:07
举报
文章被收录于专栏:Java知其所以然

嵌套类有四种:

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

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

匿名类的缺陷

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

匿名类的常用用法

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

静态内部类

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

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

代码语言:javascript
复制
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)

代码语言:javascript
复制
0 aload_0
1 getfield #2 <com/ypf/jun/Demo.age>
4 ireturn

上面的字节码指令等价于

return demo.age;

public static access$100()

代码语言:javascript
复制
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 中文版 第二版>

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-10-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java知其所以然 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 匿名类的缺陷
  • 匿名类的常用用法
  • 静态内部类
  • 用法
  • 思考
    • 内部类是如何获取到外部类的属性?
    • 静态内部类好处
    • 非静态内部类
    • 非静态内部类常见用法
    • 静态内部类和非静态内部类的区别
    • 非静态内部类、匿名内部类引起的内存泄漏
    • 如何正确使用嵌套类呢
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档