Java基础之Java到底是值传递还是引用传递

Java 中有一个很经典但是一直争论不休的问题。就是 Java 在传参时到底是传递值还是传递引用。

在讨论到底是值传递还是引用传递之前,先来准确定义一下引用

是指变量存储的就是值本身,比如 Java 的基本类型。 引用是指变量存储的是指向实际值的引用,也称之为句柄,比如 Java 中的数组和对象,来看下图:

要想完整的理解这个点,需要稍微提一下 JVM 的知识。我们都知道 Java 是运行在 Java 虚拟机(JVM)上的。JVM 中有两块著名的内存区域:Java虚拟机栈。值类型的变量所占据的内存是在栈上分配,引用类型的变量所占据的内存是在堆上分配。

这块展开说就会涉及到 JVM 的内存模型,后续通过其他的文章再进行说明。

赋值操作

先看一下这段代码:

1
2
3
4
5
int a = 56;
int b = a;
b = 65;
System.out.println("原变量的值:"+a);
System.out.println("新变量的值:"+b);

对于上面代码的执行结果,应该很简单:

1
2
原变量的值:56
新变量的值:65

赋值操作其实就是新建了一个变量,把原变量中的值拷贝了一份到新变量中。如下图:

也就是说修改 b 其实就是修改 b 变量中的值,与 a 变量完全没有关系。因为在把 a 的值赋值给 b 时,实际上是把 a 变量的拷贝了一份给 b。拷贝完之后 a 和 b 就没有关系了。

其实这里的说法不完全正确,与 JVM 的机制有关,但是大体上可以这样理解。

再来看一个例子:

1
2
3
4
5
6
ParamObject paramA = new ParamObject("小明");
ParamObject paramB = paramA;
paramB.setName("小红");

System.out.println("原对象的值:"+paramA.getName());
System.out.println("新对象的值:"+paramB.getName());

与上面的例子不同,这个例子的输出是:

1
2
原对象的值:小红
新对象的值:小红

上面的例子执行的情况如下图:

在将 paramA 的值赋给 paramB 时,和上面的基本类型其实是一样,把 paramA 中的引用创建了一个副本,所以 paramB 中存储的也是指向同一个对象的引用。所以通过 paramB 也就可以修改对象。

再看下面这个例子:

1
2
3
4
5
6
ParamObject paramA = new ParamObject("小明");
ParamObject paramB = paramA;
paramB = new ParamObject("小红");

System.out.println("原对象的值:"+paramA.getName());
System.out.println("新对象的值:"+paramB.getName());

这个例子输出的结果又不同:

1
2
原对象的值:小明
新对象的值:小红

这个例子的执行情况是这样的:

paramB 再进行 new 操作之后,paramB 中存储的引用就变成了新对象的地址了。所以修改新的对象并不会影响原来的对象。

所以可以把 Java 中的赋值操作总结为:对原变量中存储的内容进行拷贝,把拷贝的内容放到新的变量中,不论变量中存储的是基本类型还是引用类型

Java 中的参数传递

定义一个方法:

1
2
3
4
public void func(int a) {
a = 5;
System.out.println(a);
}

方法参数中的 a 称之为形参,在调用时传入的参数称之为实参

先说一下结论,Java 中参数的传递其实和赋值操作是一样的,相当于把实参赋值给形参。

所以传参也是将实参的副本给形参,修改形参的内容并不会影响实参(注意:这里说的是修改形参,并不是修改形参引用的对象)。

通过下面例子来证实一下:

1
2
3
4
5
6
7
8
int b = 5;
func(b);
System.out.println("执行方法后的值:"+b);

public void func(int a) {
a = 5;
System.out.println("在方法中修改形参的值"+a);
}

例子的输出为:

1
2
在方法中修改形参的值: 5
执行方法后的值:5

这个结果与上面基本类型的变量进行赋值的结果一直。再来看看引用类型参数的传递:

1
2
3
4
5
6
7
8
ParamObject b = new ParamObject("小红");
func1(b);
System.out.println("执行方法后的值:"+b.getName());

public void func1(ParamObject a) {
a.setName("小明");
System.out.println("在方法通过引用修改对象的值:"+a.getName());
}

这个例子的输出为:

1
2
在方法通过引用修改对象的值:小明
执行方法后的值:小明

这个例子与上面第二个例子的效果一致。再来看最后一个例子:

1
2
3
4
5
6
7
8
ParamObject b = new ParamObject("小红");
func2(b);
System.out.println("执行方法后的值:"+b.getName());

public void func2(ParamObject a) {
a = new ParamObject("小明");
System.out.println("在方法新建对象后的值:"+a.getName());
}

最后这个例子的输出为:

1
2
在方法新建对象后的值:小明
执行方法后的值:小红

也与上面第三个例子的结果表现一致。所以在 Java 中参数传递的方式与赋值完全一样。都是拷贝原变量中的内容,这种方式就是值传递。如果在传递的过程中不是通过副本,而是直接传递变量本身,这种方式称之为引用传递

所以说 Java 中的参数传递是值传递

© 2020 Rayjun    PowerBy Hexo