Java:简述Java中的类型传递问题(值传递,引用传递)

Java:简述Java中的类型传递问题(值传递,引用传递)


一、类型传递的简单认识

首先,让我们来看看如何定义值传递和引用传递。

  1. 值传递(pass by value): 在调用函数时将实际参数拷贝一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
  2. 引用传递(pass by reference): 在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

值传递和引用传递都是在发生函数调用时的一种参数求值策略,而非传递的内容类型(值类型(值)还是引用类型(指针) )。值类型/引用类型,是用于区分两种内存分配方式,值类型(值)在栈上分配,引用类型(指针)在堆上分配。一个描述内存分配方式,一个描述参数求值策略,两者之间无任何依赖或约束关系。

二、Java 代码中的类型传递

在 Java 中有基本类型如 int、boolean、char,也有对象类型如 Object、HashMap、Integer 等。

例子1:值传递(传值-值,不会影响到原数据)

public static void swap(int a, int b) {
    int x = a;
    a = b;
    b = x;
    System.out.println("swap a: " + a);
    System.out.println("swap b: " + b);
}

public static void main(String[] args) {
    int a = 3;
    int b = 4;
    swap(a, b);
    System.out.println("a = " + a);
    System.out.println("b = " + b);
}

上述代码的输出结果为:

swap a: 4
swap b: 3

a = 3
b = 4

int 是一个基本类型,我们将 2 个变量的值传入到 swap 方法中,它们实际的结构是这样的:
在这里插入图片描述

例子2:引用传递(传值-指针,会影响到原数据)

static class Foo {
    String name;

    public Foo(String name) {
        this.name = name;
    }
}

static void change(Foo myfoo) {
    System.out.println("change input myfoo.name: " + myfoo.name);

    myfoo.name = "FiFi";
    System.out.println("change new   myfoo.name: " + myfoo.name);
}

public static void main(String[] args) {
    Foo foo = new Foo("Jerry");
    change(foo);

    System.out.println(foo.name);
}

上述代码的输出结果为:

change input myfoo.name: Jerry
change new   myfoo.name: FiFi
FiFi

对于 change 方法接收到的 name 是 Jerry 我们可以理解,修改后方法内输出的 FiFi 也可以理解,因为在同一函数体内,变量的修改必然是可见的。需要解释的就是外部的修改也会发生变化,这是为啥呢?

解释:我们 new Foo(“Jerry”) 这句话会在堆上开辟一块内存空间,假设叫它 0xabcd 好了,把这块内存赋值给一个名为 foo 的引用类型。进入 change 方法的时候就会传递一份 foo引用类型的拷贝(参数入栈),我故意把这个变量名写为 myfoo 防止大家理解错误,myfoo 和 foo 这 2 个引用类型都指向同一块堆内存空间,在 change 方法内通过 myfoo 修改的值也会影响到其他指向这块内存的引用类型。

结论:对于引用类型,是通过传值的方式传引用。
在这里插入图片描述

例子3:引用传递(传值-指针,会影响到原数据)

static void change(Foo myfoo) {
    System.out.println("change input myfoo.name: " + myfoo.name);

    // 改变 foo 的指向,让它指向一个新的内存
    myfoo = new Foo("biezhi");
    System.out.println("change new   myfoo.name: " + myfoo.name);
}

public static void main(String[] args) {
    Foo foo = new Foo("Jerry");
    change(foo);

    System.out.println(foo.name);
}

上述代码的输出结果为:

change input myfoo.name: Jerry
change new   myfoo.name: biezhi
Jerry

如果 Java 中可以传递引用意味着 myfoo = new Foo(“biezhi”); 这行代码的修改会影响到之前的 foo 变量,会让 foo 和 myfoo 指向同一块新的地址,然后导致外部的 foo.name会输出 biezhi。

我们看到的结果不是这样的,正因为是传值(拷贝了一份)。这行代码会创建一个新对象,让 myfoo 指向新内存,而 foo 指向的还是旧的内存,所以 myfoo 的是不会影响到外部的。

例子4:基础数据类型的封装类

看到上面其实值传递的概念和理解已经比较清晰了,之前又遇到一个问题,来看看代码吧

static void change(Integer num) {
    System.out.println("change input num: " + num);
    num = 2333;
    System.out.println("change new   num: " + num);
}

public static void main(String[] args) {
    Integer num = 1;

    change(num);
    System.out.println(num);
}

上述代码的输出结果为:

1
2333
1

大家思考下:Integer 是引用类型吗?传递给 change 方法的参数修改后会影响外部吗?

Integer 创建出来的是一个对象(自动装箱),所以是引用类型。但是传递给 change 方法的参数修改后并不会影响到外部。

第二个问题为什么就不会影响到外部呢? 原因是这样的,Integer 这个类(还有 String、Double、Long等)比较特殊,它是 final 的,是不可变的。当我们使用 num = 2333; 这行代码去赋值的时候发生了什么?这行代码意味着给 num 又创建一个新的对象,所以方法内的 num 引用类型指向了 2333 所处的内存。当我们使用 = 进行操作时候修改的是引用,而不是一个单纯的赋值操作,相当于前面例子中的 myfoo = new Foo(“biezhi”)。如果你想实现 Integer 数值的修改可以试试 AtomicInteger 这个类。

三、小结

  1. 一个方法不能修改一个基本数据类型的参数。
  2. 一个方法可以修改一个对象参数的状态。
  3. 一个方法不能实现让对象参数引用一个新对象。

如果我们 分清 引用 和 引用传递 是什么,就很容易理解Java 中的值传递。


参考:

  1. https://mp.weixin.qq.com/s/pJ3K-wwrqa9RXr7s0nf_Bg
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页