天天看点

大厂面试系列-为什么说在Java语言方法参数调用只有值传递呢?

作者:架构师面试宝典
大厂面试系列-为什么说在Java语言方法参数调用只有值传递呢?

在实际开发中肯定有人问过Java中的参数传递到底是怎么进行的?为什么在Java中只有值传递呢?在面试中也很少有人能将这个问题回答的很清楚。下面我们我们就来看看在Java中到底是如何实现值传递的。

简单说明

有人疑惑过如下的一个操作,我们先定义了一个User类。

public class User {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}           

然后创建一个测试类Test。

public  class Test {
    public static void main(String[] args) throws Exception {
        User user = new User();

        user.setPassword("4546");
        user.setUsername("test");
        getUserInfo(user);
        System.out.println(user);

    }


    public static String getUserInfo(User user){
        user.setUsername("user");
        user.setPassword("12312312");
        return "OK";
    }
}
           

这个时候我们来猜测一下打印出的数据到底是test用户还是user用户。带着这个问题我们来探究关于Java的值传。

我们会看到在上面Test代码中有两个方法一个是我们的main方法一个是自定义的方法。并且两个方法都传入了方法参数。在Java中传入的参数有两种一种是形式参数,一种是实际参数。

形式参数

形式参数是在定义函数名或者函数体的时候使用的参数,用来表示在使用该函数的时候传入的参数值。

实际参数

对于实际参数来讲,调用有参数函数的时候,在主调用函数和被调用函数之间形成的参数传递,也就是说在主函数中调了另外一个函数的时候,在函数中传入的参数就是称之为实际参数。

例如上面的例子中,在方法定义的时候传入的User对象就是一个形式参数,而在使用getUserInfo()方法的时候传入的参数就是实际参数。

当我们在进行方法调用的时候,会将实际参数传递给形式参数,如上面的代码中我们将User对象传递给了getUserInfo()方法,并且在方法体中对User对象进行了修改。那么在整个过程中到底传递的是什么呢?

求值策略

根据百度百科的解释,求值策略是确定编程语言中表达式的求值的一组确定性的规则。主要是位于函数上,求值策略规定了什么时候以什么样的顺序给定函数的实际参数。并且规定了什么时候将这些参数带入到函数中。当然还有很多概念,有兴趣的读者可以继续深入探索,这里我们就不再展开说明了。

关于求值策略主要分为两大类,一类是 严格求值,一类是非严格求值。这里我们介绍一下严格求值相关的内容。

严格求值

对于严格求值来讲,就是说在函数调用过程中,给函数的实际参数一定要在使用这个函数之前进行求值。在很多的编程语言中都是遵守严格求值的操作。

在严格求值中有几个概念,值调用、引用调用、共享对象调用。

  • 值调用

对于传值调用来讲,需要实际参数先进行求值,然后通过值复制的方式传递给调用函数的形式参数。这是因为方法参数值获取到的只是一个局部拷贝的值,如果在调用函数中改变了形式参数的值是不会影响到实际参数的值的。

  • 引用调用

对于传引用调用来讲,传给参数的是它实际需要使用参数的隐式调用而不是对实际参数的值拷贝操作。也就是说传入的参数其实是实际参数的引用,既然是引用那么修改被调用函数中的参数值就会对实际在使用的对象进行修改。

  • 共享对象调用

对于传共享对象调用来讲,首先获取到的是实际参数的地址,然后将其进行复制,并且把对应的拷贝传递给函数的形式参数,这个时候传入的参数和实际的参数指向的都是同一个对象,所以我们称为是共享对象调用,所以在如果在调用函数中修改了对应的值之后,实际使用者也会发生对应的值变化。

从上面我们可以看出好像共享对象调用和值调用似乎操作过程是一样的。都是进行了求值、复制、拷贝、传值等阶段。真的是这样么?从上面的表达中可以知道,共享对象调用与值调用的过程虽然是一样的,但是其结果却是与引用调用的结果相同。

这是因为共享对象调用过程与值传递过程一样的,唯一不同的是对于复制操作的使用。所以我们可以将共享对象调用看做是值传递调用的一个特殊情况。

大厂面试系列-为什么说在Java语言方法参数调用只有值传递呢?

图片来源网络

首先我们来分析一下值调用与引用调用的过程,其主要的区别就是对于复制的处理,在值传递调用的过程中我们是将实际参数的值复制了一份,然后传入到调用函数中,这个时候,调用函数中只是使用了对应的值。但是在引用调用的时候,我们是将实际参数本身复制了一份,相当于直接将实际参数传递到了调用函数中。所以这两者的区别就是一个传递的是副本,一个传递的相当于实际参数本身。

为什么Java中只有值传递呢?

通过上面的分析,明显可以感觉到在我们开篇的时候的示例代码中的操作是一个引用传递。但是为什么要说Java中只有值传递呢?会不会是因为User对象是一个对象所以是引用传递,如果将参数值换成基本数据类型呢?代码如下

public  class Test {
    public static void main(String[] args) throws Exception {

        int a = 1;
        getInteger(1);
        System.out.println(a);

    }

    public static void getInteger(int a){
        a++;
        return;
    }

}           

其实在我们很多人的认知中都将示例代码中的如下操作当成了对象的引用传递。所以会误以为是Java对象中的对象传递与基本数据类型传递是不一样的。

public  class Test {
    public static void main(String[] args) throws Exception {
        User user = new User();

        user.setPassword("4546");
        user.setUsername("test");
        getUserInfo(user);
        System.out.println(user);

    }


    public static String getUserInfo(User user){
        user.setUsername("user");
        user.setPassword("12312312");
        return "OK";
    }
}           

而且我们在实际运行完成上面的代码之后,也会发现确实与我们的分析结果是一样的。但是这其实是一个误区。下面我们就来看看Java中的对象传递。

Java中的对象传递

根据上面的分析,我们似乎发现了再传递的参数为对象的时候,好像表现出来了我们分析的引用传递的现象,无论是值传递还是引用传递都是求值策略的一种形式,而对于求值策略来讲我们知道还有很多的求值策略。例如前面提到的共享对象调用与引用调用的结果是一样的,但是过程就是不一样的。那么我们为什么没有将其看做共享对象调用呢?

那么在Java中的对象传递到底属于哪一种呢?

在网上有一种说法,就是对于基本数据类型的传递,原始参数是通过形式参数传入到函数调用中,并且这个形式参数只存在于方法调用的范围,当方法返回的时候,形式参数就消失了,对它所改变的任何操作都会消失。

而对于对象参数的应用,引用的数据类型是按照值传递给了调用方法,也就意味着当方法返回的时候,传入的引用任然是传递之前的引用,而对于引用对象的属性操作相当于操作了原始引用的引用对象。理解上有点困难,我们可以看看如下图所示。

大厂面试系列-为什么说在Java语言方法参数调用只有值传递呢?

我们知道在Java中对于引用来讲其实就是一个指向某个对象的内存地址,而对于引用的值传递就是将在一个引用传递给了一个新的引用,这个引用本身是没有发生变化的。只是由于整个引用指向的对象与传递之前的引用指向了同一个对象。所以说看到了我们上面实例代码中所展示的内容。我们在方法中修改了User对象属性值之后实际的User对象的属性值也发生了变化。

总结

根据上面的表现,我们从结果上来看,也可以将其看做是一个共享对象调用。就是上面所说的将对象的地址拷贝了一份然后传给了形式参数,只不过这里我们看上去传入的就是那个引用地址的表现。并且前面我们也说了共享对象调用就是值调用的一种特例。既然是子集,那么就可以理解为什么Java是值传递了。

继续阅读