再论传值与传引用

在Java语言中,很多初学者一直搞不懂在方法调用中,传的参数究竟是传值还是传引用的问题。在我接触的工作几年的程序员中,也有一些人弄不清楚这个问题,那么我现在就结合代码运行的结果和代码编译以后的字节码来透彻地分析这个问题,先看下面一段代码:

package com.waioo;

public class A {
public void change(int i){
i = 2;
}

public static void main(String[] args) {
A a = new A();
int k = 0;
a.change(k); //2
System.out.println(k);
}

}


在这段代码中,将一个普通的int型数据传入change(int)函数,然后让函数改变参数的值,这段代码的输出结果是什么呢?初学者会说,输出“2”,有经验的程序员说,这肯定会输出“0”撒,那么对,老手你说对了,为什么呢?因为当方法的参数类型是基本数据类型的时候,传入的参数是采取的的直接复制参数值的策略,即在行2这里,传入的k实际上不是变量,而是直接传入的数值0到change(int)方法内,所以并不会影响到main方法中的k的值。通过javap -c A可以看到,A.class的字节码内容如下:

public void change(int);
Code:
0: sipush 201
3: istore_1
4: return

这里是直接将数值201压入堆栈,而并不是操作的一个变量。

接下来对代码做一下改动,将int参数改为java.util.Date日期类型参数,代码如下:

package com.waioo;

import java.util.Date;


public class A {
public void changeYear(Date date){
date.setYear(1);
}

public static void main(String[] args) {
A a = new A();
Date date = new Date();
a.changeYear(date);
System.out.println(date);
}
}


这段代码的输出结果为:Sat Mar 23 23:48:37 CST 1901,看来确实,java在传引用类型变量(类,接口和数组)的时候,确实传的是引用,因为main方法中的date的值改变了。但是如果我们在date.setYear(1);前面加一行,结果如何呢?一切都就变了。将上面的代码稍微改动一下:

package com.waioo;

import java.util.Date;

public class A {
public void changeYear(Date date){
date = new Date();//加入这一行
date.setYear(1);
}

public static void main(String[] args) {
A a = new A();
Date date = new Date();
a.changeYear(date);
System.out.println(date);
}
}


好,因为参数为引用类型,那么传入的应该是一个指向当前时间的引用,在change(Date)方法里将引用重新指向一个对象,那么我再将其年在1900的基础上加一,那么仍然该输出1901撒,但是,结果却是:Mon Mar 23 23:54:05 CST 2009。原因在哪里?我们分别查看两端代码的change(Date)方法所对应的字节码:

前者的字节码如下:
0 aload_1     // 将位于局部变量表中位置1处的方法的形参date压入栈
1 iconst_1 // 将栈里的年份增量置为1
2 invokevirtual #16 <java/util/Date.setYear> // 直接在date上调用setYear方法
5 return

后者的字节码如下:
0 new #16 <java/util/Date>  //创建一个新的Date对象
3 dup //注意这里的dup,这里表示将会复制一个新的Date引用,指向上一步创建的新的Date对象。
4 invokespecial #18 <java/util/Date.<init>>
7 astore_1
8 aload_1
9 iconst_1
10 invokevirtual #19 <java/util/Date.setYear>
13 return

看到这里,大家都应该明白方法调用在做什么事情了,实际上在参数为引用类型的方法调用中,如果没有对传入的引用使用“=”号进行对象的重新指定,那么传入的参数的确指向的是原来创建的对象,一旦重新进行了赋值,也就是重新指定该引用所指向的对象,那么,此引用就非彼引用了,在date = new Date()以后,原来传入的引用仍然存在,但date引用就已经不再是main方法中传过去的date引用,编译器会复制原来的date引用,即dup操作。然后将引用指向新的Date对象,那么此后change方法中的任何针对date的操作都不是作用于main方法里面的date引用所指向的对象。因为date的名字一致,所以,很多人会产生错觉。

还有需要说明一点:有些兄弟说:“在引用类型方法调用的时候,实际上存在两个指向同一个对象的引用”,这句话也是不完全正确的,在没有改变传入的引用实际的指向的时候,只存在一个引用。

可以理解为,在做了date = new Date()动作以后。方法里面存在两个名字都叫date的引用,一个指向调用者原来的对象,一个指向当前的新对象,而我们操作的就是当前的新对象。

所以,总结一下:
1.在Java中方法调用的时候,既会传值,也会传引用,具体是按什么方式来传递参数,是由参数本身的类型决定的。

2.基本数据类型参数都直接传值,而且这些值都存放在编译好的二进制字节码中,基本数据类型包括:float,double,byte,short,int,long,char.

3.引用数据类型参数都传递引用,在对引用重新指定对象之前,引用都指向调用方法里面所创建的对象,在改变了引用所指的对象以后,这个引用已经完全和原来的引用没有任何关系,从代码的角度,可以认为改变了引用所指对象以后,原来的引用已经被指向当前创建对象的引用自动覆盖。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值