杰瑞科技汇

Java中String是值传递吗?

核心概念:Java 只有值传递

最重要的一点必须明确:Java 中只有值传递

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

“值传递”意味着,在调用一个方法时,你传递给方法的是参数值的一个拷贝,而不是参数本身。

  • 对于基本数据类型:拷贝的是值本身,方法内对这个拷贝的修改,不会影响到方法外的原始变量。
  • 对于引用数据类型:拷贝的是引用地址值,方法内可以通过这个地址访问到堆中的同一个对象,但方法内对这个地址本身的修改(例如让它指向一个新对象),不会影响到方法外的原始引用。

String 是引用类型,但它很特殊

String 在 Java 中是一个不可变类,这意味着一旦一个 String 对象被创建,它的内容就不能被改变,任何对 String 的操作,concat()replace()substring() 等,都不会修改原始的 String 对象,而是会返回一个新的 String 对象

这个特性是理解 String 在方法传递中行为的关键。


深入分析 String 的值传递行为

我们通过几个经典的代码示例来一步步拆解。

示例 1:方法内修改 String 的内容(实际上是创建新对象)

public class StringPassing {
    public static void main(String[] args) {
        String originalStr = "Hello";
        System.out.println("调用方法前 originalStr: " + originalStr); // 输出: Hello
        modifyString(originalStr);
        System.out.println("调用方法后 originalStr: " + originalStr); // 输出: ??? 
    }
    public static void modifyString(String str) {
        // str 是 main 方法中 originalStr 的一个引用拷贝
        // str 和 originalStr 指向堆中同一个 "Hello" 对象
        System.out.println("方法内 str 初始值: " + str); // 输出: Hello
        // str.concat(" World") 不会修改 "Hello" 这个对象
        // 它会创建一个新的 String 对象 "Hello World",并让 str 指向这个新对象
        str = str.concat(" World");
        System.out.println("方法内 str 修改后: " + str); // 输出: Hello World
    }
}

执行结果:

调用方法前 originalStr: Hello
方法内 str 初始值: Hello
方法内 str 修改后: Hello World
调用方法后 originalStr: Hello

图解分析:

  1. main 方法开始:

    • 在栈中创建一个引用变量 originalStr
    • 在字符串常量池(属于堆的特殊区域)中创建 String 对象 "Hello"
    • originalStr 指向 "Hello"
    main 栈:             堆 (字符串常量池)
    +-----------+       +----------------+
    |originalStr| ----> | "Hello"        |
    +-----------+       +----------------+
  2. 调用 modifyString(originalStr)

    • 值传递发生originalStr 中存储的引用地址值拷贝了一份。
    • 这个拷贝的地址值被赋给了 modifyString 方法的参数 str
    • originalStrstr 都指向同一个 "Hello" 对象。
    main 栈:             modifyString 栈:      堆 (字符串常量池)
    +-----------+       +-----------+       +----------------+
    |originalStr| ----> | str       | ----> | "Hello"        |
    +-----------+       +-----------+       +----------------+
  3. modifyString 方法内部执行 str = str.concat(" World");

    • str.concat(" World") 方法被调用,由于 String 不可变,它不会修改 "Hello"
    • 它会在字符串常量池中创建一个全新的 String 对象"Hello World"
    • 赋值语句 str = ... 会将 str 这个引用变量重新指向这个新创建的 "Hello World" 对象。
    • 关键点:这个操作只改变了 modifyString 方法栈中局部变量 str 的指向,它对 main 方法中的 originalStr 没有任何影响。
    main 栈:             modifyString 栈:      堆 (字符串常量池)
    +-----------+       +-----------+       +----------------+
    |originalStr| ----> | str       |        | "Hello"        |
    +-----------+       +-----+-----+       +----------------+
                             |                      |
                             |                      +-----> | "Hello World" |
                             |                               +----------------+
                             +--------------------------------+
  4. modifyString 方法结束:

    • str 这个局部变量随着方法的结束而被销毁。
    • originalStr 仍然指向那个从未改变的 "Hello" 对象。
    main 栈:             堆 (字符串常量池)
    +-----------+       +----------------+
    |originalStr| ----> | "Hello"        |
    +-----------+       +----------------+

在方法内尝试通过 str 修改 String 的内容,实际上只是让 str 指向了一个新对象,原始的 originalStr 指向的对象和引用都保持不变。


示例 2:通过 StringBuilder 修改(可变对象对比)

为了更好地理解 String 的不可变性,我们来看一个可变的对象,StringBuilder

public class StringBuilderPassing {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");
        System.out.println("调用方法前 sb: " + sb); // 输出: Hello
        modifyStringBuilder(sb);
        System.out.println("调用方法后 sb: " + sb); // 输出: Hello World!
    }
    public static void modifyStringBuilder(StringBuilder strBuilder) {
        System.out.println("方法内 strBuilder 初始值: " + strBuilder); // 输出: Hello
        // strBuilder.append() 会直接修改 strBuilder 指向的对象
        strBuilder.append(" World!");
        System.out.println("方法内 strBuilder 修改后: " + strBuilder); // 输出: Hello World!
    }
}

执行结果:

调用方法前 sb: Hello
方法内 strBuilder 初始值: Hello
方法内 strBuilder 修改后: Hello World!
调用方法后 sb: Hello World!

图解分析:

  1. main 方法开始:

    • sb 指向堆中的 StringBuilder 对象。
    main 栈:             堆
    +-----------+       +---------------------------+
    | sb        | ----> | StringBuilder (value="Hello") |
    +-----------+       +---------------------------+
  2. 调用 modifyStringBuilder(sb)

    • sb 的引用地址被拷贝一份,赋给 strBuilder
    • sbstrBuilder 指向同一个 StringBuilder 对象。
    main 栈:             modifyStringBuilder 栈:      堆
    +-----------+       +----------------+       +---------------------------+
    | sb        | ----> | strBuilder    | ----> | StringBuilder (value="Hello") |
    +-----------+       +----------------+       +---------------------------+
  3. modifyStringBuilder 方法内部执行 strBuilder.append(" World!");

    • StringBuilder可变的。append() 方法会直接修改 strBuilder 所指向的那个对象的内容。
    • 对象的值从 "Hello" 变成了 "Hello World!"
    • 因为 sbstrBuilder 指向的是同一个对象,sb 能看到这个变化。
    main 栈:             modifyStringBuilder 栈:      堆
    +-----------+       +----------------+       +---------------------------+
    | sb        | ----> | strBuilder    | ----> | StringBuilder (value="Hello World!") |
    +-----------+       +----------------+       +---------------------------+

结论对比:

  • String:方法内只能让引用指向新对象,无法修改旧对象。
  • StringBuilder:方法内可以通过引用直接修改对象本身

特性 Java 传递方式 String 的行为 StringBuilder 的行为
传递机制 值传递 传递引用地址的拷贝 传递引用地址的拷贝
对象可变性 - 不可变 可变
方法内修改 - 无法修改原始对象,只能让参数引用指向一个新对象,原始引用不受影响。 可以修改原始对象,因为方法内和原始引用指向同一个对象,修改是可见的。
核心原因 - String 的不可变性 + 值传递。 StringBuilder 的可变性 + 值传递。

一句话概括:

在 Java 中,String 作为参数传递时,传递的是其引用地址的拷贝,由于 String 对象本身不可变,方法内无法修改原始对象,只能创建新对象并让拷贝的引用指向它,因此原始 String 变量在方法调用后保持不变,这常常被误解为“引用传递”,但其本质仍然是“值传递”。

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