Java值传递和引用传递

简介: 尽管我自认为在C/C++中就已经将这两种传递理解得很透彻了,但是不经意间这错误还是犯得彻彻底底。为此,我总结出这样的一句话: 如果你想修改引用指向的内容,你需要传递引用(地址);如果你想要修改引用本身(地址),那么你需要传递引用(地址)的引用(地址),否则那只是穿上了引用外衣的值传递。

先明晰一下文中值传递和引用传递的含义(关于对引用的定义的争议请参考评论区)。

值传递:方法调用时,实参把它的值传递给对应的形参(或者说副本),方法执行中形式参数值的改变不影响实际参 数的值。
引用传递:也称为传地址。方法调用时,实参的引用(地址,而不是参数的值)被传递给方法中相对应的形参,在方法执行中,对形参的操作实际上就是对实参的操作,方法执行中形式参数值的改变将会影响实际参数的值。

两个看似没什么区别的代码

代码一

public static void main(String[] args) {
    List<Object> objects = null;
    foo(objects);
}

public static void foo(List<Object> objectsCopy) {
    objectsCopy = new ArrayList<>();
    objectsCopy.add(new Object());
    // Bla bla bla
}

代码二

public static void main(String[] args) {
    List<Object> objects = new ArrayList<>();
    foo(objects);
}

public static void foo(List<Object> objectsCopy) {
    objectsCopy.add(new Object());
    // Bla bla bla
}

真的没有区别吗?

以上是我最近写代码时遇到的,当我使用第一种写法的时候,我发现objects 一直是null,略微诧异了一会,我换了第二种写法,问题解决。

老司机可能一看就知道了,这是一个值传递和引用传递的经典问题。

那么为什么第一个不是引用传递?难道List不是引用类型吗?一图胜过千言万语,先来张图解释一下。
Screen_Shot_2017_07_03_at_10_08_06_PM

引用本身以及基本数据类型是存放在栈里的,而引用类型所指向的内容存放在堆内。据此,画出了上图所示内容,objects表示引用本身(堆中的地址,后文地址均表示此意),而content表示引用指向的具体内容(后文也均使用该词表示引用指向的具体内容)。
解释:
代码一中,引用objects,其值为null,所以没有指向任何堆内存;
当我们调用foo方法时,引用objects首先会在foo 方法中被拷贝一份副本objectsCopyobjectsCopy同样也不指向任何堆内容(在foo方法中,所有的操作都是通过objects 的副本来操作的);
foo 方法中objectsCopy = new ArrayList<>(); 语句被调用时,content的地址&content(&在C/C++中表示取地址)就会被写到栈objectsCopy,此时objectsCopy 就指向了content;
可以很清楚的看到,接下来所有的操作都会改变content的内容,但是很遗憾,objects不会有任何改变,始终为null。
Screen_Shot_2017_07_03_at_10_09_34_PM

代码二中,main函数中执行了语句objects = new ArrayList<>();,于是引用objects指向堆中的content
当我们调用foo方法时,引用objects首先会在foo 方法中被拷贝一份副本objectsCopy ,因为objects指向堆中的content,于是objectsCopy指向堆中的content
接下来对objectsCopy 的所有的操作都会改变content的内容,因为objects指向的也是content,所以就改变了objects指向的内容。这就是著名的引用传递。

那么代码一是什么传递?
函数中修改一个存放在栈中的数据,而传递进来的参数是它本身,这是什么传递?或者说函数传了一个引用参数(地址),而现在修改的是引用本身,这是什么传递?
这就是地地道道的、彻头彻尾的goddamn值传递。

仅看表面上传递的是引用类型还是值类型是无法判断这将是值传递还是引用传递,这要取决于你具体的操作是改变引用本身(地址)还是引用指向的内容(content)。
Screen_Shot_2017_07_03_at_10_27_06_PM

尽管在代码一中,我传递的是一个引用类型,但是我修改的是引用本身,所以它是值传递,它真正操作的部分如上图所示;代码二中我修改的是content,但我传递的是content的引用,所以它才是引用传递。
因此,修改A,传递A的引用,这就是引用传递;修改A,传递A,这就是值传递。传递引用类型不是引用传递。

好绕啊。。。C++或许更好理解一些。

修改引用指向的内容(x和y),传递的是地址(指针)int* xint* y(地址可以对应理解为Java中的引用),这就是引用传递。

void swap(int* x, int* y)
{
    int temp = *x;
    *x = *y;
     *y = temp;
}

修改指针,传递的也是指针本身,这就是值传递。

void foo(int* x, int* y)
{
    x = new int(2);
    y = new int(5);
}

尽管我自认为在C/C++中就已经将这两种传递理解得很透彻了,但是不经意间这错误还是犯得彻彻底底。为此,我总结出这样的一句话:

如果你想修改引用指向的内容,你需要传递引用;如果你想要修改引用本身的值,那么你需要传递引用的引用,否则那只是穿上了引用外衣的值传递。

最后再补充一点C++中值传递和引用传递。

值传递

void swap(int x, int y)
{
    int temp = x;
    x = y;
     y = temp;
}

地址传递

void swap(int* x, int* y)
{
    int temp = *x;
    *x = *y;
     *y = temp;
}

引用传递

void swap(int &x, int &y)
{
    int temp = x;
    x = y;
     y = temp;
}

除值传递外,地址和引用传递都会改变x和y的值。

目录
相关文章
|
11月前
|
存储 Java 程序员
Java中相等比较与值传递
Java中相等比较与值传递
71 0
|
3月前
|
Java
java是值传递还是引用传递
本文澄清了Java中参数传递的常见误解,总结出Java采用“值传递”的方式。对于基本类型,传递其值的拷贝,方法内修改不影响原值;而对于对象类型,则传递其引用地址的拷贝,尽管是拷贝,但因指向同一对象,故方法内的修改会影响原对象状态。形参仅在方法内部有效,而实参则是调用方法时传递的具体值。通过示例和比喻(如复刻仓库钥匙),形象地解释了值传递、引用传递及Java特有的“共享对象传递”概念,帮助理解不同情况下参数传递的行为差异。
|
3月前
|
Java
java中的值传递和引用传递
【8月更文挑战第3天】在Java中,值传递用于基本数据类型,传递的是值的副本,因此方法内的修改不影响原值;而引用传递用于对象和数组,虽传递的是引用的副本,但对对象内容的修改会影响原始对象。理解这两者对于正确处理方法调用及参数至关重要。
|
3月前
|
Java
java中的值传递和引用传递
【8月更文挑战第2天】在Java中,基本数据类型如`int`、`double`等采用值传递,传递的是变量值的副本,因此方法内的修改不影响原变量。对象类型则通过引用传递,传递的是对象引用的副本,允许方法内修改原对象。例如,对`StringBuilder`对象的修改会影响原始对象。
|
6月前
|
存储 安全 Java
Java方法的值传递技术详解
Java方法的值传递技术详解
41 3
|
5月前
|
Java
Java的值传递与“引用传递”辨析
Java的值传递与“引用传递”辨析
26 0
|
6月前
|
JavaScript 前端开发 Java
【JAVA面试题】什么是引用传递?什么是值传递?
【JAVA面试题】什么是引用传递?什么是值传递?
|
6月前
|
Java
每日一道Java面试题:Java是值传递还是引用传递?
每日一道Java面试题:Java是值传递还是引用传递?
37 1
|
6月前
|
存储 Java
如何理解Java是按值传递
如何理解Java是按值传递
|
Java
JAVA参数传值机制中值传递和引用传递
JAVA参数传值机制中值传递和引用传递
95 0
下一篇
无影云桌面