Разница между добавлением строковых литералов и строковых объектов - PullRequest
3 голосов
/ 13 апреля 2011

В чем разница между добавлением строкового литерала и строкового объекта?

Например

    String s1 ="hello";
    String s2 ="hello1";
    String s3 ="hello" + "hello1";
    String s4 ="hellohello1";
    String s5 = s1 + s2;

    System.out.println(s3 == s4); // returns true
    System.out.println(s3 == s5); // return false
    System.out.println(s4 == s5); // return false

Почему s3 / s4 не указывают на то же местоположениеs5

Ответы [ 4 ]

3 голосов
/ 13 апреля 2011

Поскольку s1 + s2 не является константным выражением , так как s1 и s2 не являются final, поэтому его результат не интернирован, т. Е. Создан другой объект для его представления, поэтомуСравнение ссылок дает false.

JLS 3.10.5 Строковые литералы :

Строковые литералы или, в более общем смысле, строки, являющиеся значениями константвыражения (§15.28) - «интернированы», чтобы обмениваться уникальными экземплярами, используя метод String.intern.

JLS 15.28 Выражение константы :

Выражение константы времени компиляции - это выражение, обозначающее значение типа примитива или String, которое не завершается внезапно и состоит только из следующего:

  • ...
  • Простые имена, которые ссылаются на постоянные переменные (§4.12.4).

JLS 4.12.4 определяет final переменных.

Если вы объявляете s1 и s2 как final, s3 == s5 будет true.

3 голосов
/ 13 апреля 2011

Потому что вы сравниваете ссылки. Для сравнения содержимого используйте s1.equals(s2).

Если вы сравниваете ссылки намеренно, неясно, почему вы ожидаете, что компилятор / JVM интернирует или не интернирует идентичные строки, возникающие по-разному.

1 голос
/ 13 апреля 2011

РЕДАКТИРОВАТЬ: я предполагаю, что вы знаете, что вы сравниваете ссылки, а не содержимое строк. Если нет, то s3.equals(s5) - это то, что вы ищете (как уже упоминалось).

s3 оптимизируется компилятором до "hellohello1", который также используется для s4. Я весьма удивлен, что компилятор не настолько умен, чтобы делать то же самое для s5. Какую версию JDK вы используете? . Эта оптимизация допускается только для константных выражений (см. 15.28 спецификации языка Java ). Другими словами, любое присвоение неконечной переменной отрицает возможность последующей оптимизации.

Вот вывод javap -c -l простого класса, который упаковывает ваш код в основной метод (не то, чтобы кто-то об этом просил, но мне самому любопытно). Итак, давайте посмотрим, что происходит:

public static void main(java.lang.String[]);
  Code:
    0:  ldc #16; //String hello
    2:  astore_1
    3:  ldc #18; //String hello1
    5:  astore_2
    6:  ldc #20; //String hellohello1
    8:  astore_3
    9:  ldc #20; //String hellohello1
    11: astore  4
    13: new #22; //class java/lang/StringBuilder
    16: dup
    17: aload_1
    18: invokestatic    #24; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
    21: invokespecial   #30; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
    24: aload_2
    25: invokevirtual   #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    28: invokevirtual   #37; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    31: astore  5
    33: getstatic   #41; //Field java/lang/System.out:Ljava/io/PrintStream;
    36: aload_3
    37: aload   4
    39: if_acmpne   46
    42: iconst_1
    43: goto    47
    46: iconst_0
    47: invokevirtual   #47; //Method java/io/PrintStream.println:(Z)V
    50: getstatic   #41; //Field java/lang/System.out:Ljava/io/PrintStream;
    53: aload_3
    54: aload   5
    56: if_acmpne   63
    59: iconst_1
    60: goto    64
    63: iconst_0
    64: invokevirtual   #47; //Method java/io/PrintStream.println:(Z)V
    67: getstatic   #41; //Field java/lang/System.out:Ljava/io/PrintStream;
    70: aload   4
    72: aload   5
    74: if_acmpne   81
    77: iconst_1
    78: goto    82
    81: iconst_0
    82: invokevirtual   #47; //Method java/io/PrintStream.println:(Z)V
    85: return

LocalVariableTable: 
  Start  Length  Slot  Name   Signature
   0     86      0     args   [Ljava/lang/String;
   3     83      1     s1     Ljava/lang/String;
   6     80      2     s2     Ljava/lang/String;
   9     77      3     s3     Ljava/lang/String;
  13     73      4     s4     Ljava/lang/String;
  33     53      5     s5     Ljava/lang/String;


}

У меня нет опыта чтения байт-кода, но я попробую :)) 1020 *

  • числа, начинающиеся с # (например, # 16), являются ссылками на постоянный пул. Содержимое всегда добавляется как комментарий к этой строке
  • ldc #16, за которым следует astore_1 означает «загрузить константу # 16 и сохранить ее в слоте 1». Как вы можете видеть, это делается 4 раза в начале для слотов 1 - 4, что переводит в s1, s2, s3 и s4 (см. LocalVariableTable).
  • для s5, не вдаваясь в подробности, очевидно, есть StringBuilder и загрузка слота 1 (aload_1) и слота 2 (aload_2) перед сохранением результата в слоте 5 (astore 5).
0 голосов
/ 13 апреля 2011

Поскольку компилятор оптимизирует объединение строковых литералов.

На практике это не должно иметь значения (большую часть времени), так как вы обычно хотите сравнивать строки на равенство, используя метод equals, а не проверять, совпадают ли ссылки на объекты.

Также обратите внимание, что вы можете пройти стажировку в s5, например ::10000*

s5 = s5.intern();

Это редко требуется, хотя.

...