java基础06 Java是值传递还是引用传递?

  答案:Java没有引用传递。
  很多人看到这个答案可能会很苦恼,可能会说博主傻缺,怎么会没有引用传递呢。各位看官,请稍安勿躁,且听我慢慢道来。

基本概念

  在道出原因之前,我们先了解一下基本概念,以便初学者或基础不太扎实的看官有一个基本的认识。

  • 实参:指实际参数,是提前准备好的参数用于传入方法中。存储在栈内存中;若实参是基本类型,存的是基本类型的值;若实参是引用类型,存的是实参的引用,用于指向堆中的对象。
  • 形参:指形式参数,方法中的参数。方法调用是在栈中取存储的对应实参副本。
  • 值传递:方法调用的时候,实参把它的值传递给对应的形参,方法内对形参的改变不会影响到实参的值。
  • 引用传递:方法调用的时候,实参将它的地址传递给形参,方法内对形参值的改变,会影响到实参的值。
/**
 * 实参和形参
 */
public class ParameterConcept {

    public static void main(String[] args) {
        int actualParameter = 1;
        print(actualParameter);		// 这里传递的actualParameter 是实参
        System.out.println("actualParameter的值:" + actualParameter);
    }

    public static void print(int shapeParameters){	// 这里的 shapeParameters 表示形参
        shapeParameters = 10;
        System.out.println("shapeParameters的值:" + shapeParameters);
    }
}

这个就是典型的值传递,其输出结果如下:

shapeParameters的值:10
actualParameter的值:1

求值策略

当我们进行方法的调用的时候,需要把实参传递给形参,那我们传递的究竟是什么东西?
我们先来理解一个概念:求值策略(Evaluation strategy)。在百度百科是这样解释的:

  • 在计算机科学中,求值策略(Evaluation strategy)是确定编程语言中表达式的求值的一组(通常确定性的)规则。重点典型的位于函数或算子上——求值策略定义何时和以何种次序求值给函数的实际参数,什么时候把它们代换入函数,和代换以何种形式发生。
  • 求值策略分为两大基本类,严格的和非严格的,基于如何处理给函数的实际参数。

我们通俗的理解就是,计算机对值有一种策略,这种策略是用来指定求值的顺序、值传递的时机以及传递的方式。

严格求值

  在“严格求值”中,函数调用过程中,给函数的实际参数总是在应用这个函数之前求值。多数现存编程语言对函数都使用严格求值。所以,我们本文只关注严格求值。
  在严格求值中有几个关键的求值策略是我们比较关心的,那就是传值调用(Call by value)、传引用调用(Call by reference)以及传共享对象调用(Call by sharing)。

上面对值传递(传值调用)和引用传递(传引用调用)做了简单的解释,这里再做更详细一点的解释。

  1. 传值调用(值传递):在传值调用的过程中,实参会被进行一次求值操作,然后把求到的值拷贝一份传递给形参。方法中是对形参进行操作(也就是实参值的一个副本),所以不会影响到实参的值。
  2. 传引用调用(引用传递):在传引用调用的过程中,传递给方法是实参的一个隐式引用,不是实参值的副本,所以对形参的操作会影响到实参的值。
  3. 传共享对象调用(共享对象传递):在传共享对象调用的过程中,对实参进行求值操作,会获取实参的存储地址(引用指向的地址),对这个地址进行拷贝一份再传递给形参,形参获取的地址与实参的地址是指向同一个位置,所以对形参的操作会影响到实参的值。

  到这里,大家应该能发现,传共享对象调用传值调用很相似,都是对实参进行求值、拷贝、传递三个主要操作,唯一不同的是一个传递的是指向真实值的地址,一个传递的是真实值的副本。
  传共享对象调用可以理解为值传递与引用传递的结合版,它有着值传递类似的操作过程和引用传递方式的结果。

三种传递的图示

值传递
值传递
引用传递
引用传递
传共享对象调用
传共享对象传递

public class Transmit {

    public static void main(String[] args) {
        Student student = new Student("小明", 23);
        change(student);
        System.out.println("学生的年龄:" + student.getAge());
    }

    private static void change(Student student){
        student.setAge(80);
    }

    @Data
    @AllArgsConstructor
    static class Student{
        private String name;
        private int age;
    }
}

显示结果为:

学生的年龄:80

  从显示的结果我们可以看出,传递的对象是一个引用类型(数据类型有两种,下面会给出),学生的年龄经过 change 方法之后,变成了 80 ,所以肯定不是值传递,那它是引用传递还是传共享对象调用呢?
  由于引用传递和共享对象传递最终的执行效果是一样的,都会影响到调用者的值,就很难判断,在翻阅很多博客的时候,在一篇博客中是这样记载着。
  在 《The Java™ Tutorials》中,是有关于这部分内容的说明的。首先是关于基本类型描述如下:

Primitive arguments, such as an int or a double, are passed into methods by value. This means that any changes to the values of the parameters exist only within the scope of the method. When the method returns, the parameters are gone and any changes to them are lost.

  即,原始参数通过值传递给方法。这意味着对参数值的任何更改都只存在于方法的范围内。当方法返回时,参数将消失,对它们的任何更改都将丢失。

  关于对象传递的描述如下:

Reference data type parameters, such as objects, are also passed into methods by value. This means that when the method returns, the passed-in reference still references the same object as before. However, the values of the object’s fields can be changed in the method, if they have the proper access level.

  也就是说,引用数据类型参数(如对象)也按值传递给方法。这意味着,当方法返回时,传入的引用仍然引用与以前相同的对象。但是,如果对象字段具有适当的访问级别,则可以在方法中更改这些字段的值。

  这些引用描述都是来自于官方文档,Java就是值传递,只不过是把对象的引用当做值传递给方法,这就很符合共享对象传递的定义了。
  其实Java中使用的求值策略就是传共享对象调用,也就是说,Java会将对象的地址的拷贝传递给被调函数的形式参数。只不过"传共享对象调用"这个词并不常用,所以Java社区的人通常说"Java是传值调用",这么说也没错,因为传共享对象调用其实是传值调用的一个特例。

数据类型

数据类型可分为两大类:

  • 基本类型:Java的八大基本类型。
  • 引用类型:对象,String等八大基本类型的封装类。

总结

我们知道,编程语言中需要进行方法间的参数传递,这个传递的策略叫做求值策略。

在程序设计中,求值策略有很多种,比较常见的就是值传递和引用传递。还有一种值传递的特例——共享对象传递。

值传递和引用传递最大的区别是传递的过程中有没有复制出一个副本来,如果是传递副本,那就是值传递,否则就是引用传递。

在Java中,其实是通过值传递实现的参数传递,只不过对于Java对象的传递,传递的内容是对象的引用。

我们可以总结说,Java中的求值策略是共享对象传递,这是完全正确的。

但是,为了让大家都能理解你说的,我们说Java中只有值传递,只不过传递的内容是对象的引用。这也是没毛病的。

但是,绝对不能认为Java中有引用传递。

posted @ 2020-09-10 12:12  琉璃丶冰芯  阅读(158)  评论(0编辑  收藏  举报