确定 String 是否为编译时常量

Determine whether a String is a compile-time constant

给定对任何 String 的引用,是否可以通过编程方式确定这是否是对编译时间常量的引用?
或者如果不是,那么它是否存储在实习生池中而不做s.intern() == s?

isConst("foo")                       -> true
isConst("foo" + "bar")               -> true   // 2 literals, 1 compile time string
isConst(SomeClass.SOME_CONST_STRING) -> true
isConst(readFromFile())              -> false
isConst(readFromFile().intern())     -> false  // true would be acceptable too

(以下评论的上下文:最初提出的关于文字的问题)

为了澄清最初的问题,每个字符串文字都是一个 compile-time 常量,但并非每个 compile-time 常量都必须源自字符串文字。

在运行时,为 compile-time 常量构造的 String 对象与通过其他方式构造的对象没有区别。为 compile-time 常量构造的字符串会自动添加到池中,但其他字符串可以通过 intern() 手动添加到同一池中。由于字符串是延迟构造和添加的,因此甚至可以手动构造和添加字符串,因此 compile-time 具有相同值的常量会解析为该字符串 later-on。 利用这种可能性,检测 compile-time 常量的 String 实例何时真正解析。

可以从该答案中得出一种方法来简单地检测字符串是否在池中:

public static boolean isInPool(String s) {
    return s == new String(s.toCharArray()).intern();
}

new String(s.toCharArray()) 构造一个具有相同内容的字符串,该字符串不在池中,如果 s 指的是池中的一个实例。否则,intern() 可能解析为另一个现有对象或添加我们的字符串或新构造的字符串和 return 对它的引用,具体取决于实现,但在任何一种情况下, returned引用将不同于 s.

请注意,此方法的副作用是将一个字符串添加到池中(如果之前不存在),该字符串至少会保留到下一个垃圾收集周期,可能会保留到下一个完整的 gc,具体取决于关于实施。

测试方法可能很适合调试或满足好奇心,但在生产代码中使用它毫无意义。应用程序代码不应依赖于此 属性 并且评论中提出的用例在性能关键代码中强制使用池化字符串并不是一个好主意。

除了测试本身很昂贵并且抵消了性能改进的目的之外,池化字符串优于 non-pooled 的基本假设是有缺陷的。不在池中并不意味着应用程序每次调用性能关键代码时都会执行昂贵的重建。它可以简单地在变量中保存引用或使用 HashMap,这两种方法都比调用 intern() 更有效。事实上,在某些情况下,即使是临时字符串也可能是最有效的解决方案。