• 关于java是值传递还是引用传递


    一、概念

    实际上对这两种传递方式,知乎上有个回答说得很好:

    值传递和引用传递,属于函数调用时参数的求值策略(Evaluation Strategy),这是对调用函数时,求值传值的方式的描述,而非传递的内容的类型(内容指:是值类型还是引用类型,是值还是指针)。

    值类型/引用类型,是用于区分两种内存分配方式,值类型在调用栈上分配,引用类型在堆上分配。

    一个描述内存分配方式,一个描述参数求值策略,两者之间无任何依赖或约束关系

    实际上意思就是,在函数调用时,不要通过传进去的参数类型去判断是值传递还是引用传递,而是要看这种语言具体的求值策略。

    二、详解

    在函数调用过程中,调用方提供实参,这些实参可以是常量:

    Call(1);             // 常量
    Call(x);             // 变量
    Call(2 * x + 1);     // 常量与变量的组合
    Call(GetNumber());   // 对其它函数的调用

    但是所有这些实参的形式,都统称为表达式(Expression)

    求值(Evaluation)即是指对这些表达式的简化并求解其值的过程。

    求值策略(值传递和引用传递)的关注的点在于,这些表达式在调用函数的过程中,求值的时机值的形式的选取等问题。求值的时机,可以是在函数调用前,也可以是在函数调用后,由被调用者自己求值。这里所谓调用后求值,可以理解为Lazy Load或On Demand的一种求值方式。

    除了值传递和引用传递,还有一些其它的求值策略,详见下表:

    求值策略

    求值时间

    传值方式

    值传递(Pass by value)

    调用前

    值的结果(是原值的副本)

    引用传递(Pass by reference)

    调用前

    原值(原始对象,无副本)

    名传递(Pass by name)

    调用后(用到才求值)

    与值无关的一个名

    重点是值传递和引用传递。上面给出的传值方式的表述有些单薄,下表列出了一些二者在行为表象上的区别:

     

    值传递

    引用传递

    根本区别

    会创建副本(Copy)

    不创建副本

    结果

    函数中无法改变原始对象

    函数中可以改变原始对象

    这里的改变不是指mutate, 而是change,指把一个变量指向另一个对象,而不是指仅仅改变属性或是成员什么的,所以说Java是Pass by value,原因是它调用时Copy,实参不能指向另一个对象,而不是因为被传递的东西本质上是个Value。

    这些行为,与参数类型是值类型还是引用类型无关。对于值传递,无论是值类型还是引用类型,都会在调用栈上创建一个副本,不同是,对于值类型而言,这个副本就是整个原始值的复制而对于引用类型而言,由于引用类型的实例在堆中,在栈上只有它的一个引用(一般情况下是指针),其副本也只是这个引用的复制,而不是整个原始对象的复制。

    综上所述,对于Java的函数调用方式最准确的描述是:参数藉由值传递方式,传递的值是个引用。(句中两个“值”不是一个意思,第一个值是evaluation result,第二个值是value content)。

    三、例子

    1. 值类型与引用类型

    int num = 10;
    String str = "hello";

    1

    num是基本类型,值就直接保存在变量中。而str是引用类型,变量中保存的只是实际对象的地址。一般称这种变量为"引用",引用指向实际对象,实际对象中保存着内容,比较特别的是,string对象是不可变对象

    2. 函数调用

    1) 基本类型

    void foo(int value) {
        value = 100;
    }
    foo(num); // num 没有被改变

    传进foo()里的只是num的一个副本。

    2) 没有提供改变自身方法的引用类型

    void foo(String text) {
        text = "world";
    }
    str=”hello”
    foo(str); // str 也没有被改变

    整个过程如下图所示:

    11

    3) 提供了改变自身方法的引用类型

    StringBuilder sb = new StringBuilder("iphone");
    void foo(StringBuilder builder) {
        builder.append("4");
    }
    foo(sb); // sb指向内容被改变了,变成了"iphone4",但本身还是指向同样的地址。

    append前:

    2

    append后:

    3

    4) 提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符

    StringBuilder sb = new StringBuilder("iphone");
    void foo(StringBuilder builder) {
        builder = new StringBuilder("ipad");
    }
    foo(sb); // sb指向内容没有被改变,还是 "iphone"。

    赋值前:

    4

    赋值后:

    5

    四、参考

    1. 知乎关于Java 到底是值传递还是引用传递的回答之一

    2. 知乎关于Java 到底是值传递还是引用传递的回答之二

    (完)

  • 相关阅读:
    md转html,并带目录结构
    vue05
    vue04
    mysql索引及调优
    mysql的锁与事务
    python基本数据类型的操作
    redis集群
    docker 学习(四)
    MongoDB基本操作
    MongoDB基础
  • 原文地址:https://www.cnblogs.com/harrymore/p/9140136.html
Copyright © 2020-2023  润新知