Java基础 | 值传递和引用传递

通过这两天的实习面试,发现自己的基础实在烂的不行,先不打算投简历了,安安心心的在家学了几个月,把最基本的东西全部搞懂再说。之前看到别人也在写类似的模块,觉得挺好的,因此我打算每天(也有可能几天..)整理一个常考的知识点,帮助自己整理和巩固,加油把

一、前言

先分清楚两个概念

  • 按值传递:方法接收的是调用者提供的值
  • 按引用传递:方法接收的是调用者提供的变量地址

《Java 核心技术》中说:“Java程序设计语言总是采用按值来调用的,即,方法得到的是所有参数值的一个拷贝。因为传递过来的相当于是一个副本,因此方法只能改变这个副本所对应的值或者对象。

二、分析

2.1 将基本数据类型作为参数传递

我们可以直接看一个值传递的例子

public void swap(int x, int y) {
    int temp = x;
    x = y;
    y = temp;
}

@Test
void testObject() {
    int a = 100;
    int b = 200;
    swap(100, 200);
    System.out.println("a: " + a);
    System.out.println("b: " + b);
}

//输出
a: 100
b: 200

可见,a 和 b 的值在经过“所谓的”交换函数之后,并没能把值交互。这是为什么呢?

这就是因为java是值传递的。也就是说,我们在调用一个需要传递参数的函数时,传递给函数的参数并不是我们传进去的参数本身,而是它的副本。

以这个例子来说,当我们调用 swap 这个函数时,并不是传入的真正的 a 和 b 这两个参数,而是他们俩的复制品,比如我们定义这两个复制品为 x 和 y,此时 x 会指向另一个 100,y 会指向另一个 200。之后在方法 swap 中的所有操作,其实都是基于这两个复制出来的变量 x 和 y 进行着的。尽管 x 和 y 的值确实交换了,但是他们值的改变并不能影响到 a 和 b。

再来看一个例子:

void foo(int val) {
    val = 100;
}

void foo1(String text) {
    text = "win";
}

@Test
public void print() {
    int val = 200;
    foo(val);
    System.out.println(val);

    String text = "hello";
    foo1(text);
    System.out.println(text);
}

无可厚非,输出分别是 200 和 hello。可以用图片来描述一下流程

首先,在调用方法之前,会先创建一个形参 val

当调用 foo 函数的时候,实参 val 将自身的值拷贝一份,将拷贝的副本传给形参
这里写图片描述

扫描二维码关注公众号,回复: 3115762 查看本文章

当执行函数中的语句时,其实都是对拷贝的那个参数,即形参 val 进行赋值,可以看到,我们将他直接变为了 100,而实际的参数 val,还是原来的 200
这里写图片描述

2.2 将对象引用作为参数传递

顾名思义,就是方法的参数是一个类的引用

class People {
    int age;
}

@Test
void testObject() {
    People p1 = new People();
    People p2 = new People();
    p1.age = 10;
    p2.age = 15;
    System.out.println("p1.age: " + p1.age + " p2.age: " + p2.age);

    p1 = p2;
    System.out.println("p1.age: " + p1.age + " p2.age: " + p2.age);

    p1.age = 30;
    System.out.println("p1.age: " + p1.age + " p2.age: " + p2.age);
}

//输出
p1.age: 10 p2.age: 15
p1.age: 15 p2.age: 15
p1.age: 30 p2.age: 30

通过图示我们可以分析一下

首先在栈中建立两个引用 p1 和 p2,分别指向堆中 new 出来的对象
这里写图片描述

执行 p1=p2 这个语句,把栈中 p1 指向 p2 在堆中指向的位置,即第二个 People 对象,此时 p1 和 p2 都指向了堆中第二个 People 对象
这里写图片描述

执行 p1.age=30 这个语句,即把第二个 People 对象的 age 属性变为30,由于 p1 和 p2 都指向这个对象,因此 p1 和 p2 的 age 属性都是 30
这里写图片描述

我们可以再来看一个例子

void foo(StringBuffer stringBuffer) {
    stringBuffer.append("world");
}

@Test
void bufferTest2() {
    StringBuffer sb = new StringBuffer("hello");
    foo(sb);
    System.out.println(sb);
}

输出结果是 helloworld,表明原来的值已被改变。照例,我们画个图

一开始,栈中的 StringBuffer 引用指向堆中的 StringBuffer 对象,该对象里面的值为 “hello”
这里写图片描述

当使用方法时,形参会先产生一个 StringBuffer 引用 stringBuffer,然后指向堆中的对象
这里写图片描述

然后调用方法,append 方法直接改变的就是原来 String 的值。可以看到,此时堆中 StringBuffer 对象中的值已经改变,此时两个引用 sb 和 stringBuffer 都指向同一个对象
这里写图片描述

我们再来看另一个例子

void foo1(StringBuffer stringBuffer) {
    stringBuffer = new StringBuffer("world");
}

@Test
void bufferTest2() {
    StringBuffer sb = new StringBuffer("hello");

    foo1(sb);
    System.out.println(sb);
}

此时输出的值没有改变,还是 “hello”。这是为什么呢?

当调用 fool 方法的时候,实际在栈中又新创建了一个 StringBuffer 的引用 stringBuffer,这个引用重新指向了一个新的 StringBuffer 对象。
这里写图片描述

所以即使当执行方法之后,输出 sb,依旧还是原来的 sb 指向的对象中的值,即 “hello”

2.3 值传递和引用传递

由于 C++ 有值传递和引用传递两种方式,那么 Java 呢?实际上,Java 是采用的值传递

class Employee {
    int x;

    Employee(int a) {
        this.x = a;
    }
}

public class ObjectTest2 {
    static void swap(Employee x, Employee y){
        Employee temp = x;
        x = y;
        y = temp;
    }

    public static void main(String[] args) {
        Employee employee = new Employee(100);
        Employee employee1 = new Employee(200);
        System.out.println("交换前:" + employee.x + " " + employee1.x);
        swap(employee, employee1);
        System.out.println("交换后:" + employee.x + " " + employee1.x);
    }
}

结果是:

交换前:100 200
交换后:100 200

这个例子很明显,如果 Java 是引用传递,那么在调用完那个方法之后,应该可以实现数据的交换,但实际上,并没有交换。

可以看到,两个对象引用传到方法之后,拷贝出了两个对象引用 x, y,x 指向 的是第一个 Employee 对象,y指向的是第二个。然后 swap() 方法中交换的其实仅仅是拷贝所指向的地址

最后,方法结束,x 和 y 也被回收,而最原始的两个对象引用 employee 和 employee1 还是指向之前的两个对象

那为什么不是引用传递呢?

其实可以联想一下 C++ 是如何进行引用传递的

void swap(int *p1, int *p2){
    int *temp;
    temp = p1;
    p1 = p2;
    p2 = temp;
}

可以看到,同样也是传入两个参数的地址,然后直接改变两个参数所在的内存地址。这个时候从内存中取出对应的数值应该就是相反的值了

然后我们再看 Java,同样传入的是地址,那么他是如何实现的呢?传入一个引用,拷贝一份,再传入一个,再拷贝一个,之后的事情就很简单了,直接对拷贝的引用所指向的对象进行操作。因此,他和 C++ 的引用传递是是不一样的。

因此,Java 本至上还是值传递

结论
  • 一个方法不能修改一个基本数据类型的参数
  • 一个方法可以改变一个对象参数的状态
  • 一个方法不能让对象参数引用一个新的对象

猜你喜欢

转载自blog.csdn.net/babycan5/article/details/81867206