难倒了同事:Java 方法调用究竟是传值仍是传引用

Java 方法调用中的参数是值传递仍是引用传递呢?相信每一个作开发的同窗都碰到过传这个问题,不光是作 Java 的同窗,用 C#、Python 开发的同窗一样确定遇到过这个问题,并且颇有可能不止一次。java

那么,Java 中究竟是值传递仍是引用传递呢,答案是值传递,Java 中没有引用传递这个概念。程序员

数据类型和内存分配

Java 中有能够归纳为两大类数据类型,一类是基本类型,另外一类是引用类型。web

基本类型shell

byte、short、int、long、float、double、char、boolean 是 Java 中的八种基本类型。基本类型的内存分配在栈上完成,也就是 JVM 的虚拟机栈。也就是说,当你使用以下语句时:json

int i = 89;
复制代码

会在虚拟机栈上分配 4 个字节的空间出来存放。数组

引用类型app

引用类型有类、接口、数组以及 null 。咱们平时熟悉的各类自定义的实体类啊就在这个范畴里。ide

当咱们定义一个对象而且使用 new 关键字来实例化对象时。学习

User user = new User();
复制代码

会经历以下三个步骤:this

一、声明一个引用变量 user,在虚拟机栈上分配空间;

二、使用 new 关键字建立对象实例,在堆上分配空间存放对象内的属性信息;

三、将堆上的对象连接到 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 changeIntValueint oldValue ){
    int newValue = 100;
    oldValue = newValue;
}
复制代码

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

数组和类

数组

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

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

public static void changeArrayValueint[] 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 的世界中没有所谓的指针传递。

人在江湖,各位捧个赞场,轻轻点一下赞吧 👍

相关文章
相关标签/搜索