जावा में अंतिम घोषित किए गए == के साथ तार की तुलना करें


220

मेरे पास जावा में स्ट्रिंग्स के बारे में एक सरल प्रश्न है। सरल कोड का निम्नलिखित खंड सिर्फ दो तारों को समेटता है और फिर उनकी तुलना करता है ==

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

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

तुलनात्मक अभिव्यक्ति स्पष्ट रूप से concat=="string"लौटती falseहै (मैं बीच का अंतर समझता हूं equals()और ==)।


जब इन दो तारों को इस finalतरह घोषित किया जाता है,

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

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

तुलनात्मक अभिव्यक्ति concat=="string", इस मामले में वापस आती है true। फर्क क्यों पड़ता है final? क्या इसे इंटर्न पूल के साथ कुछ करना है या मुझे सिर्फ गुमराह किया जा रहा है?


22
मैंने हमेशा यह मूर्खतापूर्ण पाया कि बराबर = समान सामग्री के लिए जाँच करने का डिफ़ॉल्ट तरीका था, = = ऐसा करने के लिए और बिंदुओं के समान होने पर जाँचने के लिए संदर्भ संदर्भ या कुछ समान का उपयोग करें।
डेविओ

25
यह "मैं जावा में तार की तुलना कैसे करूं?" की नकल नहीं है। किसी भी प्रकार। ओपी स्ट्रिंग्स के संदर्भ में equals()और उनके बीच के अंतर को समझता है ==, और अधिक सार्थक प्रश्न पूछ रहा है।
१।

@Davio लेकिन जब क्लास नहीं है तो वह कैसे काम करेगा String? मुझे लगता है कि यह बहुत तार्किक है, मूर्खतापूर्ण नहीं है कि सामग्री की तुलना किसके द्वारा की गई है equals, एक विधि जिसे हम यह बताने के लिए ओवरराइड कर सकते हैं जब हम दो वस्तुओं को समान मानते हैं, और पहचान की तुलना की है ==। यदि सामग्री तुलना द्वारा किया गया था ==हम उस रद्द कर सकते थे नहीं कि हम क्या "बराबर सामग्री" मतलब परिभाषित करने के लिए, और के अर्थ होने equalsऔर ==केवल उलट के लिए Stringरों मूर्खतापूर्ण होगा। इसके अलावा, इसकी परवाह किए बिना, मुझे ==इसके बजाय सामग्री तुलना करने में कोई फायदा नहीं दिख रहा है equals
शांतिबेलर्स

@SantiBailors आप सही हैं कि यह सिर्फ जावा में काम करता है, मैंने C # का भी उपयोग किया है जहाँ == सामग्री समानता के लिए अतिभारित है। == के उपयोग का एक अतिरिक्त बोनस यह है कि यह शून्य-सुरक्षित है: (अशक्त == "कुछ") गलत है। यदि आप 2 ऑब्जेक्ट के लिए बराबर का उपयोग करते हैं, तो आपको जागरूक होना होगा यदि या तो अशक्त हो सकता है या आप NullPointerException को फेंकने का जोखिम उठाते हैं।
डेविओ

जवाबों:


232

जब आप एक String(जो अपरिवर्तनीय है ) को चर घोषित करते हैं final, और इसे एक संकलन-समय स्थिर अभिव्यक्ति के साथ आरंभ करते हैं, तो यह एक संकलन-समय स्थिर अभिव्यक्ति भी बन जाता है, और इसका मान संकलक द्वारा इनलाइन किया जाता है जहां इसका उपयोग किया जाता है। इसलिए, आपके दूसरे कोड उदाहरण में, मानों को सम्मिलित करने के बाद, संकलक द्वारा स्ट्रिंग संयोजन का अनुवाद किया जाता है:

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

जब आप तुलना "string"करेंगे true, क्योंकि स्ट्रिंग शाब्दिक को नजरबंद कर दिया गया है

से JLS §4.12.4 - finalचर :

आदिम प्रकार या प्रकार का एक चर String, जो finalएक संकलन-समय स्थिर अभिव्यक्ति (.215.28) के साथ शुरू होता है, एक स्थिर चर कहलाता है ।

इसके अलावा JLS से .215.28 - लगातार अभिव्यक्ति:

प्रकार का संकलन-समय स्थिर अभिव्यक्ति Stringहमेशा "नजरबंद" होती है, ताकि विधि का उपयोग करके, अद्वितीय उदाहरणों को साझा किया जा सके String#intern()


यह आपके पहले कोड उदाहरण में नहीं है, जहां Stringचर नहीं हैं final। तो, वे एक संकलन-समय स्थिर अभिव्यक्ति नहीं हैं। इस अवधि के संचालन में विलंब होगा, इस प्रकार एक नई Stringवस्तु के निर्माण की ओर अग्रसर होगा । आप दोनों कोड के बाइट कोड की तुलना करके इसे सत्यापित कर सकते हैं।

पहला कोड उदाहरण (गैर- finalसंस्करण) निम्नलिखित बाइट कोड के लिए संकलित है:

  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

स्पष्ट रूप से यह भंडारण strऔर ingदो अलग-अलग चर में है, और StringBuilderसंघनन ऑपरेशन करने के लिए उपयोग कर रहा है।

जबकि, आपका दूसरा कोड उदाहरण ( finalसंस्करण) इस तरह दिखता है:

  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

तो यह सीधे stringसंकलित करने के लिए अंतिम समय में चर बनाता है , जो ldcचरण में ऑपरेशन द्वारा लोड किया जाता है 0। फिर ldcचरण में ऑपरेशन द्वारा दूसरा स्ट्रिंग शाब्दिक लोड किया जाता है 7। इसमें Stringरनटाइम पर किसी भी नई वस्तु का निर्माण शामिल नहीं है। स्ट्रिंग पहले से ही संकलन समय पर जाना जाता है, और उन्हें नजरबंद कर दिया जाता है।


2
अन्य जावा कंपाइलर कार्यान्वयन को रोकने के लिए कुछ भी नहीं है जो एक अंतिम स्ट्रिंग को सही नहीं है?
एल्विन

13
@ एल्विन जेएलएस के लिए आवश्यक है कि संकलन-समय स्थिर स्ट्रिंग अभिव्यक्तियों को नजरबंद किया जाए। किसी भी अनुरूप कार्यान्वयन को यहाँ एक ही काम करना होगा।
टावियन बार्न्स

इसके विपरीत, क्या जेएलएस जनादेश देता है कि एक संकलक को पहले, गैर-अंतिम संस्करण में संघनन का अनुकूलन नहीं करना चाहिए ? क्या कंपाइलर को कोड बनाने से मना किया गया है, जिसकी तुलना मूल्यांकन से होगी true?
फं .0

1
@ के मौजूदा शब्दों लेने phant0m विनिर्देश , " वस्तु नव निर्मित है (§12.5) जब तक अभिव्यक्ति एक निरंतर अभिव्यक्ति (§15.28) है। "वस्तुतः, गैर-अंतिम संस्करण में अनुकूलन लागू करने की अनुमति नहीं है, क्योंकि" नव निर्मित "स्ट्रिंग में एक अलग ऑब्जेक्ट पहचान होनी चाहिए। मुझे नहीं पता कि यह जानबूझकर है या नहीं। आखिरकार, वर्तमान संकलन रणनीति एक रनटाइम सुविधा को सौंपना है जो इस तरह के प्रतिबंधों का दस्तावेजीकरण नहीं करता है। String
होल्गर

31

मेरे शोध के अनुसार, सभी final Stringजावा में नजरबंद हैं। ब्लॉग पोस्ट में से एक से:

इसलिए, अगर आपको वास्तव में == या = का उपयोग करके दो स्ट्रिंग की तुलना करने की आवश्यकता है, तो सुनिश्चित करें कि तुलना करने से पहले आप String.intern () विधि को कॉल करें। अन्यथा, स्ट्रिंग तुलना के लिए हमेशा String.equals (स्ट्रिंग) को प्राथमिकता दें।

तो इसका मतलब है कि यदि आप कॉल String.intern()करते हैं तो आप ==ऑपरेटर का उपयोग करके दो तारों की तुलना कर सकते हैं । लेकिन यहाँ String.intern()आवश्यक नहीं है क्योंकि जावा final Stringमें आंतरिक रूप से इंटर्न हैं।

आप अधिक जानकारी पा सकते हैं == ऑपरेटर और Javadoc String.intern () पद्धति का उपयोग करके

अधिक जानकारी के लिए इस Stackoverflow पोस्ट को देखें।


3
इंटर्न () तार एकत्र नहीं किए जाते हैं और इसे पर्मगेन स्पेस में संग्रहित किया जाता है जो कि कम होता है इसलिए यदि आप सही तरीके से उपयोग नहीं किए जाते हैं तो आपको मेमोरी एरर जैसी परेशानी हो जाएगी ।
अजेश

@ अंजेश - इंटर्नेटेड स्ट्रिंग्स को इकट्ठा किया जा सकता है। यहां तक ​​कि निरंतर अभिव्यक्तियों से उत्पन्न होने वाले तार कुछ परिस्थितियों में एकत्र किए गए कचरा हो सकते हैं।
स्टीफन सी

21

यदि आप इस तरीकों पर एक नज़र डालें

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

और इसके विघटित javap -c ClassWithTheseMethods संस्करणों के साथ आप देखेंगे

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

तथा

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

तो अगर तार नहीं कर रहे हैं अंतिम संकलक का उपयोग करना होगा StringBuilderCONCATENATE करने str1और str2इसलिए

String concat=str1+str2;

के लिए संकलित किया जाएगा

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

जिसका अर्थ है कि concatरनटाइम पर बनाया जाएगा इसलिए स्ट्रिंग पूल से नहीं आएगा।


इसके अलावा अगर स्ट्रिंग्स अंतिम हैं, तो संकलक मान सकता है कि वे कभी नहीं बदलेंगे ताकि StringBuilderइसका उपयोग करने के बजाय यह सुरक्षित रूप से अपने मूल्यों को सुरक्षित कर सके

String concat = str1 + str2;

को बदला जा सकता है

String concat = "str" + "ing";

और में समाप्‍त

String concat = "string";

जिसका मतलब है कि concateस्टिंग शाब्दिक बन जाएगा जिसे स्ट्रिंग पूल में नजरबंद किया जाएगा और फिर ifबयान में उस पूल से उसी स्ट्रिंग शाब्दिक की तुलना की जाएगी ।


15

स्टैक और स्ट्रिंग कॉन्स पूल अवधारणा यहां छवि विवरण दर्ज करें


6
क्या? मुझे समझ में नहीं आता कि यह कैसे होता है। क्या आप अपना उत्तर स्पष्ट कर सकते हैं?
C

मुझे लगता है कि इरादा जवाब यह है कि क्योंकि str1 + str2 को एक इंटर्न स्ट्रिंग के साथ अनुकूलित नहीं किया गया है, स्ट्रिंग पूल से स्ट्रिंग के साथ तुलना करने से झूठी स्थिति पैदा होगी।
viki.omega9 3

3

आइए, finalउदाहरण के लिए कुछ बाइट कोड देखें

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
}

पर 0:और 2:, String "string"(निरंतर पूल से) ढेर पर धक्का दे दिया और स्थानीय चर में संग्रहीत किया जाता है concatसीधे। आप यह String "string"संकलित कर सकते हैं कि संकलन समय पर ही कंपाइलर (कॉन्कैटनेटिंग) बना रहा है।

गैर finalबाइट कोड

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
}

यहाँ आपके पास दो Stringस्थिरांक हैं, "str"और "ing"जिन्हें अ के साथ रनटाइम पर संक्षिप्त करने की आवश्यकता है StringBuilder


0

हालाँकि, जब आप जावा के स्ट्रिंग शाब्दिक अंकन का उपयोग करते हुए बनाते हैं, तो यह उस ऑब्जेक्ट को स्ट्रिंग पूल में डालने के लिए स्वचालित रूप से इंटर्न () विधि को कॉल करता है, बशर्ते वह पहले से ही पूल में मौजूद नहीं था।

फाइनल में फ़र्क क्यों पड़ता है?

कंपाइलर जानता है कि अंतिम चर कभी भी बदलाव नहीं करता है, जब हम इन अंतिम चर को जोड़ते हैं तो आउटपुट स्ट्रिंग पूल में चला जाता है क्योंकि str1 + str2अभिव्यक्ति का उत्पादन भी कभी नहीं बदलता है, इसलिए अंत में संकलक उपरोक्त दो अंतिम चर के आउटपुट के बाद इंटर विधि को कॉल करता है। गैर-अंतिम चर संकलक के मामले में, आंतरिक विधि को नहीं कहते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.