为什么说Java不存在引用传递?
在Java语言中,存在两种数据类型,一种是基本类型,如int、byte等8种基本类型,一种是引用类型,如String、Integer等。这两种数据类型区别就在于,基本类型的各个类型大小是固定的,如int类型占4个字节,且数据存储于栈中;而引用类型大小则不固定,根据引用类型的属性构成决定的,且数据存储是在堆中,栈中的变量存储是指向堆中的数据引用;
正是因为有两种数据的存储区别,很多人才容易将值传递和引用传递搞混了。并且容易产生误解,Java数据如果是基本类型就是值传递,如果是对象(引用类型)那就是引用传递,但这是错误的!
值传递和引用传递
在程序语言中是这样定义值传递和引用传递的:
值传递:是在调用函数时将实际参数复制一份到函数中,这样如果对参数进行修改,将不会影响到实际的参数。
引用传递:是指在调用函数时将实际参数地址直接传递到函数中,那么如果函数中对参数进行修改,将影响到实际的参数。
对于上述概念是程序语言中对于值传递和引用传递的通用概念,那我们用代码来验证一下为何说Java是不存在引用传递的。
首先看基本类型的传递
这段代码最后输出是:
这个结果是毫无疑问的,分别输出的pvb是100和1!即使调用了passByValue(int pbV),在方法中改变了pbV的值,但是在main方法中的pbV的值仍然是1,这说明传入passByValue方法的是个拷贝的副本值,在方法中对这个副本值进行修改,不会影响到原变量,证实Java基本类型是值传递!
再换成引用类型的看看:
大家猜猜最后会输出什么......如果按照值传递的理论概念,传递给函数的变量应该是拷贝的副本,无论在方法函数中做出什么样的修改,都不会影响到原值,所以按照值传递是不是应该输出两个“Java”字符串?!
但事实上最后输出结果却是“Java”和“Java+python”两个字符串:
难道Java是存在引用传递的?
答案是不存在的!我们来分析上面的程序在内存中的存储:
首先在main方法中创建一个StringBuffter对象,此时buffter变量引用此对象的地址,然后调用passByReference方法,此时,将buffter变量引用的地址拷贝一份副本传入passByReference方法,在passByReference中调用引用对象的append(“+python”)方法,此对象变成了”Java+python”字符串,最后输出main方法输出buffer变量所指向的对象,即”Java+python”字符串;
话说回来,将引用拷贝一份副本传至方法参数中,难道这不算引用传递吗?当然不算!
我们再来读一下引用传递的概念定义:引用传递是指在调用函数时将实际参数地址直接传递到函数中,那么如果函数中对参数进行修改,将影响到实际的参数。
事实上,在Java语言中,我们是不能直接获取到实际参数的地址的,我们所使用的都是引用去操作其指向的地址的对象,引用≠实际参数地址,所以在Java语言中不存在引用传递。只是因为在传递引用类型的对象时,拷贝对应的引用值副本进行传参,很多人误以为这就是引用传递,但这其实是值传递。
结论
在Java语言中无论是基本数据类型还是引用类型,都是使用值传递的方式,对于引用传递来说是不存在的。
那最后上一个“硬菜”,下面的这段代码为什么会输出两个一模一样的"Java"字符串呢!如果了解String类型底层的朋友,答案一眼就能看出了,欢迎评论留言。
public class MemberServiceImpl{
void test (int i){
i=3;
}
void testObj (Test test){
test=new Test();
System.out.println(test.getI());
}
public static void main(String[] args) {
MemberServiceImpl memberService=new MemberServiceImpl();
int i=0;
memberService.test(i);
System.out.println("i:"+i);
Test test=new Test();
test.setI(111);
memberService.testObj(test);
System.out.println(test.getI());
}
}
运行结果:
i:0
null
111(之所以不是null是因为,testObj方法内外的引用不是同一个,如果不修改引用指向的对象,则操作的是同一个对象)