JAVA方法中参数到底是值传递还是引用传递

Posted by Chenyawei on 2021-02-04
Words 1.5k and Reading Time 6 Minutes
Viewed Times

当一个对象被当作参数传递到一个方法后,在此方法内可以改变这个对象的属性,那么这里到底是值传递还是引用传递?

答:是值传递。Java 语言的参数传递只有值传递。当一个实例对象作为参数被传递到方法中时,参数的值就是该对象的引用的一个副本。指向同一个对象,对象的内容可以在被调用的方法内改变,但对象的引用(不是引用的副本) 是永远不会改变的。

Java的参数传递,不管是基本数据类型还是引用类型的参数,都是按值传递,没有按引用传递!

我们可以看一下microsoft的文档中对按引用传递参数的定义(如下截图):https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/ref#passing-an-argument-by-reference

img

对于参数的传递可以分为两种情况:

1.基本数据类型的参数

1
2
3
4
5
6
7
8
9
10
11
12
public class TransferTest {
public static void main(String[] args) {
int num = 1;
System.out.println("changeNum()方法调用之前:num = " + num);
changeNum(num);
System.out.println("changeNum()方法调用之后:num = " + num);
}

public static void changeNum(int x) {
x = 2;
}
}

运行结果:

1
2
changeNum()方法调用之前:num = 1
changeNum()方法调用之后:num = 1

传递过程的示意图如下:

img

分析:num作为参数传递给changeNum()方法时,是将内存空间中num所指向的那个存储单元中存放的值1复制了一份传递给了changeNum()方法中的x变量,而这个x变量也在内存空间中分配的一个存储单元。这时就把num对的值1传递给了x变量所指向的存储单元中。此后在changeNum()方法中对x变量的一切操作都是针对于x所指向的这个存储单元,与num所指向的存储单元无关。

所以,在changeNum()方法被调用后,num所指向的存储单元的值还是没有发生变化,这就是所谓的“值传递”。值传递的精髓是:传递的是存储单元中的内容,而不是存储单元的引用。

2.引用类型的参数

示例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 1  public class TransferTest2 {
2 public static void main(String[] args) {
3 Person person = new Person();
4 System.out.println(person);
5 change(person);
6 System.out.println(person);
7 }
8
9 public static void change(Person p) {
10 p = new Person();
11 }
12 }
13
14 /**
15 * Person类
16 */
17 class Person {
18
19 }

运行结果:

1
2
Person@17f052a3
Person@17f052a3

可以看出两次打印结果一致。即调用change()方法后,person变量并没发生改变。

传递过程的示意图如下:

img

分析:

01.当程序执行到第3行 Person person = new Person()时,程序在堆内存(heap)中开辟了一块内存空间用来存储Person类实例对象,同时在栈内存(stack)中开辟了一个存储单元来存储该实例对象的引用,即上图中person指向的存储单元。

02.当程序执行到第5行 change(person)时,person作为参数(实参)传递给了change()方法。这里是person将自己的存储单元的内容传递给了change()方法的p变量。此后在change()方法中对p变量的一切操作都是针对于p变量所指向的存储单元,与perosn所指向的存储单元就没有关系了。

示例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test {
public static void main(String[] args) {
P p = new P("P");
System.out.println("before change p:" + p.toString());
changeObj(p);
System.out.println("after change p:" + p.toString());
}

static void changeObj(P p) {
p = new P("pp");
System.out.println("change p:" + p.toString());
}
}

class P {
String name = "P";
public P(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}

运行结果:

1
2
3
before change p:P
change p:pp
after change p:P

首先要理解 “=” 赋值操作的意义:

对于基本数据类型来说 “=”赋值操作是直接改变内存地址(存储单元)上的值。

对于引用类型来说 “=” 赋值操作是改变引用变量所指向的内存地址(上文中存储单元)。

调用changeObj()方法进入第9行:

img

执行10行后:

img

所以对外部的p变量是没有影响的。

总结:

函数参数传递其实是一个赋值的过程,基本类型传递的是数值,引用类型传递的引用对象的内存地址。

另外一点要特别注意,函数的参数其实是函数内部的局部变量。不要跟外部变量混淆。

示例3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test {
public static void main(String[] args) {
Person a = new Person(23, "a");
Person b = new Person(22,"b");
System.out.println("交换前\na:"+a+"\nb:"+b);
swap(a, b);
System.out.println("交换后\na:"+a+"\nb:"+b);
}
private static void swap(Person a, Person b) {
Person temp = a;
a = b;
b = temp;
}
}

class Person{
private int id;
private String name;
public Person(int id,String name) {
this.id = id;
this.name = name;
}
}

运行结果:

1
2
3
4
5
6
交换前
a:com.example.webflux.demotest.Person@17f052a3
b:com.example.webflux.demotest.Person@3d646c37
交换后
a:com.example.webflux.demotest.Person@17f052a3
b:com.example.webflux.demotest.Person@3d646c37

同理,外部的ab引用不是内部的ad引用,故不会发生改变。

我们可以发现,Java方法的传值,实际上是把实参的值—-对象引用(对象的内存地址)传递给了形参,从而形参和实参的值(即变量里存储的内存地址,非变量本身的内存地址)是相同的,指向了同一个对象/内存地址。

在此之前是转的其他博主:https://www.cnblogs.com/9513-/p/8484071.html

===================================我是分割线==========================================================================================

示例4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Test {
public static void main(String[] args) {
Person person = new Person(1,"张三");
System.out.println(person);
demo2(person);
System.out.println(person);
}
private static void demo2(Person person1){
person1.setName("李四");
person1.setId(2);
System.out.println(person1);
}
}

@Data
class Person{
private int id;
private String name;
public Person(int id,String name) {
this.id = id;
this.name = name;
}
}

执行结果:

1
2
3
Person(id=1, name=张三)
Person(id=2, name=李四)
Person(id=2, name=李四)

**把person对象的地址的引用传给了person1 ,所以person和person1指向的是同一个内存地址。


notice

欢迎访问 chenyawei 的博客, 若有问题或者有好的建议欢迎留言,笔者看到之后会及时回复。 评论点赞需要github账号登录,如果没有账号的话请点击 github 注册, 谢谢 !

If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !