细节 : 详解Java中对象的引用及赋值

问题引入

  1. 前言

前些天学习数据结构与算法时特意写了一篇名为详解Linked-list的实现方式及其应用的文章,其中循环链表的代码中就淋漓尽致地体现了Java中对象的引用与赋值

  1. 循环链表的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    package pers.huangyuhui.linkedlist;

    /**
    * @ClassName: CircularLinkedList
    * @Description: 操作循环链表
    * @author: HuangYuhui
    * @param <T>
    * @date: Apr 16, 2019 7:25:36 PM
    *
    */
    public class CircularLinkedList<T> {

    // get the length of the circular linked list
    public int getLength(ListNode<T> headNode) {
    int length = 0;
    ListNode<T> currentNode = headNode;
    while (currentNode != null) {
    currentNode = currentNode.getNext();
    length++;
    if (currentNode == headNode) {
    break;
    }
    }
    return length;
    }

    // traverse the node of the circular linked list
    public void traverseNode(ListNode<T> headNode) {
    System.out.println("\n### [headNode]-address : " + headNode + "\n");
    ListNode<T> currentNode = headNode;
    while (currentNode != null) {
    System.out.print(currentNode.getData() + " -> ");
    currentNode = currentNode.getNext();
    if (currentNode == headNode) {
    break;
    }
    }
    System.out.print("headNode(" + currentNode.getData() + ")\n");
    }

    // add new node at the tail of linked list
    public void insertAtListTail(ListNode<T> headNode, ListNode<T> newNode) {

    ListNode<T> currentNode = headNode;
    while (currentNode.getNext() != headNode) {
    //// 注意: 由于`currentNode`无变化,导致`currentNode.getNext() != headNode`继而进入进入死循环 !////
    // currentNode.setNext(currentNode.getNext());
    currentNode = currentNode.getNext();
    }
    newNode.setNext(newNode);
    if (headNode == null) {
    headNode = newNode;
    } else {
    newNode.setNext(headNode);
    currentNode.setNext(newNode);
    }
    }

    // add new node at the header of linked list
    public ListNode<T> insertAtListHeader(ListNode<T> headNode, ListNode<T> newNode) {

    ListNode<T> currentNode = headNode;
    while (currentNode.getNext() != headNode) {
    currentNode = currentNode.getNext();// 尾节点
    }
    newNode.setNext(newNode);// 指针指向自身
    if (headNode == null) {
    headNode = newNode;
    }
    newNode.setNext(headNode);
    currentNode.setNext(newNode);
    // 注意: 此时链表头结点已更新! 所以应该返回更新后的头节点继而避免遍历时出现死循环!!!
    headNode = newNode;
    return headNode;
    }

    // add the new node by the specified index
    public ListNode<T> insertNodeByIndex(ListNode<T> headNode, ListNode<T> newNode, int position) {

    if (headNode == null) {
    System.out.println("Error: the circular list is empty !");
    return null;
    } else if (position == 1) {
    insertAtListHeader(headNode, newNode);
    } else if (position == getLength(headNode)) {
    insertAtListTail(headNode, newNode);
    } else {
    ListNode<T> currentNode = headNode;
    ListNode<T> temp = headNode;
    for (int i = 0; i < position - 1; i++) {
    temp = currentNode;// 待插节点的前节点
    currentNode = currentNode.getNext();// 待插节点
    }
    temp.setNext(newNode);
    newNode.setNext(currentNode);
    }
    return headNode;
    }

    // delete the last node
    public void deleteLastNode(ListNode<T> headNode) {

    ListNode<T> temp = headNode;
    ListNode<T> currentNode = headNode;
    if (headNode == null) {
    System.err.println("Error: the circular list is empty !");
    }
    while (currentNode.getNext() != headNode) {
    temp = currentNode; // 尾节点的前一个节点
    currentNode = currentNode.getNext();
    }
    temp.setNext(headNode);
    currentNode = null;
    }

    // delete the header node
    public ListNode<T> deleteHeaderNode(ListNode<T> headNode) {
    ListNode<T> currentNode = headNode;
    if (headNode == null) {
    System.err.println("Error: the circular list is empty !");
    return null;
    }

    while (currentNode.getNext() != headNode) {
    // currentNode.setNext(currentNode.getNext());//死循环
    currentNode = currentNode.getNext();
    }
    currentNode.setNext(headNode.getNext());
    // 注意: 此时链表头结点已更新! 所以应该返回更新后的头节点继而避免遍历时出现死循环!!!
    headNode = headNode.getNext();
    return headNode;
    }

    // delete the node by the specified index
    public ListNode<T> deleteNodeByIndex(ListNode<T> headNode, int position) {

    if (headNode == null) {
    System.out.println("Error: the circular linked list is empty !");
    return null;
    } else if (position == 1) {
    deleteHeaderNode(headNode);
    } else if (position == getLength(headNode)) {
    deleteLastNode(headNode);
    } else {
    ListNode<T> currentNode = headNode;
    ListNode<T> temp = headNode;
    for (int i = 0; i < position - 1; i++) {
    temp = currentNode;// 待删节点的前节点
    currentNode = currentNode.getNext();// 待删节点
    }
    temp.setNext(currentNode.getNext());
    }

    return headNode;
    }

    // Test
    public static void main(String[] args) {

    // 初始化链表头结点
    CircularLinkedList<Integer> list = new CircularLinkedList<>();
    ListNode<Integer> headNode = new ListNode<>();
    headNode.setData(1);// 初始化链表头结点
    headNode.setNext(headNode);// 节点指针指向自身

    // 初始化待插入的链表节点
    ListNode<Integer> a = new ListNode<>();
    a.setData(2);
    ListNode<Integer> b = new ListNode<>();
    b.setData(3);
    ListNode<Integer> c = new ListNode<>();
    c.setData(4);
    ListNode<Integer> d = new ListNode<>();
    d.setData(100);
    ListNode<Integer> e = new ListNode<>();
    e.setData(101);
    ListNode<Integer> f = new ListNode<>();
    f.setData(0);

    // 向链表的尾部添加三个节点
    System.out.print("the origin node: ");
    list.insertAtListTail(headNode, a);
    list.insertAtListTail(headNode, b);
    list.insertAtListTail(headNode, c);
    list.traverseNode(headNode);
    System.out.println("the length of the list: " + list.getLength(headNode));

    // 向链表中添加两个头结点( 头结点被更新 )
    System.out.print("add two header node: ");
    ListNode<Integer> newHeadNode = list.insertAtListHeader(headNode, d);
    // 注意: 由于`头结点`已在`insertAtListHeader`中已更新所以要向`traverseNode`传入新的头结点
    ListNode<Integer> newHeadNode2 = list.insertAtListHeader(newHeadNode, e);
    list.traverseNode(newHeadNode2);
    System.out.println("the length of the list: " + list.getLength(headNode));

    // 在链表的指定位置上插入新的节点
    System.out.print("Insert the new node at position 3: ");
    list.insertNodeByIndex(headNode, f, 3);
    list.traverseNode(newHeadNode2);
    System.out.println("the length of the list: " + list.getLength(headNode));

    // 删除链表的尾节点
    System.out.print("delete the tail node: ");
    list.deleteLastNode(newHeadNode2);
    list.traverseNode(newHeadNode2);
    System.out.println("the length of the list: " + list.getLength(headNode));

    // 注意: 由于`头结点`已在`deleteHeaderNode`中已更新所以要向`traverseNode`传入新的头结点对象
    ListNode<Integer> newHeadNode3 = list.deleteHeaderNode(newHeadNode2);
    System.out.print("delete the header node: ");
    list.traverseNode(newHeadNode3);
    System.out.println("the length of the list: " + list.getLength(headNode));

    // 删除链表中指定位置的节点
    ListNode<Integer> newHeadNode4 = list.deleteNodeByIndex(newHeadNode3, 4);
    System.out.print("delete the fourth node: ");
    list.traverseNode(newHeadNode4);
    System.out.println("the length of the list: " + list.getLength(headNode));

    }
    }
  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
    25
    26
    27
    28
    29
    30
    the origin node: 
    ### [headNode]-address : pers.huangyuhui.linkedlist.ListNode@16f65612

    1 -> 2 -> 3 -> 4 -> headNode(1)
    the length of the list: 4
    add two header node:
    ### [headNode]-address : pers.huangyuhui.linkedlist.ListNode@311d617d

    101 -> 100 -> 1 -> 2 -> 3 -> 4 -> headNode(101)
    the length of the list: 6
    Insert the new node at position 3:
    ### [headNode]-address : pers.huangyuhui.linkedlist.ListNode@311d617d

    101 -> 100 -> 1 -> 2 -> 0 -> 3 -> 4 -> headNode(101)
    the length of the list: 7
    delete the tail node:
    ### [headNode]-address : pers.huangyuhui.linkedlist.ListNode@311d617d

    101 -> 100 -> 1 -> 2 -> 0 -> 3 -> headNode(101)
    the length of the list: 6
    delete the header node:
    ### [headNode]-address : pers.huangyuhui.linkedlist.ListNode@7c53a9eb

    100 -> 1 -> 2 -> 0 -> 3 -> headNode(100)
    the length of the list: 5
    delete the fourth node:
    ### [headNode]-address : pers.huangyuhui.linkedlist.ListNode@7c53a9eb

    100 -> 1 -> 2 -> 3 -> headNode(100)
    the length of the list: 4

基本数据类型作为参数传递

  1. 示例程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class test {

    public static void main(String[] args) {
    int i = 1;
    System.out.println("before change, i=" + i);
    change(i);
    System.out.println("after change, i=" + i);
    }

    private static void change(int i) {
    i = 5;
    }
    }
  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
    25
    26
    27
    28
    before change, i=1
    after change, i=1
    ```

    3. *结论*

    *当`基本数据类型`作为参数传递时,传递时的是`实参的副本`,既传的是`值`,无论在函数中怎么操作这个副本,实参的值是不会被改变的.*



    ### 对象作为参数传递
    1. *第一个示例程序*
    ```java
    public class test {

    public static void main(String[] args) {

    StringBuffer sb = new StringBuffer("Hello");
    System.out.println("before change: " + sb);
    change(sb);
    System.out.println("after change: " + sb);
    }

    private static void change(StringBuffer stringBuffer) {
    stringBuffer.append(" world !");

    }
    }
  3. 程序运行结果

    1
    2
    before change: Hello
    after change: Hello world !
  4. 探究结论

从上述程序运行结果可知sb所指的对象的值被改变了!那么我们是否就可以认为Java中的对象作为参数传递时,传递的是该对象的引用呢 ? 那我们再来看第二个示例程序.

  1. 第二个示例程序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class test {

    public static void main(String[] args) {

    StringBuffer sb = new StringBuffer("Hello");
    System.out.println("before change: " + sb);
    change(sb);
    System.out.println("after change: " + sb);
    }

    private static void change(StringBuffer stringBuffer) {
    stringBuffer = new StringBuffer("Hi");
    stringBuffer.append(" world !");

    }
    }
  2. 程序运行结果

如果上面的推论:(Java中对象作为参数传递时,实际传递的是该对象的引用)是正确的,那么在调用change函数后,原对象的值应该会被改变,既变为:Hi World !,但是,该程序的运行结果如下 !!!

1
2
before change: Hello
after change: Hello

可知原对象(sb)的值并没有被改变,这是为什么呢? 下面让我们来分析一下其中的原因吧嘿嘿 ~

  1. 结论分析

当我们执行StringBuffer sb = new StringBuffer("Hello")时,我们便创建了一个指向新建对象new StringBuffer("Hello")的引用sb,如下所示.

1
sb ——————> [Hello]

第二个示例程序中,当我们调用change函数后,实际上,形参stringBuffer也指向了实参sb所指的对象! 如下所示.

1
sb ——————> [Hello] <—————— stringBuffer

那么当我们执行stringBuffer.append("world!")后,便通过对象的引用(stringBuffer)修改了对象的值,使之修改成了: Hello world !,如下所示:

1
sb ——————> [Hello world !] <—————— stringBuffer

但是在第二个示例程序中的change函数中,我们又新建了一个对象:new StringBuffer("Hi")(该操作实际上是在内存中开辟了一块在原对象地址之外的新区域),这让形参stringBuffer实际指向了这个新建的对象,并将新对象的值设置为了Hi world !,如下所示:

1
2
sb ——————> [Hello]
stringBuffer ——————> [Hi World !]
  • 综上所述,可以得出结论: 在Java中,当对象作为参数传递时,实际上传递的是一份"引用"的拷贝 !

总结

在Java中, = 不能看成一个赋值语句,因为它并不是把一个对象赋给另一个对象的过程,它的执行过程实质上是将右边对象的地址传给了左边的引用,使得左边的引用指向了右边的对象.Java表面上看起来没有指针,但它的引用实质上就是一个指针,引用里面存放的并不是对象,而是存放该对象的地址,使得该引用指向了该对象.