最近一个粉丝加我说,接到了阿里的面试,问问我阿里会面试哪方面的东西。我说,阿里的套路基本上是,你会什么就问你什么,直到问得你怀疑人生。
今天,我们就一起来讨论一个这个网友遇到的阿里面试题,Java 中 this 和 super 关键字的底层实现原理!
众所周知,在 Java 中,this 具有以下含义。
在 Java 中,每当一个对象创建后,Java 虚拟机都会给这个对象分配一个指向自身的引用,也就是 this。同时如果对象是子类对象,则还会有一个 super 引用指向当前对象的父类对象。
在类的方法定义中使用 this 关键字,表示使用该方法的对象的引用。
在一个类中,this 可以表示该类的当前实例。
this 是对当前类对象的引用,对象只有被实例化才存在。
根据 this 的这些含义,面试官就想知道 this 是怎么出来的?先看下面的代码。
public class Xttblog { private String name; public void setName(String name){ this.name = name; } public static void main(String[] args) { Xttblog xttblog = new Xttblog(); xttblog.setName("业余草"); } }
在 setName 方法中,可以使用 this 的本质是:编译器在调用某个实例方法时,实际上会把当前的实例对象的引用作为第一个参数传递给方法。
经过编译器的处理,xttblog.setName(“业余草”) 这行代码实际上变成 xttblog.setName(xttblog,”业余草”)。而如果想从方法内部获取当前对象的引用,就有一个专门的关键字 this,表示“调用方法的那个对象”的引用(如 xttblog)。
这一点,我能想到有 3 种方法来验证它。
第一种是我们通过 Bytecode 工具查看字节码的时候,会发现有 setName 方法有两个 LOCALVARIABLE,即两个局部变量,一个是 String 的,一个是 Xttblog 对象。
OCALVARIABLE args [Ljava/lang/String; L0 L3 0 LOCALVARIABLE xttblog Lcom/xttblog/Xttblog; L1 L3 1
第二种就更简单了,我们给 setName 方法在第一位再加一个 Xttblog this 参数,你会发现编译器不会报错,方法调用都能够成功。
public class Xttblog { private String name; public void setName(Xttblog this,String name){ this.name = name; } public static void main(String[] args) { Xttblog xttblog = new Xttblog(); xttblog.setName("业余草"); } }
第三种验证方法,我们借助 Java8 提供的函数接口来验证。比如下面的代码。
public class Xttblog { private String name = "业余草"; private String sayStr; public void setName(String name){ this.name = name; } private String say(String sayStr){ this.sayStr = sayStr; return this.name + " say : " + this.sayStr; } public static void main(String[] args) { Xttblog xttblog = new Xttblog(); xttblog.setName("业余草"); BiFunctionfunction = Xttblog::say; String str = function.apply(xttblog, "hello~"); System.out.println(str); } }
以 BiFunction 为例,它要求的是以两个输入参数,一个输出参数。而我们调用的 say 只需要一个字符串类型的输入参数,但是我们的示例代码中,却传递了两个参数。一个是 Xttblog 类型,一个是 String 类型,编译器没有报错,可以正常执行。
这也验证了 this 的底层实现原理。
同理,super 的本质是:当我们 new 一个子类对象的时候,子类除了生成一个 this 引用指向自身,还会生成一个指向其直接父类对象的引用 super。如果子类在继承父类的非私有的法和成员变量时,没有同名的方法和变量,可以直接通过名称使用父类的方法和变量。如果子类存在与父类同名的方法和成员变量时,要想区别访问父类和自身的变量和方法,需要使用 super 关键字调用父类的方法和属性。
这些在字节码中都有体现。以下面的代码为例:
public class Codedq extends Xttblog{ private String skill; public void setSkill(String skill){ this.skill = skill; System.out.println(super.say("hello world")); System.out.println(super.getName() + "会:" + this.skill); } }
我们在字节码中会看到,在 Codedq 的构造函数中,调用了父类 init 方法。
public()V L0 LINENUMBER 9 L0 ALOAD 0 INVOKESPECIAL com/xttblog/Xttblog. ()V RETURN L1 LOCALVARIABLE this Lcom/xttblog/Codedq; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1
注意上面的 INVOKESPECIAL 指令。super 关键字的底层原理就是靠 INVOKESPECIAL 指令。
在 Codedq 的代码中,super 就是靠 INVOKESPECIAL 指令来调用父类方法的。
invokespecial 指令的主要作用是,用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。(Invoke instance method; special handling for superclass, private, and instance initialization method invocations )。
最后,我们来个简单的总结,this 的底层实现是默认传值,super 是靠 INVOKESPECIAL 指令
。
我是一名程序员,java开发工程师,在java中呢,有很多java概念的知识点,我都有总结和案例,喜欢java,想学习java的朋友可以来和我交流学习,不管你是零基础还是想转行,还是学生,都可以。在这里为了大家在一起讨论交流,我建立一个我的java编程技术扣扣交流峮,有很多志同道合的伙伴在里面,有需要的可以加:前108中062后1881 。希望你是带着真诚的学习的心来的。