到底是传值还是传引用?

发现关于java中值传递还是引用传递,一些误解挺多的,我的观点(其实也不是我的观点,咱都是二手贩子)就是java中只有值传递;来吧,咱从头来捋一下,本山大叔在卖拐里不是说了嘛,乱了咱就捋,从头捋一下。

1. 啥叫值传递,啥叫引用传递。

在编译原理中,我们会看到参数的传递有四种,定义如下:

Pass by value: This is the  mechanism supported by C . Value of parameters are copied into called routine.

Pass by reference: No copying is done, but a reference (usually implemented as a pointer) is given to the value. In addition to allowing the called routine to change the

values, it is also efficient means for passing large variables (such as structs).

Pass by value-result: This interesting variant supported by languages such as Ada copies the value of the parameter into the routine, and then copies the (potentially changed)

value back out. This has an effect similar to pass-by-reference, but not exactly.

Pass by name: This rather unusual mechanism acts somewhat like C preprocessor macros and was introduced in Algol. Rather than evaluating the parameter value, the name or expression is actually substituted into the calling sequence and each access to the parameter in the calling body re-evaulates it.

 

由定义可知:并不是我们传递某个参数时,若这个参数是某对象的引用,所以这就是引用传递,引用传递在程序设计领域有其自己的意义。

2. 参数传递是通过值传递,不是通过引用传递。

其实很多书籍都讲到这一点,如practical java 第一条讲的就是它。我们有时候会听到说某方法传递了一个引用,其实应该说准确点,传递的是一个引用(型变量)-pass object reference by value,其传递的内容依然是这个引用型变量的值,即:还是值传递。对象都是通过其对象变量来标识的,而这个对象变量的值并不是对象本身,仅仅是存储在另外一处的该对象的引用。网上很多文章会讲到:这是不对的。

混淆值传递和引用传递通常会有个负面作用:一般认为引用传递的结论还会加上一条-对引用的改变会影响原有变量的值--当然,我们认为这是错误的。

         看下面的代码:

import java.util.ArrayList;
import java.util.List;

public class ByValue {
    public static void main(String args[]) {

        // 1.pass primitive type parameter.
        int i = 0;
        System.out.println("initially the i is " + i);
        byPrimitive(i);
        System.out.println("now the i is " + i);

        // 2. pass reference type parameter and change the parameter's initial value.
        List<String> strList = new ArrayList<String>();
        strList.add("hello world");
        System.out.println("initially the strList is" + strList.toString());
        refType(strList);
        System.out.println("now the strList is" + strList.toString());

        // 3.pass reference type parameter and remain the parameter's initial value .
        List<String> strList2 = new ArrayList<String>();
        strList2.add("hello world ");
        System.out.println("initially the strList2 is" + strList2.toString());
        refType2(strList2);
        System.out.println("now the strList2 is" + strList2.toString());


        List<String> strList3 = new ArrayList<String>();
        strList3.add("hello world 3");
        List<String> strList3Ref = new ArrayList<String>();

        // 4.deep copy
        strList3Ref.addAll(strList3);
        // 5.shallow copy
        strList3.add("hi jude 3");

        System.out.println("now strList3: " + strList3);
        System.out.println("now strList3Ref: " + strList3Ref);
    }

    //1. 修改基本 型变量拷贝的值
    //1.1 在栈内存 的新地址中复制了i的拷贝i',i'的值也是0;
    //1.2 修改了 i'的值为3;
    //1.3 i的值没有 变化
    private static void byPrimitive(int i) {
        i = i + 3;
        System.out.println("the i' is :" + i);
    }

    //2.修改引用 型变量的拷贝的所引用的对象。
    // 2.1 在栈内存 中复制了strList的拷贝 值,新建了引用strList’,strList’的值等同 于strList的值, 即:指向同样的一片堆内存区域;
    // 2.2,对strList'引用的对 象做了修改;2.3因为strList所引用的 对象就是strList'所引用的 对象,所以strList的引用对 象发生了变化。
    private static void refType(List<String> strList) {
        strList.add("hi jude");
        System.out.println("the strList' is :" + strList);
    }

    //3 修改引用 型变量的拷贝的引用。
    //3.1 在栈内存 中复制了strList2的拷贝 值,新建了引用strList2’,strList2'的值等同 于strList2的值, 即:指向同样的一片内存区域;
    //3.2 对strList2'做修改, 即,使其成为局部变量targetList所引用对 象的引用。
    //3.3 修改strList2'所引用对 象的值,即strList2'所引用对 象发生了改变,strList2却没有变 化。
    private static void refType2(List<String> strList) {
        List<String> targetList = new ArrayList<String>();
        strList = targetList;
        strList.add("hi jude");
        System.out.println("the strList' is :" + strList);
    }

}
 

以上代码中的注释已经基本清晰,第三种种" 修改引用型变量的拷贝的引用"我个人感觉用途很少, 在实际中若是在传递值过程中不想改变原有引用的对象,一般都会
先将该变量所引用的对象内容先拷贝一份, 然后再对其操作,这样就不会修改原值。对此,我们会注意到另外一个话题,就
是浅拷贝和深拷贝,在第三点我们再细述。
上面三种情况的内存分配图如下:

1. 修改基本 型变量拷贝的值


 

2.修改引用 型变量的拷贝的所引用的对象。


 

    3. 修改引用 型变量的拷贝的引用。

 

 

 


 

 

3. 浅拷贝 深拷贝 .

    引用到三方方法时得考虑,such as Collection.add() 和collection.addAll(),见代码4和5部分。

 

4. 为啥会有误解?

    4.1 一种:因为java中的对象都是靠对象变量即引用来标识的,所以参数传递中试引用时,当然就是引用传递了。

          解释:如果传递了Object reference那么就是by reference,那么对应的基本类型(primitive type)的参数传递应该就是by primitive ,在这会,reference type 和primitive type是一对对立统一的范畴,在命名上不能顾此失彼吧。归根到底还是对by reference and by value的定义不清楚,参见我们捋的第一条。

    4.2 另外一种:参数传递中,会改变原来的变量的值,所以是引用传递。

          解释:在 所谓的call by reference中,有时候原变量的值,其所引用的对象存在不改变的情况。参见例子.所以不能成为理由。

    4.3 与C++中的by reference混淆。

 

5. 最后,你实在不相信我说的,也该相信Java他爹说的话吧?

Some people will say incorrectly that objects are passed "by reference." In programming language design, the term pass by reference properly means that when an argument is passed to a function, the invoked function gets a reference to the original value, not a copy of its value. If the function modifies its parameter, the value in the calling code will be changed because the argument and parameter use the same slot in memory. If the Java programming language actually had pass-by-reference parameters, there would be a way to declare halve it so that the above code would modify the value of one, or so that common Name could change the variable sirius to null. This is not possible. The Java programming language does not pass objects by reference; it passes object references by value. Because two copies of the same reference refer to the same actual object, changes made through one reference variable are visible through the other. There is exactly one parameter passing mode pass by value and that helps keep things simple.

 

                                                                -----Gosling. The Java Programming Language 第2章 第6节

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值