精华内容
下载资源
问答
  • 引用传递
    千次阅读
    2021-03-14 19:09:29

    本问题已经有最佳答案,请猛点这里访问。

    Possible Duplicate:

    Is Java"pass-by-reference"?

    数组不是Java中的原始类型,但它们也不是对象,所以它们是通过值传递还是通过引用传递?它是否依赖于数组包含的内容,例如引用或基元类型?

    数组是对象,是的,但是Java中的任何东西都不是通过引用传递的。所有参数传递都是按值传递的。在对象的情况下,传递的是对该对象(即指针)的按值引用。按值传递引用与按引用传递不同。

    您可能会发现这很有用:stackoverflow.com/a/9404727/597657

    我不能为这个问题添加答案,但是我写了一个代码片段,它可能有助于理解下面的答案:write.as/1wjcm7m50w41k.md。

    Everything in Java are passed-by value.。对于数组(它只是一个对象),数组引用按值传递。(就像对象引用是通过值传递的一样)。

    将数组传递给其他方法时,实际上会复制对该数组的引用。

    通过该引用对数组内容所做的任何更改都将影响原始数组。

    但是,将引用更改为指向新数组不会更改原始方法中的现有引用。

    看到这个帖子…

    Java是"通过引用"还是"按值传递"?

    见本工作示例:

    public static void changeContent(int[] arr) {

    // If we change the content of arr.

    arr[0] = 10;  // Will change the content of array in main()

    }

    public static void changeRef(int[] arr) {

    // If we change the reference

    arr = new int[2];  // Will not change the array in main()

    arr[0] = 15;

    }

    public static void main(String[] args) {

    int [] arr = new int[2];

    arr[0] = 4;

    arr[1] = 5;

    changeContent(arr);

    System.out.println(arr[0]);  // Will print 10..

    changeRef(arr);

    System.out.println(arr[0]);  // Will still print 10..

    // Change the reference doesn't reflect change here..

    }

    这是我最有用的答案。谢谢伙伴!

    这两点正是我想要的。TY)

    @编码乐趣这是最有用的回答周期,不仅仅是为你。为什么选择的答案不适合我。

    你的问题是基于一个错误的前提。

    Arrays are not a primitive type in Java, but they are not objects either ..."

    事实上,Java中的所有数组都是Objs1。每一个Java数组类型都有EDOCX1×1作为其超类型,并且继承了EDCOX1、2、API中所有方法的实现。

    与所有Java对象一样,数组也是按值传递的…但该值是对数组的引用。

    实际的引用传递涉及传递变量的地址,以便可以更新变量。这不是在Java中传递数组时发生的情况。

    这里有一些链接可以解释"通过引用"和"通过值"之间的区别:

    http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/topic/com.ibm.xlcpp8a.doc/language/ref/cplr233.htm

    http://www. cf.EdU/~梅尔斯/c++/Notes/参考文献.HTML

    相关销售代表问题:

    Java是"通过引用"还是"按值传递"?

    历史背景:

    短语"pass by reference"最初是"call by reference",用来区分fortran(call by reference)和algol-60(call by value和call by name)的参数传递语义。

    在按值调用中,参数表达式被计算为一个值,该值被复制到被调用的方法中。

    在引用调用中,参数表达式部分计算为传递给调用方法的"lvalue"(即变量或数组元素的地址)。然后,调用方法可以直接读取和更新变量/元素。

    在按名称调用中,实际参数表达式将传递给调用方法(!!)可以多次评估(!!!!)。实现起来很复杂,可以用来(滥用)编写非常难以理解的代码。顾名思义,call-by-name只在algol-60中使用过(谢天谢地!).

    更新

    实际上,algol-60的名称调用类似于将lambda表达式作为参数传递。问题在于,这些不完全是lambda表达式(它们在实现级别被称为"thunk")可以间接地修改调用过程/函数范围内的变量的状态。这是让他们很难理解的原因之一。(例如,参见Jensen设备上的维基百科页面。)

    1。链接的Q&A(Java中的数组以及它们如何存储在内存中)中没有任何状态或暗示数组不是对象。

    所以…如果在方法中更改数组(作为参数传入),是否在调用者中更改原始数组中的值?

    对。没错。

    像所有Java对象一样,数组是按值传递的…但该值是对数组的引用。"那肯定不是通过值传递的。"我的意思是当然会传递一个值。

    它>>是<

    数组实际上是对象,因此会传递一个引用(引用本身是按值传递的,还不清楚?).快速示例:

    // assuming you allocated the list

    public void addItem(Integer[] list, int item) {

    list[1] = item;

    }

    您将看到调用代码对列表的更改。但是,不能更改引用本身,因为它是通过值传递的:

    // assuming you allocated the list

    public void changeArray(Integer[] list) {

    list = null;

    }

    如果传递一个非空列表,则在该方法返回时,它不会为空。

    不,爪哇的一切都是通过价值传递的!在Java中不存在传递引用,因为它不存在于ANSI C中,这就是指针存在的原因。

    Aeloo:我说过一个引用被传递给方法,否则你看不到改变,而不是Java通过引用!是的,引用是通过值传递的,但这不是点。

    @你的句子不清楚…

    @好吧,我又加了一些评论…

    "但是您不能更改引用本身,因为它是通过值传递的"——实际上,您可以(在本地)更改引用。您不能更改的是在调用上下文中从何处获取引用的变量。只有当人们混淆了引用和保存引用的变量时,这才是令人困惑的。

    不,那是错误的。数组是Java中的特殊对象。因此,这就像传递其他对象一样,在其中传递引用的值,而不是引用本身。也就是说,更改被调用例程中数组的引用不会反映在调用例程中。

    谢谢。那么每个数组访问都必须取消引用吗?这是否意味着使用数组与在爪哇中使用任何其他类型的列表一样慢,除了可以在其中存储基元类型之外,这些类型不需要被取消引用吗?

    不,因为存储在堆中的数据中是连续的,这意味着迭代查找在CPU时间方面要便宜得多。列表不保证连续存储。

    Java中的所有东西都是通过值传递的。

    在数组的情况下,引用被复制到新的引用中,但是请记住Java中的所有东西都是通过值传递的。

    请看这篇有趣的文章了解更多信息…

    数组的最终讨论在http://docs.oracle.com/javase/specs/jls/se5.0/html/arrays.html 27803上。这清楚地表明Java数组是对象。这些对象的类别在10.8中定义。

    语言规范的第8.4.1节,http://docs.oracle.com/javase/specs/jls/se5.0/html/classes.html 40420,描述了如何将参数传递给方法。由于Java语法是从C和C++派生的,所以行为类似。原语类型是按值传递的,与C一样。当传递对象时,对象引用(指针)是按值传递的,这反映了按值传递指针的C语法。参见4.3.1,http://docs.oracle.com/javase/specs/jls/se5.0/html/typesvalues.html 4.3,

    实际上,这意味着修改方法中数组的内容会反映在调用范围中的数组对象中,但将新值重新分配给方法中的引用对调用范围中的引用没有影响,这正是C中的结构指针或I中的对象指针所期望的行为。nc++。

    术语上的混淆至少有一部分源于高级语言的历史,在C的普遍使用之前。在以前流行的高级语言中,通过地址直接引用内存是应该尽可能避免的事情,并且它被认为是语言提供一个抽象层的工作。这使得语言必须显式支持从子例程(不一定是函数)返回值的机制。这种机制在正式意义上是指"通过引用"。

    当引入C时,它附带了一个简化的过程调用概念,其中所有参数都是仅输入的,返回给调用方的唯一值是函数结果。但是,传递引用的目的可以通过显式和广泛使用指针来实现。由于它具有相同的用途,将指针作为对值的引用传递的实践通常被通俗地称为传递引用。如果例程调用的语义要求通过引用传递参数,则C的语法要求程序员显式传递指针。按值传递指针是在C中实现按引用传递语义的设计模式。

    由于在C中的原始指针似乎唯一的目的是创建崩溃的bug,随后的开发,尤其是Java,已经试图返回更安全的手段来传递参数。然而,C的主导地位使得开发人员有义务模仿C编码的常见风格。结果是引用被类似地传递给指针,但是被使用更多的保护来实现,以使它们更安全。另一种选择可能是像艾达这样的语言的丰富语法,但是这会呈现出一种不受欢迎的学习曲线,并减少了Java的可能采用。

    简而言之,在Java中对对象(包括数组)进行参数传递的设计是专门服务于通过引用的语义意图,但是用以值传递引用的语法来实现。

    因为Java语法是从C和C++派生的,所以行为类似。相似的语法并不意味着相似的语义。

    我引用了旧的规范,因为它仍然是正确的,我不知道OP使用的是哪个版本。参数传递在8.4.1中描述如下:当调用方法或构造函数(&167;15.12)时,实际参数表达式的值在方法或构造函数主体执行之前初始化新创建的参数变量(每个声明的类型)。出现在声明符ID中的标识符可以用作方法或构造函数主体中引用形参的简单名称。

    关于SyAc税,Java、C和C++之间的并行并不是偶然的,该设计旨在简化C和C++程序员的转换。语言设计是人类交流的问题,而不是数学上的严格性,将熟悉的语法和不熟悉的语义混合在一起会造成不适当的复杂性。我们正在努力建立一个易于采用的系统。

    你没明白我的意思。我确信您知道两种相关语言有相同的语法,但语义不同的情况。我要说的是,无论语言是否相关,相同的语法并不意味着相同的语义。

    此外,讨论"通过引用的语义意图"是对FORTRAN、C、C++等50%个使用情况下的使用意图的假设进行了假设。例如,swap(int &a, int &b)方法。记住,经典Fortran中的引用调用不涉及指针及其风险。(你甚至可以说,C根本不通过引用来调用。它正在做的是显式地创建传递指针…按价值…必须以某种方式使用,以避免"未指明的行为"。)

    @实际上,我很难想到一个例子,在这个例子中,相似的语法并不能反映出实现相似语义的尝试。你能举个例子吗?

    这是一个显而易见的例子。Fortran和Algol60和C对方法/函数/过程调用使用相同的语法。name(expr, expr, expr)。但是在每种情况下,传递语义的参数是不同的。另一个明显的例子是在C和Java中EDCOX1和1。现在这是一个很好的区别…但它表明了由于语言之间的关系越来越密切,我试图更清楚地阐明这一点。

    第三个例子是在C与Java中EDCOX1的2含义相当不同的含义。

    我认为实际上,对于Fortran和C.Fortran来说,使用引用调用对于不公开原始指针是很重要的(尽管它确实使动态内存分配变得不切实际)。最初的C编译器被设计为非常轻量的,因此语言不实现引用调用,指针填补了空白。您提到的用例也反映了C的低级特性,因为Fortran中的此类例程可能需要内联汇编。对于我的经验中的应用程序开发人员来说,这不是最常见的用例。

    我知道你来自Fortan,Algol和C,但是这些语言的语法并不像Java和C++那样狡猾。也许我有点接近历史,不认为这是显而易见的。

    我可以在经典Fortran IV中的标量变量上轻松地实现swap。除非我记错了。

    让我们在聊天中继续讨论

    有点诡计房地产…即使引用通过Java中的值传递,因此对引用本身的更改也在调用的函数级范围内。编译器和/或JVM通常会将值类型转换为引用。

    更多相关内容
  • 值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参的副本。修改形参的值并不会影响实参。  按引用传递(call by reference)时,函数的形参接收实参的隐式引用,而不再是副本。这意味着函数...
  • python中函数参数是引用传递(不是值传递)。对于不可变类型,因变量不能被修改,所以运算时不会影响到变量本身;而对于可变类型来说,函数体中的运算有可能会更改传入的参数变量. 形参: 函数需要传递的参数 实参...
  • 在传统的观念里,都认为JavaScript函数传递的是引用传递(也称之为指针传递),也有人认为是值传递引用传递都具备。那么JS的参数传递到底是怎么回事呢?事实上以下的演示也完全可以用于Java 首先来一个比较简单的,...
  • 如果函数收到的是一个可变对象(比如字典 或者列表)的引用,就能修改对象的原始——相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能 直接修改原始对象...
  • 另一方面,值传递由于形参和实参内存地址不一样,有些功能并不能实现(swap()),因此,除了极为简单的函数会使用值传递外,大多数会采用引用传递。而对于指针传递,则在涉及数组的情况下使用较多,因为指针本身会给...
  • java 中没有引用传递,都是值传递的,可以通过传递副本修改对象的,副本交换,并不影响原引用
  • 本篇文章主要是对C#中的引用传递与值传递进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助
  • Java:按值传递还是按引用传递详细解说
  • 这些基本类型在赋值的时候是通过值传递的方式。值得注意的是还有另外三种类型: Array、Function和Object,它们通过引用来传递。从底层技术上看,它们三都是对象。 基本数据类型 如果一个基本的数据类型绑定到某个...
  • 主要介绍了java到底是值传递还是引用传递的相关知识,本文通过几个例子给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
  • 当一个变量为一个参数传入方法内部的时候,会有两种不同的传递方式:值传递引用传递。  值传递的特点:不管方法内部对传进去的参数作任何改变,也不会影响方法外部的变量的值  引用传递的特点:方法内部对传...
  • 有说有值传递引用传递两种,也有说只有值传递的,这里只说下个人见解  先看一个例子 public class Test1 { public static void main(String[] args) { int a = 10; changeNum(a); System.out.println("main...
  • #值传递引用传递 python中数据类型有:整形,字条串,元组,列表,字典,集合 不可变数据类型:当该数据类型对应变量的值变化,值对应的内存地址 也发生变化,这个值就为不可变数据类型 可变数据类型:当该数据...
  • 主要介绍了java通过实例了解值传递引用传递,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • Java值传递引用传递的区别

    千次阅读 多人点赞 2021-09-09 22:55:57
    文章目录Java值传递引用传递前景实参与形参值传递引用传递Java中的值传递总结 前景 关于这个问题,引发过很多广泛的讨论,看来很多程序员对于这个问题的理解都不尽相同,甚至很多人理解的是错误的。还有的人可能...

    Java值传递与引用传递

    前景

    关于这个问题,引发过很多广泛的讨论,看来很多程序员对于这个问题的理解都不尽相同,甚至很多人理解的是错误的。还有的人可能知道Java中的参数传递是值传递,但是说不出来为什么。
    在开始深入讲解之前,有必要纠正一下大家以前的那些错误看法了。如果你有以下想法,那么你有必要好好阅读本文。

    • 错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
    • 错误理解二: Java是引用传递。
    • 错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。

    实参与形参

    我们都知道,在Java中定义方法的时候是可以定义参数的。比如Java中的main方法, public static void main(String[ ] args),这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。
    形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
    实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
    简单举个例子:

    public static void main( String[ ] args) {
    ParamTest pt = new ParamTest();
    pt.sout( "Hollis");//实际参数为Hollis
    }
    public void sout( String name) {/!形式参数为name
    system.out.println(name);
    }
    
    

    实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。

    值传递与引用传递

    上面提到了,当我们调用一个有参函数的时候,会把实际参数传递给形式参数。但是,在程序语言中,这个传递过程中传递的两种情况,即值传递和引用传递。我们来看下程序语言中是如何定义和区分值传递和引用传递的。

    值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
    引用传递是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

    有了上面的概念,然后大家就可以写代码实践了,来看看Java中到底是值传递还是引用传递,于是,最简单的一段代码出来了:

    public static void main( String[] args) {
         ParamTest pt = new ParamTest();
        int i = 10;
        pt.pass(i);
        System.out.println( "print in main , i is " +i);
    }
    public void pass(int j){
        j = 20;
        system.out.println( "print in pass , j is " + j);
    }
    
    

    上面的代码中,我们在pass方法中修改了参数j的值,然后分别在pass方法和main方法中打印参数的值。输出结果如下:

    print in pass , j is 20
    print in main , i is 10
    

    可见,pass方法内部对i的值的修改并没有改变实际参数i的值。那么,按照上面的定义,有人得到结论: Java的方法传递是值传递。
    但是,很快就有人提出质疑了(哈哈,所以,不要轻易下结论咯。)。然后,他们会搬出以下代码:

    public static void main(String[ ] args) {
        ParamTest pt = new ParamTest();
        User hollis = new User();
        hollis.setName( "Hollis");
        hollis.setGender("Male");
        pt.pass(hollis);
        system.out.println( "print in main , user is " + hollis);
    }
    public void pass(User user) {
        user.setName( "hollischuang");
        System.out.println( "print in pass , user is " + user);
    }
    

    同样是一个pass方法,同样是在pass方法内修改参数的值。输出结果如下:

    print in pass , user is User{name='hollischuang', gender='Male '}
    print in main , user is User{name='hollischuang' , gender='Male '}
    

    经过pass方法执行后,实参的值竟然被改变了,那按照上面的引用传递的定义,实际参数的值被改变了,这不就是引用传递了么。于是,根据上面的两段代码,有人得出一个新的结论:Java的方法中,在传递普通类型的时候是值传递,在传递对象类型的时候是引用传递。
    但是,这种表述仍然是错误的。不信你看下面这个参数类型为对象的参数传递:

    public static void main( string[] args) {
        ParamTest pt = new ParamTest();
        string name = "Hollis";
        pt.pass(name ) ;
        System.out.println( "print in main , name is " + name);
    }
    public void pass(string name) {
        name = "hollischuang";
        system.out.println( "print in pass , name is " + name);
    }
    
    
    

    上面的代码输出结果为

    print in pass , name is hollischuangprint in main , name is Hollis
    

    这又作何解释呢?同样传递了一个对象,但是原始参数的值并没有被修改,难道传递对象又变成值传递了?

    Java中的值传递

    上面,我们举了三个例子,表现的结果却不一样,这也是导致很多初学者,甚至很多高级程序员对于Java的传递类型有困惑的原因。其实,我想告诉大家的是,上面的概念没有错,只是代码的例子有问题。来,我再来给大家画—下概念中的重点,然后再举几个真正恰当的例子。

    值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
    引用传递是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

    那么,我来给大家总结一下,值传递和引用传递之前的区别的重点是什么。

    值传递引用传递
    根本区别会创建副本 copy不创建副本
    所有函数中无法改变原始对象函数中可以改变原始对象

    我们上面看过的几个pass的例子中,都只关注了实际参数内容是否有改变。如传递的是User对象,我们试着改变他的name属性的值,然后检查是否有改变。其实,在实验方法上就错了,当然得到的结论也就有问题了。
    为什么说实验方法错了呢?这里我们来举一个形象的例子。再来深入理解一下值传递和引用传递,然后你就知道为啥错了。
    你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
    你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。
    但是,不管上面那种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。那你说你会不会受到影响?而我们在pass方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。
    还拿上面的一个例子来举例,我们真正的改变参数,看看会发生什么?

    public static void main(String[ ] args){
        ParamTest pt = new ParamTest();
        User hollis = new User();
        hollis.setName( "Hollis");
        hollis.setGender("Male" );
        pt.pass(hollis);
        system.out.println("print in main , user is " + hollis);
        public void pass(User user) {
            user = new User();
            user.setName( "hollischuang");
            user.setGender( "Male");
            system.out.println( "print in pass , user is " + user);
    
    

    上面的代码中,我们在pass方法中,改变了user对象,输出结果如下:

    print in pass , user is User{name='hollischuang ' , gender='Male '}
    print in main , user is User{name='Hollis', gender= 'Male '}
    

    我们来画一张图,看一下整个过程中发生了什么,然后我再告诉你,为啥Java中只有值传递。

    稍微解释下这张图,当我们在main中创建一个User对象的时候,在堆中开辟一块内存,其中保存了name和gender等数据。然后hollis持有该内存的地址ex123456(图1)。当尝试调用pass方法,并且hollis作为实际参数传递给形式参数user的时候,会把这个地址ex123456交给user,这时,user也指向了这个地址(图2)。然后在pass方法内对参数进行修改的时候,即user = newUser();,会重新开辟一块 eX456789的内存,赋值给user。后面对user的任何修改都不会改变内存eX123456的内容(图3)。
    上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在user=new User()的时候,实际参数的引用也应该改为指向eX456789,但是实际上并没有。
    通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以,上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数。
    我们再来回顾下之前的那个“砸电视”的例子,看那个例子中的传递过程发生了什么。

    同样的,在参数传递的过程中,实际参数的地址eX1213456被拷贝给了形参,只是,在这个方法中,并没有对形参本身进行修改,而是修改的形参持有的地址中存储的内容。
    所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。就像钥匙和房子的关系。
    那么,既然这样,为啥上面同样是传递对象,传递的String对象和User对象的表现结果不一样呢?我们在pass方法中使用name = “hollischuang”;试着去更改name的值,阴差阳错的直接改变了name的引用的地址。因为这段代码,会new一个String,在把引用交给name,即等价于name =new String(“hollischuang”);。而原来的那个”Hollis”字符串还是由实参持有着的,所以,并没有修改到实际参数的值。

    所以说,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。

    总结

    无论是值传递还是引用传递,其实都是一种求值策略(Evaluation strategy)。在求值策略中,还有一种叫做按共享传递。其实Java中的参数传递严格意义上说应该是按共享传递。

    按共享传递,是指在调用函数时,传递给函数的是实参的地址的拷贝(如果实参在栈中,则直接拷贝该值)。在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作。如果该值在栈中,那么因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。

    简单点说,Java中的传递,是值传递,而这个值,实际上是对象的引用。
    而按共享传递其实只是按值传递的一个特例罢了。所以我们可以说Java的传递是按共享传递,或者说Java中的传递是值传递。

    所以函数内部对参数进行操作不会对外部变量产生影响。如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。

    简单点说,Java中的传递,是值传递,而这个值,实际上是对象的引用。
    而按共享传递其实只是按值传递的一个特例罢了。所以我们可以说Java的传递是按共享传递,或者说Java中的传递是值传递。

    //本文借鉴于知乎Holis先生

    展开全文
  • 主要介绍了 Java 值传递引用传递详解及实例代码的相关资料,需要的朋友可以参考下
  • 值传递还是引用传递

    Java是值传递还是引用传递

    一、概述

    在 Java 语言中,本质只有值传递,而无引用传递。

    二、值类型和引用类型

    值类型就是Java 中的 8 大基础数据类型:

    • 整数型:byte、short、int、long
    • 浮点型:float、double
    • 字符类型:char
    • 布尔类型:boolean

    所谓的值类型指的是在赋值时,直接在栈中(Java 虚拟机栈)生成值的类型。

    引用类型是指除值类型之外的数据类型,比如:

    • 类、接口
    • 数组
    • 字符串
    • 包装类(Integer、Double…)

    所谓的引用类型是指,在初始化时将引用生成栈上,而值生成在堆上的这些数据类型。

    三、值传递和引用传递

    1、值传递(Pass By Value)指的是方法传参时,传递的是原内容的副本,因此对副本进行如何修改都不会影响原内容。

    public class PassValueTest {
        public static void main(String[] args) {
            int age = 18;
            System.out.println("调用方法before:" + age);
            intTest(age);
            System.out.println("调用方法after:" + age);
        }
    
        private static void intValueTest(int age) {
            age = 33;
            System.out.println("方法中修改:" + age);
        }
    }
    //运行结果///
    调用方法before:18
    
    方法中修改:33
    
    调用方法after:18
    

    在方法中修改参数并未影响原内容,我们把这种传参方式称之为值传递。

    2、引用传递(Pass By Reference)指的是方法传参时,传递的是参数本身,因此对参数进行任意修改都会影响原内容。

    /**“引用传递”*/
    public class PassByReferenceValue {
        public static void main(String[] args) {
            char[] name = {'宇', '哥'};
            System.out.println("调用方法before:" + new String(name));
            paramReferenceTest(name);
            System.out.println("调用方法after:" + new String(name));
        }
        private static void paramReferenceTest(char[] n) {
             n[1] = '神';
            System.out.println("方法中修改为:" + new String(n));
        }
    }
    
    //运行结果///
    调用方法before:宇哥
    
    方法中修改为:宇神
    
    调用方法after:宇神
    
    
    
    
    public class PassByReferenceValue {
        public static void main(String[] args) {
            char[] name = {'宇', '哥'};
            System.out.println("调用方法before:" + new String(name));
            paramReferenceTest(name);
            System.out.println("调用方法after:" + new String(name));
        }
        private static void paramReferenceTest(char[] n) {
            n = new char[2]; // 添加此行代码
            n[1] = '神';
            System.out.println("方法中修改为:" + new String(n));
        }
    }
    
    //运行结果///
    调用方法before:宇哥
    
    方法中修改为:神
    
    调用方法after:宇哥
    
    

    当我们在 paramReferenceTest方法中添加 new char[] 之后,“引用传递”就突然变值传递了?为什么?

    这是因为,在 Java 语言中本质上只有值传递,也就说 Java 的传参只会传递它的副本,并不会传递参数本身。

    前面那个带引号的“引用传递”其实只是传递了它的引用副本,

    四、总结;

    在 Java 语言中只有值传递,方法传参时只会传递副本信息而非原内容。我们还知道了基础数据类型会直接生成到栈上,而对象或数组则会在栈和堆上都生成信息,并将栈上生成的引用,直接指向堆中生成的数据。

    展开全文
  • Java 是值传递还是引用传递

    万次阅读 多人点赞 2019-03-20 02:40:16
    最近整理面试题,整理到值传递引用传递,到网上搜了一圈,争议很大。带着一脸蒙圈,线上线下查了好多资料。最终有所收获,所以分享给大家,希望能对你有所帮助。 首先说下我的感受,这个题目出的很好,但是在 ...

        最近整理面试题,整理到值传递、引用传递,到网上搜了一圈,争议很大。带着一脸蒙圈,线上线下查了好多资料。最终有所收获,所以分享给大家,希望能对你有所帮助。
        首先说下我的感受,这个题目出的很好,但是在 Java 中这个题目是有问题的(在下面我会解释)。并且,有很多结论是 Java 中只有 值传递。我认为这样说不够严谨。当然如果针对 Java 语言本身来讲,Java 中只有 值传递,没有引用传递,是正确的。但是如果针对 值传递,引用传递定义来说,Java 中还是有引用传递的。下面来分析:

    一、值传递、引用传递定义


        在深入分析问题之前,先让初问者简单明白一下什么是值传递,引用传递。我先用 Java 代码解释:

    public class StringBase {
    
        public static void main(String[] args) {
            int c = 66; //c 叫做实参
            String d = "hello"; //d 叫做实参
    
            StringBase stringBase = new StringBase();
            stringBase.test5(c, d); // 此处 c 与 d 叫做实参
    
            System.out.println("c的值是:" + c + " --- d的值是:" + d);
        }
        
        public void test5(int a, String b) { // a 与 b 叫做形参
            a = 55;
            b = "no";
        }
    }


    【运行结果】
    c的值是:66 --- d的值是:hello

    可以看出通过方法传递后,int 类型与 String 类型的原值并没有受到前面 test5 方法执行后的影响,还是输出了原值。这种形为通常被说成值传递。如果原值经过 test5 方法后被改变了,这种形为通常被描述为引用传递

    定义

        值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
        引用传递:是指在调用函数时将实际参数的地址直接传递到函数中(的形参),那么在函数中对参数所进行的修改,将影响到实际参数。
        引用传递:形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。(下面文章中 C++ 的定义,我觉得这样说更精简形象一些,所以放了两个定义,其实意思是一样的)

        以上,就是相关的定义,大家对这个定义几乎没有分歧,但是我建议大家,有必要去看看 C++ 中 值传递、引用传递的定义。因为在 C++ 中有三个定义:值传递、引用传递、指针传递,推荐一个地址: C++ 值传递、指针传递、引用传递详解

    //引用传递
    void change2(int &n) {
        cout << "引用传递--函数操作地址" << &n << endl;
        n++;
    }

        我们看上边 C++ 引用传递的代码,使用的 & 操作符。& 操作符在 C++ 中被定义为"引用",引用在 C++ 中的定义是“引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样”,再看引用其中的一个描述:“声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元”。因此这引用的概念在 Java 中根本不存在。Java 中哪有给变量起个别名的!!!
        因此说,这个题出的就有问题,在 Java 官方中我一直没有找到明确的证据说“Java 中 值传递、引用传递 的定义”我所看到的全是说 C++ 中关于值传递、引用传递的定义。但是,在 Java 中没有 C++ 里"引用"的概念。Java 里只有对象,new 关键字。这就很尴尬了,拿 C++ 中的定义,来解释 Java,我觉得这就是有问题的。问题就出在了引用传递!!!
        在 C++ 中关于引用传递的定义明确,代码解释清晰。在 C++ 中引用传递,传递的是一个别名,操作别名就跟操作原值一个样。
        然而在 Java 中,没有引用的概念,Java 中只要定义变量就会开辟一个存储单元。因此,对 Java 语言来说只有值传递,没有引用传递是正确的。
        虽然 Java 中没有引用(C++ 中 引用"&")。但是,引用传递的定义,在 Java 中还是有符合条件的。抛开语言中的特性。只针对:值传递、引用传递的定义我们来分析一下,Java 是属于值传递还是引用传递。
        要想知道 Java 是属于值传递还是引用传递,这就要从 Java 内存模型聊起了,我们来看基本数据类型与引用类型在内存中的存储方式。

    二、基本数据类型、引用类型

    1.基本数据类型、引用类型定义
        基本数据类:Java 中有八种基本数据类型“byte、short、int、long、float、double、char、boolean”
        引用类型:new 创建的实体类、对象、及数组
    2.基本数据类型、引用类型在内存中的存储方式
        基本数据类型:存放在栈内存中。用完就消失。
        引用类型:在栈内存中存放引用堆内存的地址,在堆内存中存储类、对象、数组等。当没用引用指向堆内存中的类、对象、数组时,由 GC回收机制不定期自动清理。
    3.基本类型、引用类型内存简单说明图


        好,看了基本的内存图,应该能明白 Java 是属于值传递还是引用传递。不明白,也没关系,下面会详细说明,先说引起争议的代码。

    三、在 Java 中 值传递 与 引用传递,产生模糊不清的代码

    public class TransmitTest {
    
        public static void main(String[] args) {
    
            String a = "hello"; //String 引用数据类型,调用 pass 方法后 b 的值没有改变,不是 hello
            int b = 1; //int 基本数据类型,调用 pass 方法后 a 的值没有改变,还是 1
    
            User user = new User(); //new Class 引用类型,调用 pass 方法后 name 与 age 的值改变了
            user.setName("main"); // 调用 pass 后,name 为 pass 了
            user.setAge(2); //调用 pass 后,age 为 4 了
    
            pass(user, a, b); //pass 方法调用
    
            System.out.println("main 方法 user 是:" + user.toString());
            System.out.println("main 方法 a 的值是:" + a + " --- b 的值是:" + b);
        }
    
        public static void pass(User user, String a, int b) {
    
            a = "你好";
            b = 3;
    
            user.setName("pass");
            user.setAge(4);
    
            System.out.println("pass 方法 user 是:" + user.toString());
            System.out.println("pass 方法 a 的值是:" + a + " --- b 的值是:" + b);
        }
    }
    
    class User {
    
        String name;
        int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "name = " + name + " --- age = " + age;
        }
    }

    【运行结果】
    pass 方法 user 是:name = pass --- age = 4
    pass 方法 a 的值是:你好 --- b 的值是:3
    main 方法 user 是:name = pass --- age = 4
     

        main 方法 a 的值是:hello --- b 的值是:1    结果分析,int b,实参是 1,pass 方法调用后,值还是 1 没变,说明基本数据类型是值传递,大家对这个也几乎没争议。
    争议的在下边了:
            1.String a 是引用类型,pass 方法调用后,值还是 hello 没变。(结论:好像引用类型也是值传递啊!!!)
            2.new User() 也是引用类型,在方法调用后,值居然变了。原值不应该是 name = main,age = 2 的吗?为什么 name = pass,age = 4 了呢?(结论:引用类型好像是引用传递啊???)
        这就奇葩了,String 与 new 创建的类,同为引用类型,为什么产生的结果不一样呢?String 不也是一个类吗?User 不也是一个类吗?
        有人解释说这个代码比喻的不对,应该用如下代码比喻,在 pass 方法中添加一行代码,user = new User(),pass 方法修改如下:

    public static void pass(User user, String a, int b) {
    
        a = "你好";
        b = 3;
    
        user = new User();
        user.setName("pass");
        user.setAge(4);
    
        System.out.println("pass 方法 user 是:" + user.toString());
        System.out.println("pass 方法 a 的值是:" + a + " --- b 的值是:" + b);
    }


    【运行结果】
    pass 方法 user 是:name = pass --- age = 4
    pass 方法 a 的值是:你好 --- b 的值是:3
    main 方法 user 是:name = main --- age = 2
    main 方法 a 的值是:hello --- b 的值是:1

        这样一来,改变了形参的值,但是实参没有改变。因此有人得出结论,Java 中只有值传递,没有引用传递。(我并不这么认为,原因如下)
        使用 user = new User() 这个代码来做验证,我觉得是符合 String 类型做形参时的验证地,但是,此示例不符合引用传递的验证
        在验证之前,我们先看下使用 user=new User(); 语句之前与之后的内存模型图,能有助于我们更好的验证结果,同时也有助于更好的理解 Java 内存模型。我们看 TransmitTest 类在 Java 内存模型中的存储图:

    图1 pass() 方法中没有使用 user=new User() 语句的内存模型图


        在 图1 中,main() 方法中的 user 类,与 pass() 方法中的 user 类,指向的是同一个堆内存中的 User 类,红色虚线是在 main() 方法中初次给 name 属性赋的值"main"。实线部分,是在 pass() 方法中给 name 属性赋的值"pass"。因为在堆内存中只有一个 User 类实体,因此 main() 方法与 pass() 方法中的 user 指向的都是同一个 User 类 0x000031。因此,无论在 main() 方法还是 pass() 方法中,改变其 user 的属性值后,打印 User 类的属性值肯定是一样的,他们用的是一个实体类。

    图2 pass() 方法中使用了 user=new User() 语句的内存模型图


        在 图2 中,main() 方法中的 user 类首次加载,堆内存开辟了一个地址为 0x000031 的 User 类实体。当把 main() 方法中的实参 user 传递给 pass() 方法中形参 user 的时候,栈内存在 pass() 方法区中开辟了一个空间,并引用了地址为 0x000031 的 User 类。此时两个方法中的 User 类其实是一个。
        然而当 pass() 方法中的 user=new User()语句执行后,堆内存中新开辟了一个地址为 0x000032 的 User 类,pass() 方法中的 user 从此指向了地址为 0x000032 的 User 类。
        因为 pass() 方法 与 main() 方法中的 user 属性分别指向了不同的 User 类,所以两个方法中的 User 类的属性无论怎么修改,相互都不影响。
        但是,这种操作是不能验证引用传递定义的。因为实参传值给形参后,形参自己改变了地址,这就和引用传递无关了。我们再来用代码验证。
        我们可以使用 C++ 引用传递代码来验证,使用 user = new User() 语句验证引用传递的错误性
    C++ 中引用传递代码

    class User
    {
    public:
        int age;   // 长度
        string name;  // 宽度
    };
    
    
    //引用传递
    void pass(User &user) {
    
        cout << "引用传递 -- user的地址是:" << &user << endl;
    
        user.age = 2;
        user.name = "你好";
    }
    
    
    int main() {
    
        User user;
        user.age = 1;
        user.name = "hello";
    
        cout << "实参 -- user的地址是:" << &user << endl;
        pass(user);
        cout << "实参 -- user的值 age=" << user.age << ",name=" << user.name << endl;
    
        system("pause");
        return false;
    }


    【运行结果】
    实参 -- user的地址是:00DCF768
    引用传递 -- user的地址是:00DCF768
    实参 -- user的值 age=2,name=你好

        在 C++ 中,引用传递的实参与形参地址一致,在引用的方法中,使用的就是实参的地址。当修改形参值后,实参值也跟着变。现在我们按照 user=new User(); 的方法改变一下引用方法 pass 如下:

    //引用传递
    void pass(User &user) {
    
        cout << "引用传递 -- user的地址是:" << &user << endl;
    
        User user2 = user; //相当于 Java 中的 user=new User();
        cout << "引用传递 -- user2的地址是:" << &user2 << endl;
    
        user2.age = 2;
        user2.name = "你好";
    }


    【运行结果】
    实参 -- user的地址是:00CFFACC
    引用传递 -- user的地址是:00CFFACC
    引用传递 -- user2的地址是:00CFF9AC
    实参 -- user的值 age=1,name=hello

        我们看,改变引用传递中形参 user 的地址后(后期改变的地址,这跟引用传递,值传递还有什么关系?),再修改形参 user 的值,实参没有任何变化。这就破坏了引用传递的场景,因此不能使用 user=new User(); 语句来验证引用传递的定义。

        排除了其他异议,我们再来分析 Java 中有没有引用传递。

    先把引用传递的定义放上

        引用传递:是指在调用函数时将实际参数的地址直接传递到函数中(的形参),那么在函数中对参数所进行的修改,将影响到实际参数。
        引用传递:形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

        经过上面的长篇大论,我想这时候你应该能明白了。在对引用类型做方法传递的时候,是不是先把实参的地址给形参的?之后对形参的操作是,是不是相当于操作实参?最后有没有影响到实际参数?
    答案肯定都是有的。

    定义关键1:是指在调用函数时将实际参数的地址直接传递到函数中(给形参了)
        证明:Java 在进行方法调用传递引用类型参数的时候,就是先给形参一个与实参相同的地址的(此处与 C++ 的不同之处是,C++ 是别名,没有在内存中给形参开辟空间,而 Java 给形参开辟了一个栈内存空间,存放与实参相同的引用地址。但是这与引用传递的定义不违背啊!!!定义可没说形参是否有开辟空间的概念)。

    定义关键2:在函数中对参数所进行的修改,将影响到实际参数。
        证明:Java 在进行方法调用传递引用类型参数后,修改形参的内容后,就是影响了实参的值。

    四、String 与包装类的特殊分析

        好了,解决了实例对象,我们再来说 String 与包装类,为什么 String 与包装类作为引用类型,却有值传递的功能,居然没有影响到实参!

    原因如下
        我们都知道。String 类型及其他七个包装类,是一群特殊群体。当使用 String a = "hello"; 语句时,相当于执行了 String a = new String("hello")。然而在 Java 中每一次 new 都是一次对象的创建。如果你创建的对象在堆中不存在,便会创建一个,如果是新创建的对象,那么地址都会变的,后期改变的地址,这跟引用传递,值传递还有什么关系?

        其实 String 型方法参数传值的过程,可以用以下代码来解释,我们先看 String 类型的还原:

    String a = "hello"; //a 相当于实参
    String a1 = a; //a1 就相当于形参
    a1 = "你好";
    System.out.println("a是:" + a + " --- a1是:" + a1);


    【运行结果】
    a是:hello --- a1是:你好

    逐步还原解释:
        String a = "hello"; 在 String 池中检查并创建一个常量:"hello",给 a 分配一个栈内存,在此存储常量 hello 的地址。
        String a1 = a; 给 a1 分配一个栈内存,在此存储常量 hello 的地址。相当于 a 把自己持有的地址,复制给了 a1。

    内存图如下


        a1 = "你好"; 等同于 a1 = new String("你好")。在 String 池中检查是否有 "你好" 的常量。如果有,将 a1 的地址指向 "你好" 的地址。如果 String 池中没有 "你好" 常量,在堆内存中创建 "你好" 常量,并将 a1 地址指向 "你好"。

    内存图如下


        总结如下:String 类型,在进行方法传参的时候,是先将实参地址,赋值给形参(形参在栈内存中确实新开辟了一个新的内存空间,用于存储地址)。但是当再次给 String 类型的形参赋值(与实参内容不一样的值时),形参地址变了,这就和引用传递无关了。我们可以用 C++ 代码中的引用传递,来验证 String 型的这一特殊情况,代码如下:

    //引用传递
    void pass(string &a, int &b) {
    
        cout << "引用传递 -- a的地址是:" << &a << " --- b的地址是:" << &b << endl;
        cout << "引用传递 -- a的值是:" << a << " --- b的值是:" << b << endl;
    
        string c = a; // 相当于 java 中的 new String
        int e = b;
    
        cout << "引用传递 -- c的地址是:" << &c << " --- e的地址是:" << &e << endl;
        cout << "引用传递 -- c的值是:" << c << " --- e的值是:" << e << endl;
    
        c = "你好"; //在引用传递中改变形参地址后做修改操作,不影响实参
        e = 2; //在引用传递中改变形参地址后做修改操作,不影响实参
    }
    
    int main() {
        string a = "hello";
        int b = 1;
    
        cout << "实参 -- a的地址是:" << &a << " --- b的地址是:" << &b << endl;
    
        pass(a, b);
    
        cout << "实参 -- a的值是:" << a << " --- b的值是:" << b << endl;
    
        system("pause");
        return false;
    }


    【运行结果】
    实参 -- a的地址是:00CFF9CC --- b的地址是:00CFF9C0
    引用传递 -- a的地址是:00CFF9CC --- b的地址是:00CFF9C0
    引用传递 -- a的值是:hello --- b的值是:1
    引用传递 -- c的地址是:00CFF8A0 --- e的地址是:00CFF894
    引用传递 -- c的值是:hello --- e的值是:1
    实参 -- a的值是:hello --- b的值是:1

        我们看,在 C++ 中的引用传递方法中,改变形参的地址后做修改操作,照样不影响实参的值,这就破坏了引用传递的本质,不能这样比喻。

        因此,String 与其他包装类,在做形参的时候,由于他们在赋不同于实参的值时,改变了形参的地址,因此使引用传递,看起来像值传递,其实本质还是引用传递。


    五、总结


    1.这个题目出的不严谨,但是很好(因为涉及了 Java 内存模型)
    2.就 Java 语言本身来说,只有值传递,没有引用传递。
    3.根据 值传递,引用传递的定义来说:
            Java 中的基本类型,属于值传递。
            Java 中的引用类型,属于引用传递。
            Java 中的 String 及包装类,属于特殊群体,作为形参时,由于每次赋值都相当于重新创建了对象,因此看起来像值传递,但是其特性已经破坏了,值传递、引用传递的定义。因此他们属于引用传递的定义,却表现为值传递。

    此题争议很大,我仅分享自己的理解,如有不同结论,欢迎指正,一起共勉!

    展开全文
  • Java中的参数传递,到底是值传递还是引用传递? 错误理解一:值传递引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。 错误理解二:Java是引用传递。 错误理解三:传递的...
  • java中的值传递引用传递

    千次阅读 2021-12-07 20:07:09
    值传递(Pass By Value或者Call By Value)是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。一般java中的基础类型数据传递都是值传递引用传递(Pass By Reference或者Call By Reference)...
  • Go语言之值传递引用传递

    千次阅读 2022-02-15 17:31:20
    Go语言中函数的参数有两种传递方式:按值传递和按引用传递。 Go默认使用按值传递来传递参数,也就是传递参数的副本。在函数中对副本的值进行更改操作时,不会影响到原来的变量。 按引用传递其实也可以称作”按...
  • 快速区分值传递引用传递
  • c语言值传递引用传递 First of all, this article is NOT going to explain C++ references and pointers. If you are not clear on what these are then this is quite probably the wrong article for...
  • 主要介绍了详解java的值传递、地址传递、引用传递的相关资料,需要的朋友可以参考下
  • Java中是值传递引用传递

    千次阅读 2022-01-15 12:22:33
    值传递 / 引用传递 值传递:就是在方法调用的时候,实参是将自己的一份拷贝赋给形参,在方法内,对该参数值的修改不影响原来的实参。 引用传递:是在方法调用的时候,实参将自己的地址传递给形参,此时方法内对该...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,162,688
精华内容 865,075
关键字:

引用传递

友情链接: AD7862.zip