首页 > 为什么java值传递时传递的是一份拷贝?

为什么java值传递时传递的是一份拷贝?

大家都知道,java里面,无论是引用传递还是值传递,实际上传递的只是他们的一份拷贝,但是我有一点想不明白,为什么要传递拷贝,而不能传递他们原生的值或引用呢?求大神门指点一下迷津,万分感谢。


看怎么理解,可以理解为Java只有值copy。

先看基本类型:
int a = 10;
有个方法 doWithInt(int a), 当调用doWithInt(a)的时候,实际是把a的值10传递过去了。

再看对象类型:
Object obj = new Object();
有个方法doWithObj(Object obj), 当调用doWithObj(obj)的时候,实际是把obj的引用这个值(比如是0x4123fac9)传过去了。注意这里的值,是指的obj的引用,对于一个对象来说,能标示它的唯一值的就是他的地址引用。

所以你看,理解万岁啊。


看你怎么理解引用跟值了。从某种角度上说引用也是值,所以在说我的理解前先下个定义:

Object reference= (Object)value;
上式中reference 是所谓的“引用”, value是所谓的“值”。

从这个定义上来说,java的传递全都是“值传递”。
令Object reference2 = (Object)value;
然后传递reference2 去操作value。reference2是函数范围内的局部变量,用完即收回。

至于你说的“实际上传递的只是他们的一份拷贝”,如果把“他们”理解为reference的话那么这句话是对的。为什么呢?因为java只想传递value,不想修改reference,所以传递一个reference的copy。

那么问题来了:为什么java不能传reference呢?
答:这个问题其实要问java设计者了。个人觉得这么设计牺牲了一些灵活性,但是获得的好处么。。。你想到了C++的指针了吗


是因为java里 没有指针?

java里的reference是一个抽象的概念, 里面放得也不是地址. 试想一下, 每次gc后, 对象实例有可能在不同的 代中移动, 内存地址就变了! 所以java中不会直接操作地址.


值传递传的是拷贝,引用传的是地址。


java只有值传递,这句话的意思从底层来看,就是栈上的东西永远会被复制。int存于栈上所以传参的时候会被复制。
对于object,他的引用存于栈上,但是实例在托管堆上,复制的是它的引用,实例本身并没有被复制,所以效果相当于引用传值。在子函数里面,对实例本身的任何操作,比如调用其方法,都会影响外面,但是如果你重新new了一次,不会影响到外面的。


大更新后,原来的回答就删了
为了回答Lance_D的提问,重新翻看了一下java虚拟机的书。

1.先看一段代码

public static void main(String args[]){
    char[] c1 = {'a','b'};
    System.out.println("c1:"+c1[0]+" "+c1[1]);
    changer1(c1);
    System.out.println("c1 change1 after:"+c1[0]+" "+c1[1]);
    changer2(c1);
    System.out.println("c1 change2 after:"+c1[0]+" "+c1[1]);
}

private static void changer1(char[] c1) {
    char[] c2 = {'e','f'};
    c1 = c2;
}

private static void changer2(char[] c1) {
    char[] c2 = {'e','f'};
    c1[0] = c2[0];
}

输出

c1:a b
c1 change1 after:a b
c1 change2 after:e b

看一下内存里change1的真实过程

change2的过程

change3的过程

2.理解String

Java中的String类其实就是char数组的包装类,源码:

/** The value is used for character storage. */
private final char value[];

/** Cache the hash code for the string */
private int hash; // Default to 0

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

所以不管是String s1="ab";或者String s1=new ("ab");其实真实的情况是

s1 = "cd"

3.重新看一下java虚拟机中支持的原始数据类型与引用数据类型

原始数据类型

数值类型

byte类型:值为8位有符号二进制补码整数,默认为零
short类型:值为16位有符号二进制补码整数,默认为零
int类型:值为32位有符号二进制补码整数,默认为零
long类型:值为64位有符号二进制补码整数,默认为零
char类型:值为16位无符号整数表示的、指向基本多文种平面的Unicode码点,以UTF-16编码,默认值为Unicode的nell码点('\u0000')
float类型与double类型要复杂一些

boolean类型

值为布尔值truefalse,默认为false

returnAddress类型

表示一条字节码指令的操作码(opcode)

引用数据类型

分别由类实例、数组实例和实现了某个接口的类实例动态创建

4.看个复杂一点点的实例

public class Test {

    class Student{

        public String name;
        public int number;

        public Student(String sName, int sNumber){
            name = sName;
            number = sNumber;
        }

    }

    public void test(){
        Student s1 = new Student("Jack",1);
        System.out.println("s1:"+s1.name+" "+s1.number);
        change1(s1);
        System.out.println("s1:"+s1.name+" "+s1.number);
        change2(s1);
        System.out.println("s1:"+s1.name+" "+s1.number);
    }


    private void change1(Student s1) {
        s1 = new Student("Mack",2);
    }

    private void change2(Student s1) {
        s1.name = "Mack";
        s1.number = 2;
    }


    public static void main(String args[]){
        new Test().test();
    }

}

输出:

s1:Jack 1
s1:Jack 1
s1:Mack 2

实际change1过程:

change2过程:


基本数据类型是值传递, 其他的是引用传递吧


多看看jvm内存


在MF的重构中有提交,java本身是值传递,如果传递常量,是传递拷贝,如果传递的是对象,则传递的是引用指向拷贝,引用本身指向是不能改变,但是能够改变引用本身的内容。

理解为传递的是引用指向的拷贝即可,指向不能更改,引用所引用的内容可以更改。


如果传入参数是对象的话,实际是对象的引用,根据按值传递的原则,又新建了一个引用副本,所以在方法体内对该引用内容进行修改,当然会直接改变所指向对象的值。而对于基本类型的参数,我们传递值后又建了这个值的副本,对这个副本的修改自然不会修改原参数的值。

【热门文章】
【热门文章】