Java字符串池和类型转换

10
我的问题与Java处理字符串字面值的方式有关。从Java语言规范(JLS)中可以清楚地看出,字符串字面值被隐式地合并 - 换句话说,对象是在堆的字符串常量池部分创建的,而不是调用new String("whatever")时创建的基于堆的对象。
与JLS所说的不符的是,当使用转换后的常量字符串类型进行字符串连接创建新字符串时,这应该根据JLS视为常量字符串,显然JVM会创建一个新的字符串对象而不是隐式合并它。我需要解释一下这种特殊行为是否是平台特定的行为,并感激您使内容更加通俗易懂。我正在运行Mac OSX Snow Leopard。
public class Test
{
    public static void main(String args[])
    {
        /*
            Create a String object on the String constant pool
            using a String literal
        */
        String hello = "hello";
        final String lo = "lo"; // this will be created in the String pool as well
        /*
            Compare the hello variable to a String constant expression
            , that should cause the JVM to implicitly call String.intern()
        */
        System.out.println(hello == ("hel" + lo));// This should print true
        /*
            Here we need to create a String by casting an Object back
            into a String, this will be used later to create a constant
            expression to be compared with the hello variable
        */
        Object object = "lo";
        final String stringObject = (String) object;// as per the JLS, casted String types can be used to form constant expressions
        /*
            Compare with the hello variable
        */
        System.out.println(hello == "hel" + stringObject);// This should print true, but it doesn't :(

    }
}

5
我认为你误解了问题的要点。 - Oliver Charlesworth
4
请问您能否提供 JLS 相关部分链接,指明将表达式强制转换为 String 的内容会被放入常量池中的规定?我的猜测是第二个表达式无法被常量折叠,因为编译器没有进行数据流分析来确定 stringObject 是常量 "lo"。请注意,我的翻译保留了原意并尽可能地使语言通俗易懂,但不包含解释或其他额外信息。 - millimoose
不,它不会。"=="用于比较两个字符串引用变量是否引用了完全相同的对象,即如果您喜欢,完全相同的内存地址。 - HackerMonkey
1
@dann.dev OP并不是在问如何比较字符串,他在问Java中常量字符串表达式的工作原理。 - millimoose
@Hasanein - 我进行了编辑以尝试澄清问题。如果您认为我歪曲了任何事情,请随时重新编辑。 - Paul Bellora
显示剩余6条评论
2个回答

6
在编译时常量表达式中不允许将类型转换为Object。只允许转换为String和原始类型。请参阅JLS(Java SE 7版)第15.28节:> - 转换为基本类型和转换为String类型。(实际上还有第二个原因。object不是final的,因此不能被视为常量变量。“使用编译时常量表达式(§15.28)初始化的原始类型或String类型的变量称为常量变量。”——第4.12.4节。)

1
隐式转换为Object意味着它不再是常量。 - Tom Hawtin - tackline
将对象更改为final仍会产生类似的结果。您能否举一个使用字符串转换形成常量字符串表达式的示例? - HackerMonkey
@Hasanein "hi" == "h"+(String)"i" - Tom Hawtin - tackline
1
@Hasanein 我看不出来这怎么可能发生在编译时常量表达式中。 - Tom Hawtin - tackline
@TomHawtin 非常感谢,现在清楚了,String强制转换的唯一用例是将String文字(或常量对象)转换回String,任何其他尝试将任何其他对象转换为String都是运行时操作,这会使常量表达式的条件无效。 - HackerMonkey
显示剩余3条评论

3

看起来你在这里引用了一个对象 final String stringObject = (String) object;,这不再是'编译时'常量,而是'运行时'常量。来自这里的第一个例子提到了这一点:

String s = "lo";
String str7 = "Hel"+ s;  
String str8 = "He" + "llo"; 
System.out.println("str7 is computed at runtime.");     
System.out.println("str8 is created by using string constant expression.");    
System.out.println("    str7 == str8 is " + (str7 == str8));  
System.out.println("    str7.equals(str8) is " + str7.equals(str8));

字符串str7是在运行时计算的,因为它引用了另一个不是文字的字符串,所以根据这个逻辑,即使你将stringObject声明为final,它仍然引用一个对象,因此无法在编译时计算。

而在Java语言规范这里中,它指出:

"字符串连接操作符+(§15.18.1)在结果不是编译时常量表达式(§15.28)时隐式创建一个新的字符串对象。"

我找不到任何可以使用类型转换的例子,除了这个可怕的、可怕的例子之外:

System.out.println(hello == "hel" + ( String ) "lo");

这句话几乎没有什么逻辑用处,但也许关于字符串转换的部分是因为上面的情况而包含在其中。


谢谢dann,你说得对。我认为在规格说明中提到的唯一原因是支持将字符串转换回字符串,但我也看不出它有多少实际用途。 - HackerMonkey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接