Java中的Kotlin 内部类原理


Posted in Java/Android onJune 16, 2022

Java 中的内部类

这是一个 Java 内部类的简单实现:

public class OutterJava {
    private void printOut() {
        System.out.println("AAA");
    }
​
    class InnJava {
        public void printInn() {
            printOut();
        }
    }
}

外部类是一个私有方法,内部类为什么可以访问到外部类的私有方法呢?思考这个问题,首先要从它的字节码入手,看看 JVM 到底对 java 文件做了什么。

字节码分析流程是:

  • javac xxx.java生成 class 文件。
  • javap -c xxx.class对代码进行反汇编,可以生成可查看的代码内容。

通过 javac 命令生成 class 文件,此时会发现生成了两个 class 文件,一个外部类 OtterJava 的,一个内部类 InnJava 的。

OutterJava.class

OutterJava.class 反汇编后的代码如下所示,这里面除了一个构造方法,多生成了一个

Compiled from "OutterJava.java"
public class java.OutterJava {
  public java.OutterJava();
    Code:
       0: aload_0
       1: invokespecial #2                  // Method java/lang/Object."<init>":()V
       4: return
​
  private void printOut();
    Code:
       0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #4                  // String AAA
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
​
  static void access$000(java.OutterJava);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method printOut:()V
       4: return
}

从反编译出来的内容来看,多了一个静态的access$000(OutterJava)方法,它的内部调用了 printOut()

InnJava.class

Compiled from "OutterJava.java"
class java.OutterJava$InnJava {
  final java.OutterJava this$0;
​
  java.OutterJava$InnJava(java.OutterJava);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:Ljava/OutterJava;
       5: aload_0
       6: invokespecial #2                  // Method java/lang/Object."<init>":()V
       9: return
​
  public void printInn2();
    Code:
       0: aload_0
       1: getfield      #1                  // Field this$0:Ljava/OutterJava;
       4: invokestatic  #3                  // Method java/OutterJava.access$000:(Ljava/OutterJava;)V
       7: return
}

在 InnJava 的字节码反编译出来的内容中,主要有两个点需要注意:

  • 构造方法需要一个外部类参数,并把这个外部类实例保存到了this$0中。
  • 调用外部类私有方法,实际上是调用了OutterJava.access$000方法。

小结:

在 Java 中,内部类与外部类的关系是:

  • 内部类持有外部类的引用,作为内部构造参数传入外部类实例,并保存到了内部类的属性this$0中。
  • 内部类调用外部类的私有方法,实际上是外部类生成了内部实际调用私有方法的静态方法access$000,内部类可以通过这个静态方法访问到外部类中的私有方法。

Kotlin 中的内部类

同样的 Java 代码,用 Kotlin 实现:

class Outter {
    private fun printOut() {
        println("Out")
    }
​
    inner class Inner {
        fun printIn() {
            printOut()
        }
    }
}

这里如果不加inner关键字,printIn()内的printOut()会报错Unresolved reference: printOut 。

不加inner关键字,反编译后的字节码:

public final class java/Outter$Inner {
  // ...
  public <init>()V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Ljava/Outter$Inner; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
  // ...
}

不加inner关键字,内部类的构造方法是没有外部类实例参数的。如果加上inner,就和 Java 一样:

// 加上了 inner 的构造方法
  public <init>(Ljava/Outter;)V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD java/Outter$Inner.this$0 : Ljava/Outter;
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Ljava/Outter$Inner; L0 L1 0
    LOCALVARIABLE this$0 Ljava/Outter; L0 L1 1
    MAXSTACK = 2
    MAXLOCALS = 2

而内部类对于外部类私有方法的访问,也是通过静态方法access$XXX来实现的:

public final static synthetic access$printOut(Ljava/Outter;)V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/Outter.printOut ()V
    RETURN
   L1
    LOCALVARIABLE $this Ljava/Outter; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

总结

在 Kotlin 中,内部类持有外部类引用和通过静态方法访问外部类私有方法都是与 Java 一样的。唯一的不同是,Kotlin 中需要使用 inner关键字修饰内部类,才能访问外部类中的内容。实质是inner关键字会控制内部类的构造方法是否带有外部类实例参数。

到此这篇关于Java中的Kotlin 内部类原理的文章就介绍到这了,更多相关Java Kotlin 内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!


Tags in this post...

Java/Android 相关文章推荐
Springboot如何使用logback实现多环境配置?
Jun 16 Java/Android
启动Tomcat时出现大量乱码的解决方法
Jun 21 Java/Android
看完这篇文章获得一些java if优化技巧
Jul 15 Java/Android
Java Socket实现多人聊天系统
Jul 15 Java/Android
关于springboot 配置date字段返回时间戳的问题
Jul 25 Java/Android
简述Java中throw-throws异常抛出
Aug 07 Java/Android
使用logback实现按自己的需求打印日志到自定义的文件里
Aug 30 Java/Android
深入浅出讲解Java8函数式编程
Jan 18 Java/Android
Android基础入门之dataBinding的简单使用教程
Jun 21 Java/Android
Java 多线程并发FutureTask
Jun 28 Java/Android
Spring boot实现上传文件到本地服务器
Aug 14 Java/Android
Android移动应用开发指南之六种布局详解
Sep 23 Java/Android
Spring Security动态权限的实现方法详解
Java实现注册登录跳转
Jun 16 #Java/Android
Java界面编程实现界面跳转
springboot实现string转json json里面带数组
Jun 16 #Java/Android
Android Gradle 插件自定义Plugin实现注意事项
Jun 16 #Java/Android
Java完整实现记事本代码
Jun 16 #Java/Android
Springboot中如何自动转JSON输出
Jun 16 #Java/Android
You might like
基于php无限分类的深入理解
2013/06/02 PHP
输入值/表单提交参数过滤有效防止sql注入的方法
2013/12/25 PHP
php创建多级目录的方法
2015/03/24 PHP
PHP网站建设的流程与步骤分享
2015/09/25 PHP
PHP微信模板消息操作示例
2017/06/29 PHP
javascript 对象的定义方法
2007/01/10 Javascript
火狐4、谷歌12不支持Jquery Validator的解决方法分享
2011/06/20 Javascript
js如何实现设计模式中的模板方法
2013/07/23 Javascript
详解angular2采用自定义指令(Directive)方式加载jquery插件
2017/02/09 Javascript
vue拦截器Vue.http.interceptors.push使用详解
2017/04/22 Javascript
JavaScrpt中如何使用 cookie 设置查看与删除功能
2017/07/09 Javascript
说说如何使用Vuex进行状态管理(小结)
2019/04/14 Javascript
js实现登录拖拽窗口
2020/02/10 Javascript
公众号SVG动画交互实战代码
2020/05/31 Javascript
使用Vant完成DatetimePicker 日期的选择器操作
2020/11/12 Javascript
[59:48]LGD vs IG 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
python实现的二叉树算法和kmp算法实例
2014/04/25 Python
使用python实现rsa算法代码
2016/02/17 Python
PHP网页抓取之抓取百度贴吧邮箱数据代码分享
2016/04/13 Python
Python创建对称矩阵的方法示例【基于numpy模块】
2017/10/12 Python
Python编程之黑板上排列组合,你舍得解开吗
2017/10/30 Python
对python抓取需要登录网站数据的方法详解
2018/05/21 Python
linux环境下Django的安装配置详解
2019/07/22 Python
python中@contextmanager实例用法
2021/02/07 Python
python实现MySQL指定表增量同步数据到clickhouse的脚本
2021/02/26 Python
HTML5上传文件显示进度的实现代码
2012/08/30 HTML / CSS
正宗的日本零食和糖果订阅盒:Bokksu
2019/11/21 全球购物
省级四好少年事迹材料
2014/01/25 职场文书
2014年公司庆元旦活动方案
2014/03/05 职场文书
预备党员的自我评价
2014/03/12 职场文书
老师对学生的寄语
2014/04/09 职场文书
工作作风承诺书
2014/08/30 职场文书
2014年初三班主任工作总结
2014/12/05 职场文书
现实表现证明材料
2015/06/19 职场文书
个人工作失误的保证书怎么写?
2019/06/21 职场文书
公司员工违法违章行为检讨书
2019/06/24 职场文书