Имеется следующий код:
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1")+new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
Вывод кода выше:
false
true
Я знаю, что s
и s2
- разные объекты, поэтому результат оценивается как false, но второй результат равен true. Может ли кто-нибудь сказать мне разницу?
Ответ 1
Вот что происходит:
Пример 1
String s1 = new String("1");
s1.intern();
String s2 = "1";
- Строковый литерал
"1"
(переданный в конструктор String
) интернирован по адресу A.
Строка s1
создается по адресу B, потому что это не буквальное или постоянное выражение.
- Вызов
intern()
не действует. Строка "1"
уже интернирована, и результат операции не привязан к s1
.
- String
s2
со значением "1"
извлекается из пула строк, поэтому указывает на адрес A.
Результат: Строки s1
и s2
указывают на разные адреса.
Пример 2
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
- Строка
s3
создается по адресу C.
- Вызов
intern()
добавляет строку со значением "11"
по адресу C в пул строк.
- String
s4
со значением "11"
извлекается из пула строк, поэтому указывает на адрес C.
Результат: Строки s3
и s4
указывают на тот же адрес.
Резюме
String "1"
интернируется до вызова intern()
, в силу своего присутствия в вызове конструктора s1 = new String("1")
.
Изменение этого вызова конструктора на s1 = new String(new char[]{'1'})
приведет к тому, что сравнение s1 == s2
будет равно true, так как теперь оба будут ссылаться на строку, которая была явно интернирована, вызывая s1.intern()
.
(я использовал код из этого ответа, чтобы получить информацию о местах расположения строк.)
Ответ 2
Для сценария 1:
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
с байт-кодом:
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String 1
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: aload_1
11: invokevirtual #5 // Method java/lang/String.intern:()Ljava/lang/String;
14: pop
15: ldc #3 // String 1
для String s = new String("1");
он создаст новый объект String
, у него будет новый адрес с "1" , который уже находится в String Pool:
ldc #3 // String 1
и для s2
, в качестве байт-кода:
15: ldc #3 // String 1
s2
указывает на переменную String Pool: "1"
, поэтому s
и s2
имеют другой адрес, а результат - false
.
Для сценария 2:
String s3 = new String("1")+new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
с байт-кодом:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String 1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #4 // String 1
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_2
23: aload_2
24: invokevirtual #7 // Method java/lang/String.intern:()Ljava/lang/String;
27: astore_3
28: ldc #8 // String 11
Как байт-код, вы можете видеть, что new String("1")+new String("1");
создается с помощью StringBuilder
new #2 // class java/lang/StringBuilder
это полностью новый объект без переменной String Pool.
и после s3.intern()
этот метод добавит текущий s3
в Пул строк памяти и 8: aload_1
.
и s4
пытается загрузить из
ldc #8 // String 11
так что s3
и s4
адрес должен совпадать, и результат будет истинным.
Ответ 3
Просто для тех, кто использует groovy, информация о добавлении: поведение отличается
![введите описание изображения здесь]()
Ответ 4
s.intern() не изменяет строку s. Вы должны были написать:
s = s.intern();