Java 传递机制:值传递还是引用传递?(详细图文版)

前言

明白Java中的传递机制,有助于帮助我们更好的理解Java在内存中的地址指向。这篇文章我会通过尽可能简单的讲解,让大家更好的理解。


1. 传递类型

首先,我们需要知道,传递类型指的是调用函数时传入的参数传递的方式。传递类型包括两种:值传递引用传递

值传递:我们传入的实参拷贝一份传给函数的形参,在函数中操作形参不改变实参的值

引用传递:将实参的引用地址传递给方法

对于C++这类的语言来说,既存在值传递又存在引用传递
值传递实现方式:int func(int a, int b);
而引用传递的实现通过指针引用来实现,例如:int func(int *a, int &b);

而对于Java来说,只存在值传递,不存在引用传递


2. Java中的值传递机制

为了让大家更好的理解,接下来会通过大量的画图来进行展示。在这里插入图片描述
这里我从网上找了一张JVM内存结构图,可以看到Java程序在内存中是分为各个区域的。这里要讲的Java值传递机制为了帮助大家理解,主要针对栈区和堆区这两块最常见的区域进行讲解,而忽略里面的细节部分。

首先,我们需要知道,Java中的变量类型分为两种:基本类型和引用类型。接下来我通过几个例子来帮助大家更好的理解Java值传递机制。

第一个例子

public class Main01 {
	
	public static void main(String[] args) {
		int a = 10, b = 20;
		exchange(a, b);
		System.out.println("a = " + a + ", b = " + b);
	}
	
	public static void exchange(int a, int b) {
		int tmp = a;
		a = b;
		b = tmp;
	}
}

首先看一个基本类型的例子,看看这段代码,大家猜一下结果是什么?

a = 10, b = 20

应该不难理解, 因为Java采用值传递机制,传入的是实参的一份拷贝,改变函数中形参的值不会影响原来的值,接下来通过内存来进行理解。

Java中的栈区由一个个的栈帧构成,一个栈帧对应一个方法,而方法的开始和结束对应入栈和出栈过程。程序执行时首先将由主方法main()构成的栈帧压栈,执行主方法过程中遇到方法的执行则对应压栈,遇到方法的结束对应出栈,当主方法执行结束时则程序结束。

第一个例子

上面这段代码通过画图就是这个样子,先开辟主方法main()对应的栈帧。然后压入exchange()方法对应的栈帧,exchange()方法结束后出栈,最后主方法main()结束意味着程序的结束。可以看到,在exchange()方法中改变形参a’和b’并没有影响主方法main()中实参a、b的值。

我们接着往下看,再分析一段关于引用类型的代码

第二个例子

public class Main01 {
	
	public static void main(String[] args) {
		Student s1 = new Student("张三");
		Student s2 = new Student("李四");
		exchange(s1, s2);
		System.out.println(s1);
		System.out.println(s2);
	}
	
	public static void exchange(Student s1, Student s2) {
		Student tmp = s1;
		s1 = s2;
		s2 = tmp;
	}
}

class Student {
	
	public Student(String name) {
		this.name = name;
	}
	
	public String name;
	
	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}
	
}

这次的结果又是什么?

Student [name=张三]
Student [name=李四]

可以看到,s1和s2的name值并没有发生改变

这是为什么呢?引用类型传入的是地址,在exchange()方法中交换其指向,为什么主方法main()中s1和s2的指向为什么没有相互交换呢?

看完这个例子,如果你针对这个疑问已经有了答案的话,那太好了,你对Java值传递一定有了一定的理解?如果还存在疑惑,那就跟着我继续往下分析吧!

分析时,我们需要带着一个问题,值传递对于基本类型和引用类型有什么区别吗?

我们同样通过内存图来进行分析

第二个例子
你只要记住一点,Java值传递传递的实参的一份拷贝,在这个例子中,exchange()函数中携带的同样是s1和s2的一份拷贝,只不过这一次,两者指向的是堆区的同一块内存空间。

在执行exchange()函数时,交换的是形参s1’和s2’的内存地址指向,对于实参s1和s2来说,其指向还是没有发生改变。

看完前面两个例子,你似乎会觉得无论是基本类型还是引用类型都是值传递,传递是一份拷贝,都无法真正改变原来的值?

但是,真的是这样吗?

接下来我们看最后一个关于引用类型的例子,或许它能帮助你真正理解Java的值传递

第三个例子

public class Main01 {
	
	public static void main(String[] args) {
		Student s1 = new Student();
		Student s2 = new Student();
		s1.name = "张三";
		s2.name = "李四";
		exchange(s1, s2);
		System.out.println(s1);
		System.out.println(s2);
	}
	
	public static void exchange(Student s1, Student s2) {
		Student tmp = new Student();
		tmp.name = "张三";
		s1.name = s2.name;
		s2.name = tmp.name;
	}
}

class Student {
	
	public String name;
	
	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}
	
}

仔细分析一下这段代码,你的结果是什么?

是否认为s1.name还是"张三",s2.name还是"李四"?

我们看一下结果

Student [name=李四]
Student [name=张三]

诶?怎么跟你想的不一样?不是说引用类型传递,即使传的是地址的拷贝,改变形参地址的指向无法改变实参的指向吗?

对!的确是这样的,值传递,改变形参的地址指向确实影响不了实参的地址指向,但是第三个例子中改变的是其指向地址空间的值

哈哈,是不是被我说懵了

让我们接着画内存图进行分析

执行exchange()前:
在这里插入图片描述
此时只有主方法main()对应的栈帧

紧接着执行exchange()方法,exchange()对应的栈帧入栈

首先,创建了一个新的Student对象tmp,令其name = “张三”

然后,交换了形参s1’和s2’指向堆区内存空间的对象的name值

于是就变成了下面这个样子

在这里插入图片描述
看到这里,你理解了吗?注意第三个例子和第二个例子的区别。

第二个例子是改变了形参s1’和s2’的指向,并没有改变实参s1和s2的指向,所以实参的指向没变

第三个例子确实也没有改变实参s1和s2的指向,其在堆区指向的内存空间仍没有发生改变,但是我们是通过改变其对应堆区对象的name值,所以输出就发生了改变。本质上改变形参的值还是没有改变形参的指向,这也就是值传递。

通过以上三个例子,你是否深刻了解Java值传递机制?

ps. 本人大二狗一条,能力有限,大佬轻喷,如果有哪块没看懂欢迎评论区留言交流!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值