描述这个问题的原因是这样的,在做链表相关的算法题中(剑指 Offer II 027. 回文链表),想要获取中间链表,实现快慢指针时发现,传递到方法中应该是原链表的地址值,但在方法中处理完获得到新的中间链表后,原链表没有任何变化。
先看一下ListNode链表的类。
public class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
@Override
public String toString() {
return next == null ? val + "" : val + " -> " + next;
}
}
产生疑问的代码是这样的:
public static ListNode getMidListNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
所以写了以下代码加以验证,修改链表的next为null后,打印原链表,发现发生了变化。
public static void main(String[] args) {
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
System.out.println(head);
ListNode listNode = f1(head);
System.out.println(head);
System.out.println(listNode);
System.out.println(head == listNode);
}
public static ListNode f1(ListNode head) {
head.next = null;
return head;
}
/**
* 输出的结果
* 1 -> 2 -> 3 -> 4 -> 5
* 1
* 1
* true
*/
由此得到结论1:对象直接传递,会传递地址值,方法中对该对象操作,原本对象也会发生改变。
发现和自己想的一样,再试一下第二种。
public static void main(String[] args) {
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
System.out.println(head);
ListNode listNode = f2(head);
System.out.println(head);
System.out.println(listNode);
System.out.println(head == listNode);
}
public static ListNode f2(ListNode head) {
ListNode l1 = head;
l1.next = null;
return l1;
}
/**
* 输出的结果
* 1 -> 2 -> 3 -> 4 -> 5
* 1
* 1
* true
*/
由此得出结论2:对象直接传递,会传递地址值,方法中使用新对象获取到该地址值后,对新对象内容进行修改,原本对象仍会发生改变。
发现和自己想的还是一样的,那让我产生疑问的代码问什么没有影响原本的对象呢?
产生疑问的代码是这样的:
public static void main(String[] args) {
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
System.out.println(head);
ListNode listNode = getMidListNode(head);
System.out.println(head);
System.out.println(listNode);
System.out.println(head == listNode);
}
public static ListNode getMidListNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
/**
* 输出的结果
* 1 -> 2 -> 3 -> 4 -> 5
* 1 -> 2 -> 3 -> 4 -> 5
* 3 -> 4 -> 5
* false
*/
仔细又看了一遍,还是认为head的地址已经给到slow和fast了,但为什么slow = slow.next没有影响到head并且slow和fast没有相互影响?
还是没想通,提取出来关键部分试了一下:
public static void main(String[] args) {
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
System.out.println(head);
ListNode listNode = f3(head);
System.out.println(head);
System.out.println(listNode);
System.out.println(head == listNode);
}
public static ListNode f3(ListNode head) {
head = head.next;
return head;
}
/**
* 输出的结果
* 1 -> 2 -> 3 -> 4 -> 5
* 1 -> 2 -> 3 -> 4 -> 5
* 2 -> 3 -> 4 -> 5
* false
*/
突然顿悟,因为传过来的head链表的地址值发生改变,变成了head.next的地址,而Java中方法接收的变量如果是引用类型时,传递的为该对象地址值的拷贝,所以方法中可以对该地址的值进行修改,但无法修改此对象的地址值。
写了下面的代码验证了一下,没发现什么问题。
public static void main(String[] args) {
List<Integer> mainList = new ArrayList<>();
mainList.add(0);
System.out.println(mainList);
List<Integer> methodList = f1(mainList);
System.out.println(mainList);
System.out.println(methodList);
}
public static List<Integer> f1(List<Integer> list){
list = new ArrayList<>();
list.add(1);
return list;
}
/**
* 输出的结果
* [0]
* [0]
* [1]
*/
public static void main(String[] args) {
List<Integer> mainList = new ArrayList<>();
mainList.add(0);
System.out.println(mainList);
List<Integer> methodList = f2(mainList);
System.out.println(mainList);
System.out.println(methodList);
}
public static List<Integer> f2(List<Integer> list){
list.add(1);
return list;
}
/**
* 输出的结果
* [0]
* [0, 1]
* [0, 1]
*/
- 在f1中修改了list指向的地址值,之后对list进行处理原对象不会发生变化。
- 在f2中直接对list进行操作,原本list也会发生改变。
得到最终结论,Java的引用传递时,传递的是原对象地址的拷贝,如果对该对象直接进行操作,会对原对象产生影响,但如果修改了该对象指向的地址值,原对象的地址值不会发生改变(传递的是原对象地址的拷贝)。
这句话怎么理解呢,综合起来就是引用变量的地址,可以改变但也不可以改变。 修改了引用变量的地址后,该引用变量指向了新的地址,原地址的对象还在那,之后的操作都是对新的地址上的数据进行操作。
只是我的理解,不能确定是否完全正确,期待各位大佬的批评指正。