Java Essay Serials 1 - Java Basics - 16,值传递 vs 引用传递_Java

应几个法国巴西和印度小程序员的邀请,

我每周在Facebook上发布Java系列小文章,与你们也共享一下。

翻译 | 王楷涵

Java Essay Serials 1 - Java Basics - 16,值传递 vs 引用传递_Java_02

        Java总是传递的参数值。这也就意味着Java复制变量的值,并且将这个值赋值给参数,当参数的值被修改时,原始变量的值将不会被改变。

        我们可以通过以下例子来证明这一点:

    public static void increment(int i) {
System.out.println("input parameter : " + i);
i++;
System.out.println("parameter chnged to : " + i);
}
public static void main(String[] args){
int v1 = 200;
System.out.println("original var : " + v1);
increment(v1);
System.out.println("original var : " + v1);
}

        输出结果为:

    original var : 200
input parameter : 200
parameter chnged to : 201
original var : 200

        我们可以看到,对于最初始的变量,其值并未发生改变。其原因在于,当Java通过方法increment(int i)传递变量v1时,首先会复制其值(200)给参数i,因此,在自增时,该操作仅仅只会应用在参数i上。初始变量v1则从未触及。

        现在我们来看看程序中内存的变化:

        首先Java为变量v1分配了内存(0x00EA38FA)且赋值:

  address value
v1 0x00EA38FA 200

        当调用方法increment()时,Java分配另一块内存地址(0x302C09E0)给变量i并且设置变量i的值为200:

  address value
v1 0x00EA38FA 200
... ... ...
i 0x302C09E0 200

        ++操作将改变i的值,示意如下:

  address value
v1 0x00EA38FA 200
... ... ...
i 0x302C09E0 201

        当程序退出increment()方法时, 内存映射为:

  address value
v1 0x00EA38FA 200
... ... ...

        可以看到v1的值依旧是200。

        现在,又出现了另外一个问题:当你传递一个对象引用到方法时,将会发生什么?

        在这里给出另外一个例子:

        首先我们创建一个只包含一个整数域的INT类:

    public class INT {
int i;
INT(int i) {
this.i = i;
}
void increment() {
i++;
}
  }

        现在我们修改一下程序:

    public static void increment(INT I) {
System.out.println("input parameter : " + I + ", value : " + I.i);
I = new INT(201);
System.out.println("parameter changed to : " + I+ ", value : " + I.i);
  }
   public static void main(String[] args){
INT v1 = new INT(200);
System.out.println("original var : " + v1+ ", value : " + v1.i);
increment(v1);
System.out.println("original var : " + v1+ ", value : " + v1.i);
  }

        程序输出结果为:

original var : com.test.INT@15db9742, value : 200
input parameter : com.test.INT@15db9742, value : 200
parameter changed to : com.test.INT@6d06d69c, value : 201
original var : com.test.INT@15db9742, value : 200

        值不应该被改变吗?当然不,这就是原因所在:

        Java首先创建一个v1对象,且赋值为200,请注意v1是一个对象引用,该引用也将被分配内存地址,并且其对象实例也会被分配内存,这是内存映射关系:

  address value
v1 0x003065D0 0x480012E0
... ... ...
instance 0x480012E0 200

        当调用increment(v1)方法时,Java分配了另外一块内存地址(0x302C09E0)给参数I且将v1的值(0x480012E0)赋值给I

  address value
v1 0x003065D0 0x480012E0
... ... ...
I 0x302C09E0 0x480012E0
... ... ...
instance 0x480012E0 200

        当执行increment(INT I)方法体中的I = new INT(201);这一行语句时,Java创建了一个新的INT对象实例并且分配地址(0x52001230)给I,以下是内存映射关系:

  address value
v1 0x003065D0 0x480012E0
... ... ...
I 0x302C09E0 0x480012E0
... ... ...
instance 0x480012E0 200
... ... ...
instance2 0x52001230 201

        目前在内存中,存在两个对象引用和两个对象实例了。

        在退出increment(INT I)方法后,内存将被回收:

  address value
v1 0x003065D0 0x480012E0
... ... ...
instance 0x480012E0 200

        这也就是为什么v1引用实例后值保持200不变的原因。

        虽然对象引用的参数值不能被修改,但可以更改引用的对象/实例的内容。

        例如:

    public static void increment(INT I) {
System.out.println("input parameter : " + I + ", value : " + I.i);
I.increment();
System.out.println("parameter changed to : " + I+ ", value : " + I.i);
  }
   public static void main(String[] args){
INT v1 = new INT(200);
System.out.println("original var : " + v1+ ", value : " + v1.i);
increment(v1);
System.out.println("original var : " + v1+ ", value : " + v1.i);
  }

        结果输出为:

original var : com.test.INT@15db9742, value : 200
input parameter : com.test.INT@15db9742, value : 200
parameter changed to : com.test.INT@15db9742, value : 201
original var : com.test.INT@15db9742, value : 201

        这是因为变量v1和参数都引用同一个对象。I.increment()语句改变了引用实例的内容,结果展示如下:

  address value
v1 0x003065D0 0x480012E0
... ... ...
I 0x302C09E0 0x480012E0
... ... ...
instance 0x480012E0 201