2.1 别名现象的发生场景
对对象进行赋值时 方法调用中,传递一个对象时 2.2 对对象进行赋值时的别名现象
Person.java:很简单的一个类,仅仅拥有一个属性
[java] view plain copy <span style="font-size:18px;">public class Person { int age; }</span>
Client.java:场景类或测试类 [java] view plain copy <span style="font-size:18px;">public class Client { public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person(); p1.age = 18; p2.age = 21; System.out.println("1. p1.age: " + p1.age + ", p2.age: " + p2.age); p1 = p2; // 使 p2 和 p1 拥有相同的对象引用 System.out.println("2. p1.age: " + p1.age + ", p2.age: " + p2.age); // 注意 3 的输出 p1.age = 18; System.out.println("3. p1.age: " + p1.age + ", p2.age: " + p2.age); } }</span> 输出如下: [html] view plain copy <span style="font-size:18px;">/********************** * 1. p1.age: 18, p2.age: 21 * * 2. p1.age: 21, p2.age: 21 * * 3. p1.age: 18, p2.age: 18 * *******************************/</span> 分析: 主要关注第三行的输出,可以发现当 p1 的 age 值修改为 18 后,p2 的 age 也变为 18 了,这是为什么?why? 原因是因为,当进行对象赋值操作时,如此处的 p2 = p1; 当该条语句执行完毕,p2 和 p1 将拥有对同一个对象的引用。当对 p1 中的属性进行修改时,因为是相同的对象引用,所以 p2 的值自然也是随之修改了。我们可以类比于C ,在 C 语言中使用指针操作一个内存块,内存块内存储的值被修改了,那么所有指向该内存的指针变量的值都会被修改[注:C语言学的不好] 2.3 方法调用中的别名现象 将一个对象传递给方法时,也会产生别名问题。 Person.java [java] view plain copy <span style="font-size:18px;">public class Person { char sex; }</span> Client.java [java] view plain copy <span style="font-size:18px;">public class Client { public static void f(Person person){ person.sex = 'W'; } public static void main(String[] args) { Person person = new Person(); person.sex = 'M'; System.out.println("1. person.sex: " + person.sex); // 注意 2 的输出 f(person); System.out.println("2. person.sex: " + person.sex); } }</span> 输出如下: [html] view plain copy <span style="font-size:18px;">/********************** * 1. person.sex: M * * 2. person.sex: W * **********************/</span> 分析 从输出也可看到,当调用 f() 函数后,即使 f() 函数是 void 类型,但是第2条输出中,person 的值还是改变了。这里我们以为我们给 f() 函数传递了一个值,f() 函数会在其作用域内复制参数 Person person 的副本,然后操作副本。但是实际上我们传递的是一个引用。因此实际改变的是 f() 函数之外的对象。 关于方法中别名现象问题的特别案例:我们知道对于数组对象或Objec类的导出类对象,在将这些对象作为参数传递给方法时,会产生别名现象。但是一个特别要注意的一点就是,别名现象出现的场景!!!只有我们在方法中,直接操作对象的方法来修改该对象的值时,才会产生别名现象。若是在方法内,对该对象重新赋值,即重新 new 一下,那么是不会出现别名现象的。原因待会儿解释,先给出案例。如下: [java] view plain copy <span style="font-size:18px;">public class Test { @org.junit.Test public void testArr() { int[] arr = new int[]{1, 2, 3, 4}; changeArr(arr); for (int i = 0; i < arr.length; i ++) { System.out.print(arr[i] + " "); } } private void changeArr(int[] arr) { // 若是直接在 new 一个数组,则在 testArr 中打印,, arr 没有出现别名现象,还是 1 2 3 4 arr = new int[]{2, 2, 3, 4}; // 直接修改 arr 中元素的值,则 arr 出现改变。 2 2 3 4 // arr[0] = 2; } @org.junit.Test public void testPerson () { Person p = new Person(); p.setId(1); p.setName("johnnie"); p.setAge(22); p.setSex(Sex.MAN); changePerson(p); System.out.println(p); } private void changePerson(Person p) { // p = new Person(); // p.setId(1); // p.setName("Lisa"); // p.setAge(22); // p.setSex(Sex.WOMAN); p.setAge(21); } }</span> 在 Test.java 中,我们给出了 testArr() 和 testPerson() 方法,分别用于测试数组对象和普通对象在将对象作为参数在方法传递过程中别名现象的情况。我们使用两种情况,一种是直接 new 出一个新对象赋值给形参,然后再执行 testXXX 方法;另一种是直接操作形参,即使用形参的方法直接修改其内的值,然后再执行 testXXX 方法。分别查看两种情况的结果: ① 第一种情况下: testArr() 的显示:直接就是 1 2 3 4 testPerson() 的显示:直接就是 Person [id=1, name=johnnie, age=22, sex=男] ② 第二种情况下: testArr() 的显示:直接就是 2 2 3 4 testPerson() 的显示:直接就是 Person [id=1, name=johnnie, age=21, sex=男] 可以发现,第一种情况下,在外部打印的结果是对象没有改变。而第二种情况下,对象改变了。那么这是为什么呢? 原因很简单,我们以 changeArr 方法来解释。 在第一种情况下,我们在 changeArr() 中,是使用 new 来改变的,而使用 new 关键字呢,JVM 就会在堆中新开辟一个空间,存储新的数组对象,而 arr 就指向堆中新内存地址的引用。但是,在 changeArr() 中,该 arr 还仅仅是局部变量,其生命周期是和 changeArr() 一样的,与其共存亡的,而我们 changeArr() 方法是没有返回值的,也就是我们没有把 arr 返回,让 testArr() 中的 arr 发生改变,让其重新指向堆中新开辟的内存地址,因此第一种情况是不会发生变化,还是 1 2 3 4。 在第二种情况下,就更容易理解了。因为是直接使用 arr 来修改的,在该片内存空间中直接改变值,所以,即使没有返回值,形参 arr 与 changeArr() 同时挂逼了,外部的 arr 还是指向原来的空间,但是此时该片空间中的数据却已经发生了改变。因此,结果就变为 2 2 3 4 了。