字面量和运行时常量池

字面量

在Java中,字面量是指直接使用在代码中的常量值。例如,"Vincent""hello"就是字符串字面量,1233.14则是整型和浮点型字面量。字面量可以直接在代码中使用,不需要使用new关键字来创建对象。

在使用字面量创建字符串时,Java会首先在编译时将这些字符串放入一个特殊的存储区域,这个区域被称为编译时常量池。编译时常量池是每个类的常量池的一部分,用于存储类的静态常量和字面量。

例如,在以下代码中:

String str1 = "Vincent";
String str2 = "hello";

字面量"Vincent"和"hello"在编译时会被放入编译时常量池。在运行时,如果有多个字符串使用相同的字面量,它们会共享同一个在编译时常量池中的对象。

运行时常量池

运行时常量池是在运行时创建的,用于存储在类加载后动态生成的常量。它是在方法区中的一部分,用于存储类的常量、静态变量、字符串字面量等信息。

在运行时常量池中,除了存储字面量外,还可以动态生成其他常量。例如,通过调用intern()方法可以将一个字符串添加到运行时常量池中。

让我们通过代码实现来加深理解:

public class RuntimeConstantPoolDemo {
    public static void main(String[] args) {
        String str1 = "Vincent"; // 在编译时常量池中创建 "Vincent" 字符串对象
        String str2 = "hello"; // 在编译时常量池中创建 "hello" 字符串对象

        String str3 = "Vincent"; // 字符串常量"Vincent"已存在于编译时常量池中,直接返回引用

        String str4 = new String("Vincent"); // 在堆中创建一个新的字符串对象

        String str5 = str4.intern(); // 将str4字符串对象添加到运行时常量池中,并返回引用

        // 判断是否为同一个引用
        System.out.println("str1 == str3: " + (str1 == str3)); // 输出 true,因为它们都是编译时常量池中的同一个对象
        System.out.println("str1 == str4: " + (str1 == str4)); // 输出 false,因为str4在堆中创建了新的对象
        System.out.println("str1 == str5: " + (str1 == str5)); // 输出 true,因为str5是从运行时常量池中返回的引用

        // 判断内容是否相同
        System.out.println("str1.equals(str4): " + str1.equals(str4)); // 输出 true,因为内容相同
    }
}

输出结果为:

str1 == str3: true
str1 == str4: false
str1 == str5: true
str1.equals(str4): true

从输出结果可以看出:

  • str1str3是同一个引用,因为它们都指向编译时常量池中的同一个对象。
  • str1str4不是同一个引用,因为new String("Vincent")在堆中创建了新的对象。
  • str1str5是同一个引用,因为str5是从运行时常量池中返回的引用。

总结:

  • 字面量是直接使用在代码中的常量值,例如字符串字面量:"Vincent"和"hello"。
  • 编译时常量池是在编译时存储字面量的特殊存储区域,用于存储类的静态常量和字面量,它属于类的一部分。
  • 运行时常量池是在运行时动态生成的,用于存储类的常量、静态变量、字符串字面量等信息,它属于方法区的一部分。
  • 运行时常量池中的字符串对象可以通过调用intern()方法将对象添加到其中,从而实现共享字符串对象的效果。

intern()

intern()是一个方法,定义在java.lang.String类中。它的作用是将字符串对象添加到运行时常量池中,并返回运行时常量池中该字符串的引用。如果运行时常量池中已经存在相同内容的字符串,则直接返回该字符串的引用。

intern()方法的工作原理

  1. 当调用intern()方法时,JVM首先检查该字符串是否已经在运行时常量池中。如果在运行时常量池中找到了相同内容的字符串,则直接返回该字符串在常量池中的引用。
  2. 如果运行时常量池中没有找到相同内容的字符串,JVM会将该字符串添加到运行时常量池中,并返回新添加的字符串在常量池中的引用。

代码实现

让我们通过代码来演示intern()方法的使用和效果:

public class InternMethodDemo {
    public static void main(String[] args) {
        String str1 = "hello"; // 字符串"h"添加到运行时常量池
        String str2 = new String("hello"); // 在堆中创建新的字符串对象
        String str3 = str2.intern(); // 将str2所引用的字符串对象添加到运行时常量池,并返回常量池中的引用

        // 判断是否为同一个引用
        System.out.println("str1 == str2: " + (str1 == str2)); // 输出 false,str1指向常量池,str2指向堆
        System.out.println("str1 == str3: " + (str1 == str3)); // 输出 true,str3是从运行时常量池中返回的引用

        // 判断内容是否相同
        System.out.println("str1.equals(str2): " + str1.equals(str2)); // 输出 true,内容相同
        System.out.println("str1.equals(str3): " + str1.equals(str3)); // 输出 true,内容相同
    }
}

输出结果为:

str1 == str2: false
str1 == str3: true
str1.equals(str2): true
str1.equals(str3): true

从输出结果可以看出:

  • str1str2不是同一个引用,因为str2使用new关键字在堆中创建了新的对象。
  • str1str3是同一个引用,因为str3是从运行时常量池中返回的引用。
  • str1str2的内容相同,因为它们都是"hello"这个字符串。
  • str1str3的内容相同,因为它们都是"hello"这个字符串。

总结:

  • intern()方法将字符串对象添加到运行时常量池,并返回常量池中该字符串的引用。
  • 如果运行时常量池中已经存在相同内容的字符串,则直接返回该字符串的引用。
  • intern()方法可以用于节省内存,因为共享相同内容的字符串可以在运行时常量池中重用。但过度使用intern()方法也可能导致运行时常量池过大,从而影响性能。因此,应谨慎使用该方法,只在有必要时使用。
赞赏