资讯专栏INFORMATION COLUMN

【Java深入学习系列】之值传递Or引用传递?

马忠志 / 1747人阅读

摘要:我们在处新创建了一个并将其引用在处传给了方法的参数该方法内部引用在处被重新赋值。如果是引用传递,那么引用在处已经被指向了新的输出应该为才对,事实上是怎样的呢事实上输出了也就是说方法改变了传入引用所指对象的值。此处注意,并非将重新分配,而是。

我们来看一个新手甚至写了多年Java的朋友都可能不是十分确定的问题:

在Java方法传参时,究竟是引用传递还是值传递?

为了说明问题, 我给出一个非常简单的class定义:

public class Foo {
  String attribute;
  Foo(String s) {
    this.attribute = s;
  }
  void setAttribute(String s) {
    this.attribute = s;
  }
  String getAttribute() {
    return this.attribute;
  }
}

下面在阐明观点时,可能会多次用到该类。

关于Java里值传递还是引用传递,至少从表现形式上来看,两种观点都有支撑的论据。下面我来一一分析:

观点1:引用传递

理由如下:
先看一段代码

public class Main {
  public static void modifyReference(Foo c){
    c.setAttribute("c"); // line DDD
  }

  public static void main(String[] args) {
    Foo fooRef = new Foo("a"); // line AAA
    modifyReference(fooRef); // line BBB
    System.out.println(fooRef.getAttribute()); // 输出 c
  }
}

上述示例,输出结果为"c",而不是"a", 也就是传入的fooRef里的属性被修改了,发生了side-effect。

我们在line AAA处新创建了一个Object Foo并将其引用fooRefline BBB处传给了方法modifyReference()的参数cRef, 该方法内部处理后,fooRef指向的Object中的值从"a"变成了"c", 而引用fooRef还是那个引用, 因此,我们是否可以认为,在line BBB处发生了引用传递?

先留着疑问,我们继续往下看。

观点2:值传递

继续看一段代码

public class Main {
  public static void changeReference(Foo aRef){
    Foo bRef = new Foo("b");
    aRef = bRef;   // line EEE
  }
  
  public static void main(String[] args) {
    Foo fooRef = new Foo("a"); // line AAA
    changeReference(fooRef); // line BBB
    System.out.println(fooRef.getAttribute()); // 输出 a
  }
}

上述示例,输出结果为"a", 而不是"b", 即对传入的fooRef内部的change并没有影响外部的传入前的值。

我们在line AAA处新创建了一个Object Foo并将其引用fooRefline EEE处传给了方法changeReference()的参数aRef, 该方法内部引用aRefline DDD处被重新赋值。如果是引用传递,那么引用aRefline EEE处已经被指向了新的Object, 输出应该为"b"才对,事实上是怎样的呢?事实上输出了"a",也就是说changeReference()方法改变了传入引用所指对象的值。

观点1和观点2的输出结果多少会让人有些困惑,别急,我们继续往下看。

深入分析

为了详细分析这个问题,把上述两段代码合起来:

public class Main {
  public static void modifyReference(Foo cRef){
    cRef.setAttribute("c"); // line DDD
  }
  public static void changeReference(Foo aRef){
    Foo bRef = new Foo("b"); // line FFF
    aRef = bRef;   // line EEE
  }
  
  public static void main(String[] args) {
    Foo fooRef = new Foo("a"); // line AAA
    changeReference(fooRef); // line BBB
    System.out.println(fooRef.getAttribute()); // 输出 a
    
    modifyReference(fooRef); // line CCC
    System.out.println(fooRef.getAttribute()); // 输出 c
    

  }
}

下面来深入内部来详细分析一下引用和Object内部的变化。
来看下面图示:

① Line AAA, 申明一个名叫fooRef,类型为Foo的引用,并见其分配给一个新的包含属性值为"f"的对象,该对象类型为Foo
Foo fooRef = new Foo("a"); // line AAA

② Line DDD, 方法内部,申明了一个Foo类型的名为aRef的引用,且aRef被初始化为null
void changeReference(Foo a);

③ Line CCC, changeReference()方法被调用后,引用aRef被分配给fooRef指向的对象。
changeReference(fooRef);

④ Line FFF, 申明一个名叫bRef,类型为Foo的引用,并见其分配给一个新的包含属性值为"b"的对象,该对象类型为Foo
Foo bRef = new Foo("b");

⑤ Line EEE, 将引用aRef重新分配给了包含属性"b"的对象。此处注意,并非将fooRef重新分配,而是aRef
aRef = bRef;

⑥ Line CCC, 调用方法modifyReference(Foo cRef)后,新建了一个引用cRef并将之分配到包含该属性"f"的对象上,该对象同时被两个引用fooRefcRef指向着。
modifyReference(fooRef);

⑦ Line DDD, cRef.setAttribute("c");将会改变cRef引用指向的包含属性"f"的对象,而该对象同时被引用fooRef指向着。
cRef.setAttribute("c");

此时引用fooRef指向的对象内部属性值"f"也被重新设置为"c"

总结

Java内部方法传参不是引用传递,而是引用本身的"值"的传递,归根结底还是值传递。将一个对象的引用fooRef传给方法的形参newRef,将给该对象新增了一个引用,相当于多了一个alias。我们可以通过这个原引用fooRef,或这是方法参数里的新引用newRef去访问、操作原对象,也可以改变参数里的引用newRef本身的值,却无法改变原引用fooRef的值。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66038.html

相关文章

  • 数据结构 - 收藏集 - 掘金

    面试旧敌之红黑树(直白介绍深入理解) - Android - 掘金 读完本文你将了解到: 什么是红黑树 黑色高度 红黑树的 5 个特性 红黑树的左旋右旋 指定节点 x 的左旋 右图转成左图 指定节点 y 的右旋左图转成右图 红黑树的平衡插入 二叉查找树的插入 插入后调整红黑树结构 调整思想 插入染红后... java 多线程同步以及线程间通信详解 & 消费者生产者模式 & 死锁 & Thread...

    leeon 评论0 收藏0
  • JavaScript 是如何工作的:JavaScript 的共享传递和按值传递

    摘要:它对数组和对象使用按值传递,但这是在的共享传参或拷贝的引用中使用的按值传参。例如在这里,变量和值在执行期间存储在堆栈中。返回值这是可选的,函数可以返回值,也可以不返回值。变量被推入堆栈,从而在执行时成为的副本。 这是专门探索 JavaScript 及其所构建的组件的系列文章的第 22 篇。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 如果你错过了前面的章节,可...

    keithyau 评论0 收藏0
  • JavaScript 是如何工作的:JavaScript 的共享传递和按值传递

    摘要:它对数组和对象使用按值传递,但这是在的共享传参或拷贝的引用中使用的按值传参。例如在这里,变量和值在执行期间存储在堆栈中。返回值这是可选的,函数可以返回值,也可以不返回值。变量被推入堆栈,从而在执行时成为的副本。 这是专门探索 JavaScript 及其所构建的组件的系列文章的第 22 篇。 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 如果你错过了前面的章节,可...

    陈伟 评论0 收藏0
  • JS程序

    摘要:设计模式是以面向对象编程为基础的,的面向对象编程和传统的的面向对象编程有些差别,这让我一开始接触的时候感到十分痛苦,但是这只能靠自己慢慢积累慢慢思考。想继续了解设计模式必须要先搞懂面向对象编程,否则只会让你自己更痛苦。 JavaScript 中的构造函数 学习总结。知识只有分享才有存在的意义。 是时候替换你的 for 循环大法了~ 《小分享》JavaScript中数组的那些迭代方法~ ...

    melody_lql 评论0 收藏0
  • Java向上转型及内存分析

    摘要:但有时候,当我们的代码只需要与父类打交道时,可以使用向上转型,来使我们的代码不依赖具体子类,比如以下代码,方法可以接受类的任意子类内存分析我们来分析以下转型代码在内存中的表示 学习设计模式的时候,发现很多模式都用到了向上转型(eg. 工厂方法)。而我对向上转型(upcasting)的机制并不十分熟悉。这篇文章将深入分析向上转型的机制、内存分析。 概念 先从几个基本概念开始: 1. Ja...

    Zachary 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<