使用链表引出对引用传递的思考

55 阅读4分钟

描述这个问题的原因是这样的,在做链表相关的算法题中(剑指 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的引用传递时,传递的是原对象地址的拷贝,如果对该对象直接进行操作,会对原对象产生影响,但如果修改了该对象指向的地址值,原对象的地址值不会发生改变(传递的是原对象地址的拷贝)

这句话怎么理解呢,综合起来就是引用变量的地址,可以改变但也不可以改变。 修改了引用变量的地址后,该引用变量指向了新的地址,原地址的对象还在那,之后的操作都是对新的地址上的数据进行操作。

只是我的理解,不能确定是否完全正确,期待各位大佬的批评指正。