杰瑞科技汇

Java String传值是值传递还是引用传递?

Java 只有值传递(Pass-by-Value)

要明确一个最核心、最根本的原则:Java 语言中,所有的参数传递方式都是值传递

Java String传值是值传递还是引用传递?-图1
(图片来源网络,侵删)

没有“引用传递”(Pass-by-Reference)。

为什么 String 看起来又像是“引用传递”呢?这就需要我们深入理解“值传递”和“引用”在 Java 中的具体含义了。


理解“值传递”和“引用”

为了彻底搞懂这个问题,我们必须先区分两个概念:

  1. :就是数据本身。
  2. 引用:可以理解为内存地址的“拷贝”或“别名”,它指向一个对象在堆内存中的实际位置。

在 Java 中,变量分为两种基本类型:

Java String传值是值传递还是引用传递?-图2
(图片来源网络,侵删)
  • 基本数据类型:如 int, double, char, boolean 等,它们的变量中存储的就是值本身
  • 引用数据类型:如所有对象(String, ArrayList, 自定义类等)、数组,它们的变量中存储的不是对象本身,而是该对象在堆内存中的地址(即一个引用)

两种数据类型的传值表现

理解了上面的概念后,我们来看两种数据类型在方法调用时是如何“传值”的。

A. 传递基本数据类型

当传递一个基本数据类型(如 int)时,传递的是值的拷贝

示例代码:

public class Main {
    public static void main(String[] args) {
        int num = 10;
        System.out.println("调用方法前, main方法中的 num = " + num);
        changePrimitive(num);
        System.out.println("调用方法后, main方法中的 num = " + num);
    }
    public static void changePrimitive(int value) {
        // 这里的 value 是 main方法中 num 的一个值的拷贝
        value = 20; // 修改的是这个拷贝,不影响 main方法中的原始 num
        System.out.println("方法内部, value = " + value);
    }
}

执行结果:

Java String传值是值传递还是引用传递?-图3
(图片来源网络,侵删)
调用方法前, main方法中的 num = 10
方法内部, value = 20
调用方法后, main方法中的 num = 10

内存分析:

  1. main 方法中创建 int num = 10num 变量在栈内存中,值为 10
  2. 调用 changePrimitive(num) 时,将 num 的值 10 拷贝一份,传递给 changePrimitive 方法的 value 参数。
  3. changePrimitive 方法内部,value 是一个全新的、独立的变量,它有自己的内存空间,我们修改 value = 20,只是修改了这个拷贝的值。
  4. main 方法中的 num 变量从未被触及,所以它的值依然是 10

这非常直观,也很好理解。


B. 传递引用数据类型(如 String)

当传递一个引用数据类型(如 String)时,传递的是引用(地址)的拷贝

这才是问题的核心和容易混淆的地方。

示例代码:

public class Main {
    public static void main(String[] args) {
        String text = "Hello";
        System.out.println("调用方法前, main方法中的 text = " + text);
        System.out.println("调用方法前, text的地址 = " + System.identityHashCode(text));
        changeReference(text);
        System.out.println("调用方法后, main方法中的 text = " + text);
        System.out.println("调用方法后, text的地址 = " + System.identityHashCode(text));
    }
    public static void changeReference(String str) {
        System.out.println("方法内部, 初始的 str = " + str);
        System.out.println("方法内部, str的地址 = " + System.identityHashCode(str));
        // 尝试修改 str 指向的对象
        str = "World";
        System.out.println("方法内部, 修改后的 str = " + str);
        System.out.println("方法内部, 修改后str的地址 = " + System.identityHashCode(str));
    }
}

执行结果:

调用方法前, main方法中的 text = Hello
调用方法前, text的地址 = 460141958  // 地址A
方法内部, 初始的 str = Hello
方法内部, str的地址 = 460141958  // 地址A (和main中的text地址相同)
方法内部, 修改后的 str = World
方法内部, 修改后str的地址 = 1163157884 // 地址B (和main中的text地址不同)
调用方法后, main方法中的 text = Hello
调用方法后, text的地址 = 460141958  // 地址A

内存分析(关键步骤):

  1. main 方法中

    • String text = "Hello";
    • 在堆内存中创建了一个内容为 "Hello"String 对象。
    • 在栈内存中,变量 text 存储了这个对象的引用(地址A)
  2. 调用 changeReference(text)

    • 发生了值传递,传递的不是 text 本身,也不是 "Hello" 对象,而是 text 中存储的引用(地址A)的一个拷贝
    • 这个拷贝被赋值给了 changeReference 方法的参数 str
    • main 方法中的 textchangeReference 方法中的 str都指向了堆内存中同一个 "Hello" 对象(地址A),它们是两个不同的变量,但持有相同的引用。
  3. changeReference 方法内部

    • str = "World";
    • 这一步不是修改了 str 所指向的对象的内容,而是让 str 这个引用变量指向了一个全新的对象!
    • JVM 会在堆内存中创建一个新的 String 对象,内容为 "World"(地址B)。
    • str 变量的值从原来的地址A更改为新对象的地址B。
    • 这个操作只影响了 changeReference 方法内部的局部变量 strmain 方法中的 text 变量依然指向原来的 "Hello" 对象(地址A),所以它的值没有改变。

changeReference 方法内部,我们无法通过 str 来修改 main 方法中 text 所指向的那个 String 对象,因为我们修改的是 str 这个“地址拷贝”本身,让它指向了别处,而没有通过这个地址去修改原始对象的内容。


String 的特殊性:不可变性

上面的分析已经解释了为什么 String 在方法中不会被修改,而 String 的一个核心特性——不可变性——更是从根源上杜绝了这种情况。

  • 不可变性:意味着一旦一个 String 对象被创建,它的内容就不能被改变。
  • 任何看起来像是修改 String 的操作(如 str = "World"),实际上都是:
    1. 创建了一个新的 String 对象。
    2. 让引用变量指向这个新对象。

这与我们上面分析的内存过程完全吻合,正是因为 String 是不可变的,所以我们才无法写一个方法,让它修改传入的 String 对象的内容。

对比一个可变对象(如 StringBuilder)来加深理解:

public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");
        System.out.println("调用方法前, sb = " + sb);
        changeMutable(sb);
        System.out.println("调用方法后, sb = " + sb);
    }
    public static void changeMutable StringBuilder s) {
        // s 指向和 main 中 sb 相同的对象
        s.append(" World"); // 通过引用 s,修改了它所指向的对象的内容
    }
}

执行结果:

调用方法前, sb = Hello
调用方法后, sb = Hello World

分析:

  1. main 中的 sbchangeMutable 中的 s 指向同一个 StringBuilder 对象。
  2. s.append(" World") 是通过引用 s,找到了那个对象,并调用了它的 append 方法。
  3. StringBuilder可变的,append 方法会直接修改这个对象的内容。
  4. 因为 main 中的 sb 也指向同一个对象,所以它能看到内容的变化。

这个例子清晰地展示了:即使是引用传递(实际上是引用地址的拷贝),如果对象是可变的,我们就可以通过这个拷贝去修改原始对象。


特性 描述
Java 传值机制 永远是值传递
传基本类型 传递的是值的拷贝,方法内修改不影响外部变量。
传引用类型 (如 String) 传递的是引用(地址)的拷贝,方法内可以通过这个引用访问到原始对象。
String 的特殊性 不可变性:决定了你无法通过任何方法修改一个已存在的 String 对象的内容。
结合传值机制:当你尝试在方法中“修改” String 变量时,你实际上只是让方法内的引用指向了一个新对象,而外部的原始引用保持不变。
关键区别 能否在外部看到变化,取决于:
对象是否可变(如 StringBuilder 可变,String 不可变)。
在方法内是修改了对象内容,还是修改了引用本身

当你把一个 String 对象传给一个方法时,你其实是给了这个方法一个“寻宝图”的复印件,这个复印件能帮你找到宝藏(原始对象)。String 的宝藏本身是封印的,无法修改的,你更常见的操作不是去修改宝藏,而是把这张寻宝图扔掉,换成一张指向全新宝藏的地图,这当然不会影响原来那张寻宝图的所有者。

分享:
扫描分享到社交APP
上一篇
下一篇