Java'da son olarak bildirilen == dizeleri karşılaştırmak


220

Java dizeleri hakkında basit bir sorum var. Aşağıdaki basit kod segmenti sadece iki dizeyi birleştirir ve sonra bunları karşılaştırır ==.

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

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

Karşılaştırma ifadesi açık olarak concat=="string"döner false( equals()ve arasındaki farkı anlıyorum ==).


Bu iki dize böyle bildirildiğinde final,

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

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

Karşılaştırma ifadesi concat=="string", bu durumda geri döner true. Neden finalbir fark yaratır? Stajyer havuzunda bir şey yapmak zorunda mı yoksa sadece yanıltılıyorum?


22
Ben her zaman == bunu yapmak yerine sadece eşit içeriği kontrol için eşittir eşittir aptal buldum ve sadece referenceEquals veya işaretçiler aynı olup olmadığını kontrol etmek için benzer bir şey kullanın.
Davio

25
Bu değil yinelediği "Nasıl Java dizeleri karşılaştırılır?" herhangi bir şekilde. OP , dizeler arasındaki equals()ve ==bağlamındaki farkı anlar ve daha anlamlı bir soru sorar.
arshajii

@Davio Ama sınıf olmadığında bu nasıl çalışır String? Aptalca değil, içerik karşılaştırmasının tamamlanması equals, iki nesneyi eşit olarak değerlendirdiğimizde bunu geçersiz kılabileceğimiz ve kimlik karşılaştırmasını yapmanın çok mantıklı olduğunu düşünüyorum ==. Eğer içerik karşılaştırması yapıldıysa, =="eşit içerik" ile ne demek istediğimizi tanımlamak için bunu geçersiz kılamazdık equalsve ==sadece Strings için anlamı ve tersine çevirmek saçma olurdu. Ayrıca, ne olursa olsun, ==bunun yerine içerik karşılaştırması yapmanın herhangi bir avantajı görmüyorum equals.
SantiBailors

@SantiBailors doğru bu Java nasıl çalışır doğru, ben de C = kullandım nerede == içerik eşitliği için aşırı. == kullanmanın bir avantajı da null-safe olmasıdır: (null == "bir şey") false değerini döndürür. 2 nesne için eşittir kullanırsanız, boş değer olup olmadığını veya bir NullPointerException özel durumunun atılma riskinin olup olmadığını bilmeniz gerekir.
Davio

Yanıtlar:


232

Bir ilan zaman String(bir değişmez şekilde) değişken finalve bir derleme zamanı sabiti ifade ile başlatmak, aynı zamanda bir derleme zamanı sabit ifade olur ve değeri kullanıldığı derleyici tarafından satır içi. Dolayısıyla, ikinci kod örneğinizde, değerleri satır içine aldıktan sonra, dize birleştirmesi derleyici tarafından şu dile çevrilir:

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

hangi ile karşılaştırıldığında "string"size verecektir true, çünkü dize değişmezleri stajyer .

Gönderen §4.12.4 JLS - finalDeğişkenler :

Basit tür ve tip bir değişken Stringolan, finalbir derleme zamanı sabiti ekspresyonu (§15.28) ile başlatılır ve, bir adlandırılan sabit bir değişken .

Ayrıca JLS §15.28 - Sürekli İfade:

Derleme zamanı sabit tür ifadeleri , yöntemi kullanarak benzersiz örnekleri paylaşmak Stringiçin her zaman "sabitlenir"String#intern() .


StringDeğişkenlerin olmadığı ilk kod örneğinizde durum böyle değildir final. Yani, bunlar derleme zamanı sabit ifadeleri değildir. Buradaki birleştirme işlemi, çalışma zamanına kadar ertelenir ve böylece yeni bir Stringnesnenin oluşturulmasına yol açar . Her iki kodun bayt kodunu karşılaştırarak bunu doğrulayabilirsiniz.

İlk kod örneği ( finalsürüm olmayan ) aşağıdaki bayt koduyla derlenir:

  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

Açıkça , iki ayrı değişkente depolanıyor strve birleştirme işlemini gerçekleştirmek için ingkullanıyor StringBuilder.

Halbuki, ikinci kod örneğiniz ( finalversiyon) şöyle görünür:

  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

Bu nedenle string, derleme zamanında Dize oluşturmak için son değişkeni doğrudan satır içine alır ve bu ldcadımda işlemle yüklenir 0. Sonra ikinci dize hazır bilgisi ldcadım adım işlemle yüklenir 7. Çalışma Stringzamanında yeni bir nesnenin oluşturulmasını içermez . Dize derleme zamanında zaten bilinir ve stajyerdir.


2
Başka bir Java derleyici uygulamasını son bir String stajyer değil önlemek önleyen bir şey yok değil mi?
Alvin

13
@ JLS, derleme zamanı sabit dize ifadelerinin stajyer olmasını gerektirir. Uygun herhangi bir uygulama burada aynı şeyi yapmalıdır.
Tavian Barnes

Bunun tersine JLS, bir derleyicinin ilk, son olmayan sürümdeki birleştirme işlemini optimize etmemesi gerektiğini zorunlu kılıyor mu? Derleyicinin karşılaştırmayı değerlendirmesini sağlayacak kod üretmesi yasak mı ? true
phant0m

1
@ mevcut ifadeler alarak phant0m tarifnamede , “ amacı da, yeni sentezleme sabit bir ekspresyon (§15.28) olmadıkça (§12.5) oluşturulur. "Kelimenin tam anlamıyla," yeni oluşturulan "bir dizenin farklı bir nesne kimliği olması gerektiğinden, son olmayan sürümde bir optimizasyon uygulanmasına izin verilmez. Bunun kasıtlı olup olmadığını bilmiyorum. Sonuçta, mevcut derleme stratejisi, bu tür kısıtlamaları belgelemeyen bir çalışma zamanı tesisine delege etmektir. String
Holger

31

Araştırmaya göre, hepsi final StringJava'da stajyer. Blog gönderisinden birinden:

Bu nedenle, == veya! = Kullanarak iki Dizeyi gerçekten karşılaştırmanız gerekiyorsa, karşılaştırma yapmadan önce String.intern () yöntemini çağırdığınızdan emin olun. Aksi takdirde, her zaman String karşılaştırması için String.equals (String) tercih edin.

Yani arama String.intern()yaparsanız iki dizeyi ==operatör kullanarak karşılaştırabilirsiniz . Ama burada String.intern()gerekli değil çünkü Java'da final Stringdahili olarak stajyer.

== operator ve Javadoc for String.intern () yöntemini kullanarak Dize karşılaştırması hakkında daha fazla bilgi bulabilirsiniz .

Daha fazla bilgi için bu Stackoverflow yayınına da bakın .


3
intern () dizeleri çöp toplanmaz ve düşük olan permgen boşluğunda saklanır, böylece düzgün kullanılmazsa bellek yetersizliği gibi sorun yaşarsınız .
Ajeesh

@Ajeesh - İnterned dizeleri çöp toplanabilir. Sabit ifadelerden kaynaklanan sabitlenmiş dizeler bile bazı durumlarda çöp toplanabilir.
Stephen C

21

Bu yöntemlere bakarsanız

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");
}

ve javap -c ClassWithTheseMethods göreceğiniz sürümlerle ayrıştırılmış

  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;
      ...

ve

  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      
       ...

Yani Dizeler son değilse derleyici StringBuilderbitiştirmek için kullanmak zorunda kalacak str1ve str2böylece

String concat=str1+str2;

derlenecek

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

Bu concat, çalışma zamanında oluşturulacağı ve bu nedenle String havuzundan gelmeyeceği anlamına gelir .


Ayrıca Dizeler sonsa, derleyici asla değişmeyeceklerini varsayabilir, böylece kullanmak yerine StringBuilderdeğerlerini güvenli bir şekilde birleştirebilir.

String concat = str1 + str2;

olarak değiştirilebilir

String concat = "str" + "ing";

ve birleştirildi

String concat = "string";

bu da concatedize havuzunda stajyer olacak ve daha sonra ififadede bu havuzdaki aynı dize değişmez değeriyle karşılaştırılacak bir değişmez değişmez anlamına gelir .


15

Yığın ve dize conts havuzu kavramı resim açıklamasını buraya girin


6
Ne? Bunun nasıl oy verdiğini anlamıyorum. Cevabınızı netleştirebilir misiniz?
Cᴏʀʏ

Ben amaçlanan cevap str1 + str2 stajyer bir dize için optimize olmadığı için, dize havuzundan bir dize ile karşılaştırma yanlış bir duruma yol açacağını düşünüyorum.
viki.omega9

3

finalÖrnek için bazı bayt kodlarını görelim

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
}

En 0:ve 2:, String "string"(sabit havuzundan) depo üzerine itilir ve yerel bir değişken depolanır concatşirketinden. Derleyicinin String "string"derleme zamanında kendisini oluşturduğunu (birleştirdiğini) görebilirsiniz .

finalBayt olmayan kod

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
}

Burada iki Stringsabitiniz var "str"ve "ing"bunlar çalışma zamanında a ile birleştirilmelidir StringBuilder.


0

Bununla birlikte, Java'nın String değişmez gösterimini kullanarak oluşturduğunuzda, havuzda bulunmaması kaydıyla bu nesneyi String havuzuna koymak için intern () yöntemini otomatik olarak çağırır.

Final neden bir fark yaratır?

Derleyici son değişkeni asla değişmeyeceğini bilir, bu son değişkenleri eklediğimizde, str1 + str2ifade çıktısı da asla değişmeyeceğinden çıktı String Pool'a gider . Nihai değişken olmayan derleyici durumunda stajyer yöntemini çağırmayın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.