关于java:难住了同事Java-方法调用到底是传值还是传引用

9次阅读

共计 3804 个字符,预计需要花费 10 分钟才能阅读完成。

Java 办法调用中的参数是值传递还是援用传递呢?置信每个做开发的同学都碰到过传这个问题,不光是做 Java 的同学,用 C#、Python 开发的同学同样必定遇到过这个问题,而且很有可能不止一次。

那么,Java 中到底是值传递还是援用传递呢,答案是值传递,Java 中没有援用传递这个概念。

数据类型和内存调配

Java 中有能够概括为两大类数据类型,一类是根本类型,另一类是援用类型。

根本类型

byte、short、int、long、float、double、char、boolean 是 Java 中的八种根本类型。根本类型的内存调配在栈上实现,也就是 JVM 的虚拟机栈。也就是说,当你应用如下语句时:

int i = 89;

会在虚拟机栈上调配 4 个字节的空间进去寄存。

援用类型

援用类型有类、接口、数组以及 null。咱们平时相熟的各种自定义的实体类啊就在这个领域里。

当咱们定义一个对象并且应用 new 关键字来实例化对象时。

User user = new User();

会经验如下三个步骤:

1、申明一个援用变量 user,在虚拟机栈上调配空间;

2、应用 new 关键字创建对象实例,在堆上调配空间寄存对象内的属性信息;

3、将堆上的对象链接到 user 变量上,所以栈上存储的实际上就是存的对象在堆上的地址信息;

数组对象也是一样的,栈上只是存了一个地址,指向堆上理论调配的数组空间,理论的值是存在堆上的。

为了分明的展现空间调配,我画了一张类型空间调配的示例图。

没有争议的根本类型

当咱们将 8 种根本类型作为办法参数传递时,没有争议,传的是什么(也就是实参),办法中接管的就是什么(也就是形参)。传递过来的是 1,那接到的就是 1,传过来的是 true,接管到的也就是 true。

看上面这个例子,将变量 oldIntValue 传给 changeIntValue 办法,在办法内对参数值进行批改,最初输入的后果还是 1。

public static void main(String[] args ) throws Exception{
    int oldIntValue = 1;
    System.out.println(oldIntValue);
    passByValueOrRef.changeIntValue(oldIntValue);
    System.out.println(oldIntValue);
}

public static void changeIntValue(int oldValue){
    int newValue = 100;
    oldValue = newValue;
}

扭转参数值并不会扭转原变量的值,没错吧,Java 是按值传递。

数组和类

数组

有的同学说那不对呀,你看我上面这段代码,就不是这样。

public static void main(String[] args ) throws Exception{int[] oldArray = new int[] { 1, 2};
    System.out.println(oldArray[0] );
    changeArrayValue(oldArray);
    System.out.println(oldArray[0] );
}

public static void changeArrayValue(int[] newArray ){newArray[0] = 100;
}

这段代码的输入是

1
100

阐明调用 changeArrayValue 办法时,批改传过来的数组参数中的第一项后,原变量的内容扭转了,那这怎么是值传递呢。

别急,看看上面这张图,展现了数组在 JVM 中的内存调配示例图。


实际上能够了解为 changeArrayValue 办法接管的参数是原变量 oldArray 的正本拷贝,只不过数组援用中存的只是指向堆中数组空间的首地址而已,所以,当调用 changeArrayValue 办法后,就造成了 oldArray 和 newArray 两个变量在栈中的援用地址都指向了同一个数组地址。所以批改参数的每个元素就相当于批改了原变量的元素。

个别咱们在开发过程中有很多将类实例作为参数的状况,咱们形象进去的各种对象常常在办法间传递。比方咱们定义了一个用户实体类。

public class User {

    private String name;

    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = 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 "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

比方说咱们有一个原始的实体 User 类对象,将这个实体对象传给一个办法,这个办法可能会有一些逻辑解决,比方咱们拿到这个用户的 name 属性,发现 name 为空,咱们就给 name 属性赋予一个随机名称,例如“用户 398988”。这应该是很常见的一类场景了。

咱们通常这样应用,将 user 实例当做参数传过来,解决实现后,再将它返回。

public static void main(String[] args ) throws Exception{User oldUser = new User( "原始姓名", 8);
    System.out.println(oldUser.toString() );
    oldUser = changeUserValue(oldUser);
    System.out.println(oldUser.toString() );
}

public static User changeUserValue(User newUser){newUser.setName( "新名字");
    newUser.setAge(18);
  return newUser;
}

但有的同学说,我发现批改实现后就算不返回,原变量 oldUser 的属性也扭转了,比方上面这样:

public static void main(String[] args ) throws Exception{User oldUser = new User( "原始姓名", 8);
    System.out.println(oldUser.toString() );
    changeUserValue(oldUser);
    System.out.println(oldUser.toString() );
}

public static void changeUserValue(User newUser){newUser.setName( "新名字");
    newUser.setAge(18);
}

返回的后果都是上面这样

User{name='原始姓名', age=8}
User{name='新名字', age=18}

那这不就是援用传递吗,改了参数的属性,就改了原变量的属性。依然来看一张图

实际上依然不是援用传递,援用传递咱们学习 C++ 的时候常常会用到,就是指针。而这里传递的其实是一个正本,正本中只存了指向堆空间对象实体的地址而已。咱们咱们批改参数 newUser 的属性间接的就是批改了原变量的属性。

有同学说,那画一张图说这样就是这样吗,你说是正本就是正本吗,我偏说就是传的援用,就是原变量,也说得通啊。

的确是说的通,如果真是援用传递,也的确是这样的成果没错。那咱们就来个反例。

public static void main(String[] args ) throws Exception{User oldUser = new User( "原始姓名", 8);
    System.out.println(oldUser.toString() );
    wantChangeUser(oldUser);
    System.out.println(oldUser.toString() );
}

public static void wantChangeUser(User newUser){newUser = new User( "新姓名", 18);
}

假如就是援用传递,那么 newUser 和 main 办法中的 oldUser 就是同一个援用对象,那我在 wantChangeUser 办法中从新 new 了一个 User 实体,并赋值给了 newUser,依照援用传递这个说法,我赋值给了参数也就是赋值给了原始变量,那么当实现赋值操作后,原变量 oldUser 就应该是 name = “ 新名字 ”、age=18 才对。

而后,咱们运行看看输入后果:

User{name='原始姓名', age=8}
User{name='原始姓名', age=8}

后果仍然是批改前的值,咱们批改了 newUser,并没有影响到原变量,显然不是援用传递。

论断

Java 中的参数传递是值传递,并且 Java 中没有援用传递这个概念。咱们通常说的援用传递,个别都是从 C 语言和 C like 而来,因为它们有指针的概念。

而咱们也晓得,C、C++ 中须要程序员本人治理内存,而指针的应用常常会导致内存透露一类的问题,Java 含辛茹苦的就是为了让程序员解放出来,而应用垃圾收集策略管理内存,这其中很重要的一点就是躲避了指针的应用,所以在 Java 的世界中没有所谓的指针传递。

零根底学习 Java 编程,能够退出我的十年 Java 学习园地。

正文完
 0