杰瑞科技汇

java string 引用类型

一句话总结:String 在 Java 中是一个对象(引用类型),而不是基本数据类型。 它的值(字符序列)存储在堆内存中,而变量(引用)则存储在栈内存中,指向这个 String 对象。

java string 引用类型-图1
(图片来源网络,侵删)

为什么说 String 是引用类型?

在 Java 中,数据类型分为两大类:

  1. 基本数据类型

    • 共 8 种:byte, short, int, long, float, double, char, boolean
    • 它们不是对象,变量中直接存储的是值本身
    • int a = 10;,变量 a 的栈空间里直接放着 10 这个数值。
  2. 引用数据类型

    • 除了 8 种基本类型,其他所有类型都是引用类型,包括:数组、类(如 String, Integer)、接口等。
    • 变量中存储的不是对象本身,而是指向对象在内存中地址的引用(可以理解为指针)
    • String 是 Java 中最常用的一个类,所以它自然也是引用类型。

String 的内存模型:

java string 引用类型-图2
(图片来源网络,侵删)
String str = "Hello";

当这行代码执行时,内存中发生了什么?

  1. 栈内存:创建一个名为 str 的引用变量。str 本身不包含 "Hello",它只是一个引用。
  2. 堆内存:JVM 在堆内存中创建一个 String 对象,并将字符序列 "Hello" 存储在这个对象中。
  3. 关联:将 str 这个引用指向堆内存中 "Hello" 对象的地址。

String 的特殊性:不可变性

String 是一个非常特殊的引用类型,它的核心特性是 不可变性

  • 什么是不可变性? 一旦一个 String 对象被创建,它的内容(内部的字符数组)就不能被修改。
  • 如何实现的? String 类内部使用一个 finalchar[] 数组来存储字符,并且这个数组没有提供任何修改其内容的方法(如 setCharAt),所有看似修改字符串的方法(如 substring(), replace(), concat()),实际上都创建并返回了一个新的 String 对象,而原始对象保持不变。

示例:

String s1 = "hello";
String s2 = s1.replace('h', 'H');
System.out.println(s1); // 输出: hello
System.out.println(s2); // 输出: Hello
  • s1 指向 "hello" 对象。
  • s1.replace(...) 方法执行后,并没有修改 "hello" 这个对象,而是创建了一个新的 String 对象 "Hello"
  • s2 被赋值为这个新对象的引用。
  • 内存中有两个 String 对象:"hello" 和 "Hello"。s1 仍然指向 "hello"。

String 的两种创建方式及其区别

理解了引用类型和不可变性,就能明白 String 两种创建方式的根本区别。

java string 引用类型-图3
(图片来源网络,侵删)

字面量赋值 (String Literal Pool - 字符串常量池)

String str1 = "hello";
String str2 = "hello";
  • 过程
    1. 当执行 String str1 = "hello"; 时,JVM 会首先在字符串常量池 中查找是否存在 "hello" 这个字符串。
    2. 如果不存在,就在池中创建一个新的 "hello" 对象,并将 str1 引用它。
    3. 当执行 String str2 = "hello"; 时,JVM 再次在字符串常量池中查找。
    4. 发现已经存在 "hello" 对象,于是不再创建新对象,直接让 str2 引用池中已有的 "hello" 对象。
  • 结果str1str2 指向的是同一个对象
  • 验证
    System.out.println(str1 == str2); // 输出: true
    System.out.println(str1.equals(str2)); // 输出: true
    • 比较的是两个引用是否指向同一个内存地址(对象)。
    • equals()String 类重写了 equals 方法,比较的是两个字符串的内容是否相同。

new 关键字创建 (堆内存)

String str3 = new String("hello");
String str4 = new String("hello");
  • 过程
    1. 当执行 String str3 = new String("hello"); 时,JVM 会这样做:
      • 在字符串常量池中检查 "hello" 是否存在,如果不存在,则创建一个并放入池中。(如果已经存在,则跳过此步)。
      • 在堆内存中,一定会创建一个新的 String 对象,并用池中的 "hello" 来初始化这个新对象。
      • str3 引用这个堆中的新对象。
    2. str4 的创建过程同理,会在堆内存中再创建一个全新的、内容相同的 String 对象。
  • 结果str3str4 指向的是两个不同的对象,虽然它们的内容相同,但内存地址不同。
  • 验证
    System.out.println(str3 == str4); // 输出: false
    System.out.println(str3.equals(str4)); // 输出: true

号拼接的底层原理

在 Java 中,使用 号拼接字符串是一个非常常见的操作,它的底层原理与 StringBuilder/StringBuffer 密切相关。

  • 在循环外使用 : 号用于连接编译时就能确定的常量字符串,编译器会直接在编译期间完成拼接,优化成一个字符串。

    // 编译后会被优化为 String s = "abc";
    String s = "a" + "b" + "c";
  • 在循环内或使用变量拼接: 号的操作数中包含变量,或者在一个循环中,Java 编译器会将其优化为使用 StringBuilder(在单线程环境下)或 StringBuffer(在多线程环境下)来构建字符串。

    示例:

    String s = "hello";
    s = s + " world";

    这行代码的等效过程(JVM 内部优化后的大致逻辑)是:

    1. 创建一个 StringBuilder 对象。
    2. 调用 StringBuilder.append("hello")
    3. 调用 StringBuilder.append(" world")
    4. 调用 StringBuilder.toString(),这个方法会创建一个新的 String 对象是 "hello world"。
    5. s 的引用指向这个新创建的 "hello world" 对象。

    注意:每次使用 拼接,都可能意味着创建一个新的 String 对象,在循环中进行大量拼接,如果使用 ,会频繁创建新对象,导致性能下降,在循环中应手动使用 StringBuilder


intern() 方法

intern()String 类的一个方法,它用于将字符串显式地放入字符串常量池中。

  • 作用

    1. 如果调用 intern() 的字符串内容已经在常量池中存在,则返回池中该字符串的引用。
    2. 如果不在,则将该字符串的内容复制一份到常量池中,并返回池中这个新复制的字符串的引用。
  • 示例

    String s1 = new String("hello"); // s1 指向堆中的 "hello"
    String s2 = s1.intern();         // s2 指向常量池中的 "hello"
    String s3 = "hello";             // s3 也指向常量池中的 "hello"
    System.out.println(s1 == s2); // false, s1在堆,s2在池
    System.out.println(s2 == s3); // true, s2和s3都指向池中的同一个对象

    intern() 方法在某些需要极致优化内存和比较对象身份的场景下会用到,但在日常开发中不常用。


特性 描述
类型 String 是一个引用类型,本质上是 java.lang.String 类的实例。
内存存储 存储在堆内存中,变量(引用)存储在栈内存中,指向堆对象。
核心特性 不可变性,一旦创建,内容不能改变,任何修改操作都会返回一个新对象。
创建方式 字面量 :创建的字符串会进入字符串常量池的字符串只会有一份。
new:每次都会在堆内存中创建一个全新的对象,即使内容相同。
拼接 底层由编译器优化为使用 StringBuilderStringBuffer,在循环中应避免使用 以防性能问题。
比较 :比较引用(地址)是否相同。
equals():比较字符串内容是否相同。
intern() 手动将字符串放入字符串常量池,并返回池中引用。

理解这些概念,你就能清晰地分析 String 相关代码的内存行为和执行结果,写出更高效、更健壮的代码。

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