编程语言中有方法间的参数传递,这个传递的策略叫做求值策略。本篇文章将以Java为核心带你彻底区分值传递,引用传递,地址传递的区别。
在Java中,求值策略有两种方式:值传递和引用传递。重要的是理解 Java 中的求值策略实际上都是值传递。
1.值传递(Pass by Value):
当你将一个基本数据类型(如int、float、char等)作为参数传递给方法时,实际上是将该变量的值拷贝传递给方法内部。这意味着在方法内部对参数的修改不会影响到原始变量。
public void modifyValue(int x) {
x = 10;
}
int num = 5;
modifyValue(num);
System.out.println(num); // 输出 5,原始变量没有改变
2.引用传递(Pass by Reference)的误解:
在 Java 中,对象引用也是通过值传递的,而不是真正的引用传递。当你将一个对象引用作为参数传递给方法时,实际上是将对象引用的副本传递给了方法,这个副本仍然指向原始对象。因此,如果在方法内部修改了对象的状态,这些修改将会反映在原始对象上。
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
public void modifyName(Person person) {
person.name = "Alice";
}
Person person = new Person("Bob");
modifyName(person);
System.out.println(person.name); // 输出 "Alice",原始对象的状态被修改了
//进入modifyName后,方法里的person和方法外的person都引用Bob这个person对象, person.name = "Alice"改变了这个对象的name属性
再举一个链表的例子:
public class ListNode {
public ListNode next;
public int val;
public ListNode(){
}
public ListNode(ListNode next){
this.next=next;
}
public ListNode(ListNode next,int val){
this.next=next;
this.val=val;
}
}
public class Main {
public static void main(String[] args) {
ListNode node2=new ListNode(null,2);
ListNode node1=new ListNode(node2,1);//手动构建一个两个结点的链表:1->2
move(node1);
System.out.println(node1.val);//输出结果为1 move方法内的改变并没有反映到node1变量上,因为head只是node1的一份拷贝
}
public static void move(ListNode head){
head=head.next;
}
}
//在进入move后,head和node1都引用链表中的第一个结点,head=head.next改变的只是head
在这个例子中,你可以将该次求值策略类比为C++中的引用传递,转化成C++代码大概就是这个样子:
public void move(ListNode &head){
head=head.next;
}
但绝不是地址传递:
public void move(ListNode *head){
head=head.next;
}
如果是C++中的地址传递,那在本例中输出结果将为2。node1就指向了链表中第二个结点。
(注意理解和体会引用传递,地址传递的含义和区别)
(注:本人不是专门搞C++,明白我想表达的意思就好,如果类比有瑕疵,也欢迎指出错误)
如果你再仔细看一下的话,你会发现两个例子为什么一个改变反映到了原变量上,而另一个改变没有反映到原变量上?
答案是modifyName方法中修改的是对象的属性,move方法中修改的是对象本身。
总结一下
在Java中,无论是基本数据类型还是引用数据类型,都是通过值传递的方式传递给方法。对于基本数据类型,传递的是值的拷贝,对于对象引用,传递的是引用的拷贝。如果方法内部修改了参数的值(无论是基本数据类型的值还是对象引用的值),这些修改都不会影响原始变量或对象的值。但是,如果方法内部修改了对象的状态(通过引用),这些修改将会反映在原始对象上。
说了那么多,总之一句话,在Java中不管是基本类型还是引用类型,方法中的形参都是实参拷贝的一份副本。