杰瑞科技汇

Java字符串创建对象有哪些方式?

核心要点

在 Java 中,创建 String 对象主要有两种方式:

Java字符串创建对象有哪些方式?-图1
(图片来源网络,侵删)
  1. 字面量赋值 (Literal Assignment)
  2. new 关键字构造 (Using the new Keyword)

这两种方式在底层实现和内存分配上有着根本的区别


字面量赋值

这是最常见、最简单的创建 String 的方式。

String str1 = "Hello World";
String str2 = "Hello World";

底层原理:字符串常量池

当你使用字面量创建 String 对象时,JVM(Java 虚拟机)会执行以下操作:

  1. 检查常量池:JVM 首先会在一个特殊的内存区域叫做字符串常量池 中查找是否存在 "Hello World" 这个字符串。
  2. 存在则复用:如果常量池中已经存在 "Hello World",JVM 就不会创建新的对象,而是直接将引用 str1 指向常量池中已有的对象。str2 也会指向同一个对象。
  3. 不存在则创建:如果常量池中没有 "Hello World",JVM 就会在常量池中创建这个字符串对象,然后将引用 str1 指向它。str2 同样会指向这个新创建的对象。

内存示意图:

Java字符串创建对象有哪些方式?-图2
(图片来源网络,侵删)
栈内存
+-------+
| str1  | --------+
+-------+          |
                    |
+-------+          |
| str2  | --------+
+-------+          |
                    |
堆内存 - 字符串常量池
+-----------------------+
| "Hello World" (唯一)  |
+-----------------------+

通过字面量方式创建的多个相同的字符串,它们在内存中实际上是同一个对象,这极大地节省了内存。

String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2); // 输出 true
// == 比较的是两个对象的内存地址(引用),因为 str1 和 str2 指向同一个对象,所以结果为 true。

new 关键字构造

这种方式通过调用 String 类的构造函数来创建对象。

String str3 = new String("Hello World");
String str4 = new String("Hello World");

底层原理:堆内存

当你使用 new 关键字创建 String 对象时,JVM 的操作如下:

  1. 检查常量池:JVM 依然会先去字符串常量池中查找 "Hello World"
  2. 在堆中创建新对象
    • 如果常量池中没有 "Hello World",JVM 会在常量池中创建它。
    • JVM 一定会堆内存 中创建一个新的 String 对象,并用常量池中的 "Hello World" 来初始化这个堆中的对象。
    • 如果常量池中已经有了 "Hello World",JVM 也依然会在堆内存中创建一个新的、与常量池中内容相同的 String 对象。

内存示意图:

Java字符串创建对象有哪些方式?-图3
(图片来源网络,侵删)
栈内存
+-------+
| str3  | --------+
+-------+          |
                    |
+-------+          |
| str4  | --------+
+-------+          |
                    |
堆内存
+-----------------------+
| String 对象 (str3)    |  内容指向常量池的 "Hello World"
+-----------------------+
+-----------------------+
| String 对象 (str4)    |  内容指向常量池的 "Hello World"
+-----------------------+
                    |
字符串常量池 (在堆内存的一个特殊区域)
+-----------------------+
| "Hello World" (唯一)  |
+-----------------------+

每次使用 new 关键字,都会在堆内存 中创建一个新的 String 对象,即使它们的内容完全相同,这些堆中的对象是相互独立的。

String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str3 == str4); // 输出 false
// == 比较的是内存地址,str3 和 str4 是堆中两个不同的对象,所以结果为 false。

两种方式的混合使用

当字面量和 new 关键字混合使用时,情况会变得更有趣。

String str5 = "Hello";       // 1. 在常量池中创建 "Hello"
String str6 = new String("Hello"); // 2. 发现常量池有 "Hello",就在堆中创建一个新的 "Hello" 对象

内存示意图:

栈内存
+-------+
| str5  | --------+
+-------+          |
                    |
+-------+          |
| str6  | --------+
+-------+          |
                    |
堆内存
+-----------------------+
| String 对象 (str6)    |
+-----------------------+
                    |
字符串常量池
+-----------------------+
| "Hello" (str5指向的)  |
+-----------------------+
System.out.println(str5 == str6); // 输出 false
// str5 指向常量池,str6 指向堆内存,是两个完全不同的对象。
System.out.println(str5.equals(str6)); // 输出 true
// .equals() 方法比较的是字符串的内容,"Hello" == "Hello",所以结果为 true。

性能与最佳实践

  1. 优先使用字面量:在绝大多数情况下,推荐使用字面量 String s = "text"; 来创建字符串,因为它可以利用字符串常量池的缓存机制,避免在堆中重复创建相同内容的对象,从而提高性能并减少内存消耗。

  2. 何时使用 new

    • 当你明确需要一个全新的、独立的 String 对象时,即使内容相同。
    • 在某些需要频繁修改字符串内容的场景下(例如在循环中拼接字符串),使用 new 创建新的对象可能比使用 StringBuilder 更直观(但性能更差)。
    • 在进行反射或某些需要精确控制对象创建的底层操作时。
  3. 关于字符串拼接

    • 使用 号拼接字符串时,如果编译器能确定拼接后的值是常量,它会在编译时就将其合并成一个字符串,并放入常量池。
    • 如果拼接的变量中有一个或以上是运行时才能确定的变量,那么编译器会使用 StringBuilder 来优化拼接过程,最终在堆内存中生成新的 String 对象。
    // 编译时优化,等同于 String s = "HelloWorld";
    String s1 = "Hello" + "World";
    // 运行时拼接,使用 StringBuilder
    String a = "Hello";
    String b = "World";
    String s2 = a + b; // s2 是在堆中创建的新对象

总结表格

特性 字面量赋值 (String s = "text";) new 关键字 (new String("text");)
创建位置 字符串常量池 堆内存
内存效率 的字符串只存一份。 ,每次都会创建新的对象。
对象唯一性 相同字面量创建的字符串是同一个对象。 每次创建的都是新的、独立的对象。
比较 相同字面量创建的字符串, 结果为 true 不同 new 创建的字符串, 结果为 false
.equals() 比较 ,结果取决于内容是否相同。 ,结果取决于内容是否相同。
使用场景 推荐,绝大多数情况下都应使用。 特殊需求,如需要独立对象或进行底层操作。

理解这两种创建方式的区别,是掌握 Java 内存管理和编写高性能 Java 代码的重要一步。

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