从面试题开始,分析值传递和引用传递

268 阅读3分钟

前言

在Java编程中,参数传递的方式主要分为值传递和引用传递。这两种方式决定了方法内部对参数的修改如何影响原始变量。本文将详细探讨这两种传递方式的概念、应用及注意事项,并结合面试题,分析栈堆内存的情况。

一、值传递与引用传递的区别

  1. 值传递

    • 在方法调用时,将实际参数(原始变量)的副本传递给形式参数。
    • 方法内部对形式参数的修改不会影响实际参数。
    • 值传递适用于基本数据类型(如int、float、double等)和引用数据类型的包装类(如Integer、Float、Double等)。
  2. 引用传递

    • 在方法调用时,将实际参数的引用(内存地址)传递给形式参数。
    • 方法内部对形式参数的修改可能会影响实际参数。
    • 引用传递通常发生在对象类型上。

二、面试案例分析

我们来看一个面试题,代码如下,看起来就几行代码很简单,那么到时a和b最终结果是什么呢?

image.png

运行结果:输出的是AB B,而不是AB AB,是不是觉得有点惊讶,下面开始分析一下具体原因。

image.png

栈堆内存分析

  1. 初始状态

    • 在栈内存中,有两个变量ab,它们分别指向堆内存中的两个对象AB
    • 具体来说,a的内存地址是000x1,它指向的对象是Ab的内存地址是000x2,它指向的对象是B
  2. 方法调用

    • 当调用operator(a, b)时,栈内存中新增了两个局部变量xy
    • 这两个局部变量也分别指向堆内存中的对象AB,即x的内存地址是000x1y的内存地址是000x2
  3. 操作过程

    • operator方法内部,执行x.append(y)操作。此时,x指向的对象从A变成了AB,内存地址仍然是000x1
    • 接着执行y = x操作。此时,y不再指向原来的对象B,而是指向x所指向的对象AB,即y的内存地址也变成了000x1
  4. 最终状态

    • operator方法执行完毕后,栈内存中的变量ab的指向没有发生变化。
    • a仍然指向堆内存中的对象AB,内存地址是000x1b仍然指向堆内存中的对象B,内存地址是000x2
    • 因此,输出结果是AB B

如图最终栈堆内存图: image.png

如果改成下面代码,就可以看到四个变量最终的值了,跟我们上面分析的结果一样。

public class Test {
    public static void main(String[] args) {
        StringBuffer a = new StringBuffer("A");
        StringBuffer b = new StringBuffer("B");
        operator(a, b);
        System.out.println(a + " " + b);
    }

    public static void operator(StringBuffer x, StringBuffer y) {
        x.append(y);
        y = x;
        System.out.println("y:" + y + " ,x:" + x);
    }
}

这样就更加清晰看出,上述通过栈堆内存图分析是正确,运行结果如图所示:

image.png

三、总结

这个问题涉及到了变量的作用范围和方法参数传递机制:

  1. 变量作用范围

    • xy只在operator方法内部有效,不会影响外部变量ab
  2. 方法参数传递机制

    • 形参是基本数据类型时,传递的是数据值。
    • 形参是引用数据类型时,传递的是地址值。
    • 特殊类型如String、数组、包装类等对象是不可变的。

通过这个案例,可以清楚地看到值传递和引用传递的区别及其应用。希望本文能帮助读者更好地理解Java中的参数传递机制。