Почему сравнение строк отличается?

В первом методе выводится значение false, во втором - true. Мои мысли: в test1(), когда создается d - уже в String Pool существует c. Но получается d дублируется. Поведение test2() мне понятно. Можете объяснить логику создания c и d в test1()?

public class Main
{
    public static void main(String[] args) {
        System.out.println("Hello World");
        test1();
        test2();
    }
    
    public static void test1() {
        String a = "a";
        String b = "b";
        // String c = a + b;
        String c = a.concat(b);
        String d = "ab";
        System.out.println(c == d);
    }
    public static void test2() {
        String a = "ab";
        String b = "a" + "b";
        System.out.println(a == b);   
    }
}

байт-код:

public static void test1();
    Code:
       0: ldc           #7                  // String a
       2: astore_0
       3: ldc           #8                  // String b
       5: astore_1
       6: aload_0
       7: aload_1
       8: invokevirtual #9                  // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String;
      11: astore_2
      12: ldc           #10                 // String ab
      14: astore_3
      15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      18: aload_2
      19: aload_3
      20: if_acmpne     27
      23: iconst_1
      24: goto          28
      27: iconst_0
      28: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
      31: return

  public static void test2();
    Code:
       0: ldc           #10                 // String ab
       2: astore_0
       3: ldc           #10                 // String ab
       5: astore_1
       6: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: aload_0
      10: aload_1
      11: if_acmpne     18
      14: iconst_1
      15: goto          19
      18: iconst_0
      19: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
      22: return

Ответы (2 шт):

Автор решения: Stanislav Volodarskiy

Помещать или нет строку в пул решает компилятор. Во втором случае он решил что обе строки можно поместить в пул. В первом тесте c в пул не попадает. Это объясняет разницу в тестах.

Другой вопрос как с этим жить программисту?

Вот так: не использовать == на строках если вы не уверены что обе помещены в пул. Используйте .intern() для помещения строк в пул.

→ Ссылка
Автор решения: Georgi

В первом случае была произведена конкатенация объектов класса String (String c = a.concat(b); или можно было написать String c = a + b;). Такой результат не интернируется и будет положен в heap. Конкатенация объектов String всегда помещается в heap, за исключением тех случаев, когда аргументы конкатенации были объявлены как final (см. тут). Иначе используйте intern(), чтобы поместить строку в string pool.

Во втором же случае была произведена конкатенация строковых литералов (String b = "a" + "b";), которые автоматически интернируются и попадают в string pool. И в целом, когда мы говорим, что строка была помещена в string pool подразумевается, что был помещен именно строковый литерал, а не строка, как объект класса String. String pool хранит строковые литералы.

→ Ссылка