关于java的一些深入分析

从代码执行开始:

首先提出一个问题———为什么main函数是static?

代码执行之前会先加载类

首先程序执行需要有一个源头,但是要知道实例方法需要用实例对象才能调用,因此把main设置为static,因为static方法在类加载时分配内存。当JVM执行Java程序时,它会将main方法的调用放入调用栈。这表示main方法作为第一个要执行的方法,会被放在调用栈的顶部。

一般接下来会创建类的实例对象:

在Java中,创建对象的过程通常涉及以下步骤,这些步骤的执行顺序如下:

  1. 类加载: 当你使用关键字 new 创建一个对象时,首先要确保相应的类已经被加载到JVM中。如果该类还没有加载,JVM会加载它。在这一步,类信息和静态变量信息将被加载到方法区(Java 8之前称为永久代,Java 8及之后称为元空间)中。

  2. 分配内存: 一旦类加载完成,JVM会在堆内存中分配一块内存空间,用于存储对象的实例变量。这个过程包括为对象的所有实例变量分配内存空间,并将它们初始化为默认值。

  3. 对象初始化: 一旦内存分配完成,JVM会调用对象的构造函数来初始化对象的实例变量。构造函数中的代码会设置对象的初始状态。

  4. 创建引用: 在栈内存中,会为对象创建一个引用变量(也就是一个指向堆内存中对象的指针)。这个引用变量可以被用来访问和操作对象。

所以,总的来说,先加载类信息和静态变量到方法区/元空间,然后分配堆内存并初始化对象的实例变量,最后在栈内存中创建一个引用变量。这个引用变量可以用于访问和操作对象的实例变量和方法。这些步骤构成了对象的创建和初始化过程。

方法的调用:

那么问题来了,为什么方法没有实例对象去调用也能直接执行呢?(同一个类中的方法为什么可以不创建实例对象而可以直接调用?)

main方法内部调用实例方法时,JVM确实会隐式地创建一个实例对象,并将其传递给实例方法。这个实例对象是隐含的,不需要您显式创建,而且它通常称为隐含的this对象。

在Java中,同一个类中的方法可以相互之间调用而不需要创建实例对象的主要原因是这些方法共享相同的实例上下文(对象上下文)。在同一个类中的所有方法都可以访问相同的成员变量和实例方法,因为它们都属于同一个实例。这是因为在同一个类的方法内部,Java将隐式传递当前对象的引用(即 this 引用)。这个 this 引用指向当前实例,因此在同一个类的方法中,可以使用 this 来访问当前对象的成员变量和实例方法。

当执行到某方法是会调用放入调用栈。等到方法结束会重新释放空间。

____________________________________________________________

____________________________________________________________

____________________________________________________________

最近一个粉丝加我说,接到了阿里的面试,问问我阿里会面试哪方面的东西。我说,阿里的套路基本上是,你会什么就问你什么,直到问得你怀疑人生。正所谓,你知道的越多,不知道的就越多,业余的像一颗小草!

今天,我们就一起来讨论一个这个网友遇到的阿里面试题,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("业余草");
 
        BiFunction<Xttblog,String,String> function = 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 <init>()V
   L0
    LINENUMBER 9 L0
    ALOAD 0
    INVOKESPECIAL com/xttblog/Xttblog.<init> ()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 指令。

user:编译器在调用某个实例方法时,实际上会把当前的实例对象的引用作为第一个参数传递给方法。这个说法对吗?那么对于F a = new E();a.sum();是a作为第一个参数传递给方法吗?

chatgpt:

不完全正确。在Java中,实例方法确实可以访问当前对象的实例,但并不是通过将当前对象的引用作为参数传递给方法来实现的。

当你调用实例方法时,编译器会在底层使用当前对象的引用来执行方法。这个引用在方法内部是通过 `this` 关键字来表示的,而不是作为参数传递给方法。在方法内部,可以使用 `this` 来访问当前对象的实例变量和调用其他实例方法。

所以,在你的代码中,当你调用 `a.sum()` 时,`a` 引用的对象(`E` 类的实例)会成为方法 `sum` 内部的 `this`,而不是将 `a` 作为参数传递给方法。在 `sum` 方法内部,你可以使用 `this` 来访问 `E` 类的实例变量和调用 `E` 类的实例方法。

 

代码举例:

package firstjava;

public class Second_02 {	
		public static void main(String[] args) {
			F_01 a = new E_01(); 
			F_01 a1 = new E_01(); 
            System.out.println(a.i);//输出父类i值
			System.out.println(a.sum());
			System.out.println(a.sum1());//?30-> 20
		}

	}


		class F_01 {
		    public int i = 10;

		    public int sum() {
		        return getI() + 10; 
		    }

		    public int sum1() {
		        return i + 10; 
		    }

		    public int getI() {
		        return i; 
		    }
		}
		
		
		
		

		class E_01 extends F_01{
		    public int i = 20;
//		    public int sum1() {
//		    	return i + 10;
//		    }
//		    
		    
//		  public int sum() {
//		  return i + 20;
//		  }
		    @Override
		    public int getI() {
		        return i; 
		    }
	}

在这个程序中,父类为F,子类为E。当a.sum();时候会把a所引用的实例对象,也就是在堆中那块内存,会变成sum方法中内部的this,当执行 return getI() + 10; 代码时,即相当于this.getI()+10;即会调用子类中的getI()方法(实例对象直接调用方法,而不是用引用去调用),同时this 引用的对象(`E` 类的实例)会成为getI()方法 内部的 `this`;执行 return i;即执行this.i;此时将会输出子类的i值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值