Membandingkan string dengan == yang dinyatakan final di Jawa

220

Saya punya pertanyaan sederhana tentang string di Jawa. Segmen kode sederhana berikut hanya menggabungkan dua string dan kemudian membandingkannya ==.

String str1="str";
String str2="ing";
String concat=str1+str2;

System.out.println(concat=="string");

Ekspresi perbandingan concat=="string"kembali falsesebagai jelas (saya mengerti perbedaan antara equals()dan ==).


Ketika dua string ini dinyatakan finalseperti itu,

final String str1="str";
final String str2="ing";
String concat=str1+str2;

System.out.println(concat=="string");

Ekspresi perbandingan concat=="string", dalam hal ini kembali true. Mengapa finalmembuat perbedaan? Apakah itu harus melakukan sesuatu dengan kolam intern atau aku hanya disesatkan?

Mungil
sumber
22
Saya selalu merasa konyol bahwa equals adalah cara default untuk memeriksa konten yang sama, daripada == melakukan itu dan hanya menggunakan referenceEquals atau sesuatu yang serupa untuk memeriksa apakah pointernya sama.
Davio
25
Ini bukan duplikat dari "Bagaimana cara membandingkan string di Jawa?" dengan cara apapun. OP memahami perbedaan antara equals()dan ==dalam konteks string, dan mengajukan pertanyaan yang lebih bermakna.
arshajii
@Davio Tapi bagaimana cara kerjanya ketika kelas tidak String? Saya pikir itu sangat logis, tidak konyol, untuk memiliki perbandingan konten dilakukan oleh equals, metode yang bisa kita timpa ketika kita menganggap dua objek sama, dan memiliki perbandingan identitas dilakukan oleh ==. Jika perbandingan konten dilakukan oleh ==kami tidak dapat mengesampingkannya untuk mendefinisikan apa yang kami maksud dengan "konten yang sama", dan memiliki arti equalsdan ==dibalik hanya untuk Strings akan konyol. Selain itu, terlepas dari ini, saya tetap tidak melihat keuntungan dalam ==melakukan perbandingan konten equals.
SantiBailors
@SantiBailors Anda benar bahwa ini hanya cara kerjanya di Jawa, saya juga menggunakan C # di mana == kelebihan beban untuk kesetaraan konten. Bonus tambahan menggunakan == adalah bahwa itu nol-aman: (null == "sesuatu") mengembalikan false. Jika Anda menggunakan sama dengan 2 objek, Anda harus waspada jika salah satu bisa nol atau Anda berisiko NullPointerException dilempar.
Davio

Jawaban:

232

Ketika Anda mendeklarasikan variabel String(yang tidak dapat diubah ) sebagai final, dan menginisialisasinya dengan ekspresi konstanta waktu kompilasi, ia juga menjadi ekspresi konstanta waktu kompilasi, dan nilainya digarisbawahi oleh kompiler di mana ia digunakan. Jadi, dalam contoh kode kedua Anda, setelah menguraikan nilai-nilai, rangkaian string diterjemahkan oleh kompiler ke:

String concat = "str" + "ing";  // which then becomes `String concat = "string";`

yang bila dibandingkan "string"akan memberi Anda true, karena string literal diinternir .

Dari JLS §4.12.4 - finalVariabel :

Variabel tipe atau tipe primitif String, yaitu finaldan diinisialisasi dengan ekspresi konstanta waktu kompilasi (§15.28), disebut variabel konstan .

Juga dari JLS §15.28 - Ekspresi Konstan:

Kompilasi ekspresi konstan tipe waktu Stringselalu "diinternir" sehingga dapat berbagi contoh unik, menggunakan metode ini String#intern().


Ini tidak terjadi dalam contoh kode pertama Anda, di mana Stringvariabel tidak final. Jadi, mereka bukan ekspresi konstan waktu kompilasi. Operasi penggabungan di sana akan ditunda hingga runtime, sehingga mengarah ke penciptaan Stringobjek baru . Anda dapat memverifikasi ini dengan membandingkan kode byte kedua kode.

Contoh kode pertama (bukan finalversi) dikompilasi ke kode byte berikut:

  Code:
   0:   ldc     #2; //String str
   2:   astore_1
   3:   ldc     #3; //String ing
   5:   astore_2
   6:   new     #4; //class java/lang/StringBuilder
   9:   dup
   10:  invokespecial   #5; //Method java/lang/StringBuilder."<init>":()V
   13:  aload_1
   14:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   17:  aload_2
   18:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   21:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   24:  astore_3
   25:  getstatic       #8; //Field java/lang/System.out:Ljava/io/PrintStream;
   28:  aload_3
   29:  ldc     #9; //String string
   31:  if_acmpne       38
   34:  iconst_1
   35:  goto    39
   38:  iconst_0
   39:  invokevirtual   #10; //Method java/io/PrintStream.println:(Z)V
   42:  return

Jelas itu menyimpan strdan ingdalam dua variabel terpisah, dan menggunakan StringBuilderuntuk melakukan operasi penggabungan.

Sedangkan, contoh kode kedua Anda ( finalversi) terlihat seperti ini:

  Code:
   0:   ldc     #2; //String string
   2:   astore_3
   3:   getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
   6:   aload_3
   7:   ldc     #2; //String string
   9:   if_acmpne       16
   12:  iconst_1
   13:  goto    17
   16:  iconst_0
   17:  invokevirtual   #4; //Method java/io/PrintStream.println:(Z)V
   20:  return

Jadi secara langsung inline variabel akhir untuk membuat String stringpada waktu kompilasi, yang dimuat oleh ldcoperasi pada langkah 0. Kemudian literal string kedua dimuat oleh ldcoperasi dalam langkah 7. Itu tidak melibatkan pembuatan Stringobjek baru saat runtime. String sudah dikenal pada waktu kompilasi, dan mereka diinternir.

Rohit Jain
sumber
2
Tidak ada yang mencegah implementasi Java compiler lainnya untuk tidak menginternir String akhir kan?
Alvin
13
@ Alvin the JLS mengharuskan ekspresi string konstan waktu-kompilasi diinternir. Setiap implementasi yang sesuai harus melakukan hal yang sama di sini.
Tavian Barnes
Sebaliknya, apakah JLS mengamanatkan bahwa kompiler tidak boleh mengoptimalkan penggabungan dalam versi pertama, non-final ? Apakah kompiler dilarang menghasilkan kode, yang akan dievaluasi perbandingannya true?
phant0m
1
@ phant0m mengambil kata-kata saat spesifikasi , “ The Stringobjek yang baru dibuat (§12.5) kecuali ekspresi adalah ekspresi konstan (§15.28). ”Secara harfiah, menerapkan optimasi dalam versi non-final tidak diperbolehkan, karena string“ baru dibuat ”harus memiliki identitas objek yang berbeda. Saya tidak tahu apakah ini disengaja. Bagaimanapun, strategi kompilasi saat ini adalah untuk mendelegasikan ke fasilitas runtime yang tidak mendokumentasikan pembatasan tersebut.
Holger
31

Sesuai penelitian saya, semua final Stringdiinternir di Jawa. Dari salah satu posting blog:

Jadi, jika Anda benar-benar perlu membandingkan dua String menggunakan == atau! = Pastikan Anda memanggil metode String.intern () sebelum membuat perbandingan. Kalau tidak, selalu lebih suka String.equals (String) untuk perbandingan String.

Jadi itu artinya jika Anda menelepon String.intern()Anda dapat membandingkan dua string menggunakan ==operator. Tapi di sini String.intern()tidak perlu karena di Jawa final Stringinternal diinternir.

Anda dapat menemukan lebih banyak informasi perbandingan String menggunakan == operator dan Javadoc untuk metode String.intern () .

Lihat juga pos Stackoverflow ini untuk informasi lebih lanjut.

Pradeep Simha
sumber
3
string intern () bukan sampah yang dikumpulkan dan disimpan di ruang permgen yang rendah sehingga Anda akan mendapat masalah seperti kehabisan memori kesalahan jika tidak digunakan dengan benar.
Ajeesh
@Ajeesh - String yang diinternir dapat berupa sampah yang dikumpulkan. Bahkan string internal yang dihasilkan dari ekspresi konstan dapat berupa sampah yang dikumpulkan dalam beberapa keadaan.
Stephen C
21

Jika Anda melihat metode ini

public void noFinal() {
    String str1 = "str";
    String str2 = "ing";
    String concat = str1 + str2;

    System.out.println(concat == "string");
}

public void withFinal() {
    final String str1 = "str";
    final String str2 = "ing";
    String concat = str1 + str2;

    System.out.println(concat == "string");
}

dan itu didekompilasi dengan javap -c ClassWithTheseMethods versi yang akan Anda lihat

  public void noFinal();
    Code:
       0: ldc           #15                 // String str
       2: astore_1      
       3: ldc           #17                 // String ing
       5: astore_2      
       6: new           #19                 // class java/lang/StringBuilder
       9: dup           
      10: aload_1       
      11: invokestatic  #21                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
      14: invokespecial #27                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
      17: aload_2       
      18: invokevirtual #30                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      21: invokevirtual #34                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      ...

dan

  public void withFinal();
    Code:
       0: ldc           #15                 // String str
       2: astore_1      
       3: ldc           #17                 // String ing
       5: astore_2      
       6: ldc           #44                 // String string
       8: astore_3      
       ...

Jadi jika Strings bukan kompiler akhir harus menggunakan StringBuilderuntuk menyatukan str1dan str2sebagainya

String concat=str1+str2;

akan dikompilasi ke

String concat = new StringBuilder(str1).append(str2).toString();

yang berarti yang concatakan dibuat saat runtime sehingga tidak akan datang dari kolam String.


Juga jika Strings bersifat final maka kompiler dapat mengasumsikan bahwa mereka tidak akan pernah berubah sehingga alih-alih menggunakannya StringBuilderdapat dengan aman menyatukan nilainya sehingga

String concat = str1 + str2;

dapat diubah menjadi

String concat = "str" + "ing";

dan digabung menjadi

String concat = "string";

yang berarti bahwa concateakan menjadi sengatan literal yang akan diinternir dalam string pool dan kemudian dibandingkan dengan string literal yang sama dari kolam itu dalam ifpernyataan.

Pshemo
sumber
15

Konsep pool dan string conts pool masukkan deskripsi gambar di sini

pnathan
sumber
6
Apa? Saya tidak mengerti bagaimana ini memiliki upvotes. Bisakah Anda mengklarifikasi jawaban Anda?
C
Saya pikir jawaban yang dimaksudkan adalah karena str1 + str2 tidak dioptimalkan untuk string yang diinternir, perbandingan dengan string dari kumpulan string akan mengarah ke kondisi yang salah.
viki.omega9
3

Mari kita lihat beberapa kode byte sebagai finalcontoh

Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: ldc           #2                  // String string
       2: astore_3
       3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       6: aload_3
       7: ldc           #2                  // String string
       9: if_acmpne     16
      12: iconst_1
      13: goto          17
      16: iconst_0
      17: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
      20: return
}

Pada 0:dan 2:, String "string"didorong ke stack (dari kolam konstan) dan disimpan ke variabel lokal concatsecara langsung. Anda dapat menyimpulkan bahwa kompiler membuat (menyatukan) String "string"dirinya sendiri pada waktu kompilasi.

Kode bukan finalbyte

Compiled from "Main2.java"
public class Main2 {
  public Main2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: ldc           #2                  // String str
       2: astore_1
       3: ldc           #3                  // String ing
       5: astore_2
       6: new           #4                  // class java/lang/StringBuilder
       9: dup
      10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      13: aload_1
      14: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Stri
ngBuilder;
      17: aload_2
      18: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Stri
ngBuilder;
      21: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      24: astore_3
      25: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      28: aload_3
      29: ldc           #9                  // String string
      31: if_acmpne     38
      34: iconst_1
      35: goto          39
      38: iconst_0
      39: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
      42: return
}

Di sini Anda memiliki dua Stringkonstanta, "str"dan "ing"yang harus disatukan pada saat runtime dengan a StringBuilder.

Sotirios Delimanolis
sumber
0

Meskipun, ketika Anda membuat menggunakan notasi String literal dari Java, itu secara otomatis memanggil metode intern () untuk memasukkan objek itu ke dalam kumpulan String, asalkan itu tidak ada di kumpulan sudah.

Mengapa final membuat perbedaan?

Compiler tahu variabel terakhir tidak akan pernah berubah, ketika kita menambahkan variabel akhir ini, output pergi ke String Pool karena str1 + str2ekspresi output juga tidak akan pernah berubah, Jadi akhirnya kompiler memanggil metode antar setelah output dari dua variabel terakhir di atas. Dalam hal kompiler variabel non-final jangan panggil metode intern.

Premraj
sumber