JAVA值传递和引用传递的争论

今天一起秋招的小伙伴说面试官问了JAVA值传递和引用传递的问题,我不假思索的说是引用传递,结果引来一系列的讨论。但是以下几点是得到了共同的认识的:

  1. 如果是值传递,那么函数调用过程中对目标的改变不会导致原来目标发生变化,类似于c++里面的swap(int a, int b)函数,在运行函数过程中,内部的a,b实际上是外部a,b的一个拷贝,改变不会传递到外部。
  2. 如果是引用传递,在函数内部的改变会影响外部的值,似于c++里面的swap(int &a, int &b),在运行函数过程中会改变外部的值。

实际上c++还有一种传递是指针传递,实际上是指针变量的值传递,这个不谈。那么JAVA里面的变量到底是值传递还是引用传递呢?我们分以下两种情况讨论:

1、基本类型变量的函数调用,这里面用intfloat作为主要的类型,代码如下:

package com.lixiande.leetcode;
public class test {
    public  static void vallueCrossTest(int age , float weight){
        System.out.println("函数内的age 地址" + System.identityHashCode(age));
        System.out.println("函数内的weight 地址" + System.identityHashCode(weight));
        age = 33;
        weight = 89.5f;
        System.out.println("函数内改变的age 地址" + System.identityHashCode(age));
        System.out.println("函数内改变的weight 地址" + System.identityHashCode(weight));
    }
    public static void main(String[] args) {
        int age = 10;
        float weight = 10.01f;
        System.out.println("函数外的age 地址" + System.identityHashCode(age));
        System.out.println("函数外的weight 地址" + System.identityHashCode(weight));
        vallueCrossTest(age,weight);
        System.out.println("函数外改变的age 地址" + System.identityHashCode(age));
        System.out.println("函数外改变的weight 地址" + System.identityHashCode(weight));
    }
}

在函数调用前后分别获取对应参数的地址值(这里用System.identityHashCode代替),结果如下:

函数外的age 地址728890494
函数外的weight 地址1558600329
函数内的age 地址728890494
函数内的weight 地址636718812
函数内改变的age 地址445051633
函数内改变的weight 地址1051754451
函数外改变的age 地址728890494
函数外改变的weight 地址1349277854

可以看到函数传递过程中的地址是不变的,但是对于基本类型int改变了他的值会导致指向的地址发生改变,这个是因为常量池的原因,常量池会维护1-127int值。而float类型对象则是发生了多次改变,分别是从1558600329->636718812->1051754451->1349277854,float没有常量池,会在栈上找有没有相等的字面量,没有就会在栈上新开辟,所以地址发生了变化。

2、类对象的函数调用

这里面使用一个person类作为测试类,同样使用System.identityHashCode作为地址判断:

package com.lixiande.leetcode;
class person{
    private String name;
    public person(String name){
        System.out.println("now i am init a new person");
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "person{" +
                "name='" + name + '\'' +
                '}';
    }
}
public class test {

    public  static void personCrossTest(person p ){
        System.out.println("函数内的p 地址" + System.identityHashCode(p));
        p.setName("lixiang");
        System.out.println("函数内改变的p 地址" + System.identityHashCode(p));


    }
    public static void main(String[] args) {
        person p = new person("松哥哥");
        System.out.println(p.toString());
        System.out.println("函数外的p 地址" + System.identityHashCode(p));
        personCrossTest(p);
        System.out.println("函数外的改变后p 地址" + System.identityHashCode(p));
        System.out.println(p.toString());
    }
}

结果如下:

now i am init a new person
person{name='松哥哥'}
函数外的p 地址1775282465
函数内的p 地址1775282465
函数内改变的p 地址1775282465
函数外的改变后p 地址1775282465
person{name='lixiang'}

首先,函数内外地址发生改变了吗?没有,也就是内部的person指向的和外部person指向的对象没有发生改变。这里面加入修改函数内person指向的方式进行操作,将函数修改为

    public  static void personCrossTest(person p ){
        System.out.println("函数内的p 地址" + System.identityHashCode(p));
        p = new person("lixiang");
        System.out.println("函数内改变的p 地址" + System.identityHashCode(p));
    }

结果为:

now i am init a new person
person{name='松哥哥'}
函数外的p 地址1775282465
函数内的p 地址1775282465
now i am init a new person
函数内改变的p 地址1147985808
函数外的改变后p 地址1775282465
person{name='松哥哥'}

也就是我重新修改了p的指向不会改变外部的p的指向,从这个角度上来说确实是内部p的指向的改变没有影响外部的p的指向。这个是不是很像c++里面指针传递的过程呢,改变了函数内改变了指针的指向,但是函数外的指针指向不会发生变化。

原因解释:

每一个函数调用栈都会存储在函数调用过程中产生的数据(比如没有常量池的基本类型,或者堆里的引用,或者常量池里面的引用),那么每次调用函数过程会开辟新的函数调用栈,这个栈地址和原来函数的调用地址必然是不同的。以上面的代码为例,main的栈里面的p自然和personCrossTest里面的p所在的位置自然不同(因为栈都不同),但是里面的地址一开始是相同的,指向堆内同一个person对象,在personCrossTest函数内部改变p的指向相当于改变了personCrossTestp的值,使personCrossTest 栈的p指向了一个新的person对象,而在main的栈里面的p没有改变值自然是没有改变对象。

总结:

1、传递的数据是什么?我个人认为是调用栈给被调用栈传递了一个对象的地址,如果对象为基本类型,则可能是要自己开辟。

2、从堆对象的角度来说传递的是堆内对象的一个地址(或者说引用)。

3、从栈的变量的角度来说传递的是调用栈的地址的复制,也就是地址的值传递。

4、我认为两边都不太像,实际上我认为对于一般对象的传递,java传递方式反而更像是c++的指针传递,因为在函数内部可以改变指向,不像是引用传递,而值传递又由于栈内保存的是地址,而不是直接操作的对象,复制的只是一个地址,所以也不是很像值传递。那么关键在于传递地址这个行为到底是复制了一个地址还是传递了一个对象引用,则看你个人的理解。

以上是我个人的理解,希望大家和我纠正和讨论。