प्रभावी रूप से अंतिम बनाम अंतिम - अलग व्यवहार


104

अब तक मैंने सोचा था कि प्रभावी रूप से अंतिम और फाइनल कम या ज्यादा समतुल्य हैं और वास्तविक व्यवहार में समान नहीं होने पर जेएलएस उनके साथ समान व्यवहार करेंगे। तब मुझे यह विवादित परिदृश्य मिला:

final int a = 97;
System.out.println(true ? a : 'c'); // outputs a

// versus

int a = 97;
System.out.println(true ? a : 'c'); // outputs 97

जाहिरा तौर पर, जेएलएस यहां दोनों के बीच एक महत्वपूर्ण अंतर बनाता है और मुझे यकीन नहीं है कि क्यों।

मैंने अन्य सूत्र जैसे पढ़े

लेकिन वे इस तरह के विस्तार में नहीं जाते हैं। आखिरकार, एक व्यापक स्तर पर वे बहुत अधिक समतुल्य दिखाई देते हैं। लेकिन गहरी खुदाई, वे स्पष्ट रूप से अलग हैं।

इस व्यवहार के कारण क्या है, कोई भी कुछ जेएलएस परिभाषाएं प्रदान कर सकता है जो इसे समझाते हैं?


संपादित करें: मुझे एक और संबंधित परिदृश्य मिला:

final String a = "a";
System.out.println(a + "b" == "ab"); // outputs true

// versus

String a = "a";
System.out.println(a + "b" == "ab"); // outputs false

तो स्ट्रिंग इंटर्निंग भी यहां अलग तरह से व्यवहार करती है (मैं इस स्निपेट को वास्तविक कोड में उपयोग नहीं करना चाहता, बस अलग व्यवहार के बारे में उत्सुक हूं)।


2
बहुत दिलचस्प सवाल! मुझे उम्मीद है कि जावा दोनों मामलों के लिए समान व्यवहार करेगा, लेकिन मैं अब प्रबुद्ध हूं। मैं खुद से पूछ रहा हूं कि क्या यह हमेशा व्यवहार था या यदि यह पिछले संस्करणों में अलग है
लिनो

8
नीचे महान जवाब में पिछले बोली के लिए शब्दों @Lino के लिए एक ही सभी तरह से वापस आ गया है जावा 6 : "ऑपरेंड का एक प्रकार का है, तो टी जहां टी है byte, shortया char, और अन्य संकार्य के एक निरंतर अभिव्यक्ति है प्रकार intजिसका मान टी में टाइप करने योग्य है , तो सशर्त अभिव्यक्ति का प्रकार टी है । ” --- यहां तक ​​कि बर्कले में जावा 1.0 डॉक भी पाया गया। एक ही पाठ । --- हाँ, यह हमेशा से रहा है।
एंड्रियास

1
जिस तरह से आप "चीजें"
ढूंढते

जवाबों:


66

सबसे पहले, हम केवल स्थानीय चर के बारे में बात कर रहे हैं । प्रभावी रूप से अंतिम क्षेत्रों पर लागू नहीं होता है। यह महत्वपूर्ण है, क्योंकि finalखेतों के लिए शब्दार्थ बहुत अलग हैं और भारी संकलक अनुकूलन और मेमोरी मॉडल वादों के अधीन हैं, अंतिम क्षेत्रों के शब्दार्थों पर $ 17.5.1 देखें ।

सतह के स्तर पर finalऔर effectively finalस्थानीय चर के लिए वास्तव में समान हैं। हालांकि, जेएलएस दोनों के बीच एक स्पष्ट अंतर बनाता है जो वास्तव में इस तरह की विशेष स्थितियों में प्रभाव की एक विस्तृत श्रृंखला है।


आधार

चर के बारे में JLS§4.12.4 से final:

एक स्थिर चर आदिम प्रकार या स्ट्रिंग का एक finalचर है जो एक स्थिर अभिव्यक्ति ( .215.29 ) के साथ प्रारंभ होता है । वैरिएबल एक स्थिर वैरिएबल है या क्लास इनिशियलाइज़ेशन ( is12.4.1 ), बाइनरी कम्पैटिबिलिटी ( compatibility13.1 ), रीचैबिलिटी ( .214.22 ), और निश्चित असाइनमेंट ( §16.1.1 ) के संबंध में निहितार्थ नहीं हो सकता है

चूँकि intआदिम है, चर aएक ऐसा स्थिर चर है

आगे, इसी अध्याय के बारे में effectively final:

कुछ चर जिन्हें अंतिम घोषित नहीं किया जाता है, उन्हें प्रभावी रूप से अंतिम माना जाता है: ...

तो जिस तरह से यह शब्दों में है, यह है कि अन्य उदाहरण में, स्पष्ट है aहै नहीं एक निरंतर चर माना जाता है, के रूप में यह है अंतिम नहीं है, लेकिन केवल प्रभावी ढंग से अंतिम।


व्यवहार

अब जब हमारे पास अंतर है, तो यह देखने की सुविधा देता है कि क्या चल रहा है और आउटपुट अलग क्यों है।

आप ? :यहां सशर्त ऑपरेटर का उपयोग कर रहे हैं, इसलिए हमें इसकी परिभाषा जांचनी होगी। से JLS§15.25 :

तीन प्रकार की सशर्त अभिव्यक्तियाँ हैं, दूसरे और तीसरे संक्रियात्मक अभिव्यक्तियों के अनुसार वर्गीकृत: बूलियन सशर्त अभिव्यक्तियाँ , संख्यात्मक सशर्त अभिव्यक्तियाँ और संदर्भ सशर्त अभिव्यक्तियाँ

इस मामले में, हम JLS.215.25.2 से एक संख्यात्मक सशर्त अभिव्यक्तियों के बारे में बात कर रहे हैं :

एक संख्यात्मक सशर्त अभिव्यक्ति का प्रकार निम्नानुसार निर्धारित किया जाता है:

और यह वह हिस्सा है जहां दो मामलों को अलग-अलग वर्गीकृत किया जाता है।

प्रभावी रूप से अंतिम

effectively finalइस नियम से मेल खाने वाला संस्करण :

अन्यथा, सामान्य संख्यात्मक पदोन्नति ( §5.6 ) दूसरे और तीसरे ऑपरेंड पर लागू होती है, और सशर्त अभिव्यक्ति का प्रकार दूसरे और तीसरे ऑपरेंड का प्रचारित प्रकार है।

कौन करता है, तो आप क्या करेंगे के रूप में ही व्यवहार है 5 + 'd', यानी int + char, में जो परिणाम intJLS§5.6 देखें

न्यूमेरिक प्रमोशन एक संख्यात्मक संदर्भ में सभी अभिव्यक्तियों के प्रचारित प्रकार को निर्धारित करता है। पदोन्नत प्रकार को इस तरह चुना जाता है कि प्रत्येक अभिव्यक्ति को पदोन्नत प्रकार में परिवर्तित किया जा सकता है, और, अंकगणितीय ऑपरेशन के मामले में, ऑपरेशन को पदोन्नत प्रकार के मूल्यों के लिए परिभाषित किया गया है। संख्यात्मक संदर्भ में अभिव्यक्तियों का क्रम संख्यात्मक संवर्धन के लिए महत्वपूर्ण नहीं है। नियम इस प्रकार हैं:

[...]

इसके बाद, आदिम रूपांतरण को चौड़ा ( §5.1.2 ) और आदिम रूपांतरण संकुचन ( §5.1.3 ) कुछ भाव पर लागू होते हैं, निम्नलिखित नियमों के अनुसार:

संख्यात्मक विकल्प के संदर्भ में, निम्नलिखित नियम लागू होते हैं:

किसी भी अभिव्यक्ति प्रकार का है, तो intऔर है एक निरंतर अभिव्यक्ति नहीं ( §15.29 ), तो पदोन्नत प्रकार है int, और अन्य भाव उस प्रकार के नहीं हैं intगुजरना आदिम रूपांतरण को चौड़ा करने के लिए int

तो सब कुछ के intरूप aमें एक intपहले से ही पदोन्नत किया है । के आउटपुट के बारे में बताते हैं 97

अंतिम

finalचर के साथ संस्करण इस नियम से मेल खाता है:

यदि ऑपरेंड में से एक प्रकार का है , Tजहां Tहै byte, shortया char, और अन्य ऑपरेंड प्रकार का एक स्थिर अभिव्यक्ति ( of15.29 ) है intजिसका मान प्रकार में प्रतिनिधित्व करने योग्य है T, तो सशर्त अभिव्यक्ति का प्रकार है T

अंतिम चर aप्रकार intऔर एक स्थिर अभिव्यक्ति है (क्योंकि यह है final)। यह के रूप में प्रतिनिधित्व करने योग्य है char, इसलिए परिणाम प्रकार का है char। जो आउटपुट को समाप्त करता है a


स्ट्रिंग उदाहरण

स्ट्रिंग समानता के साथ उदाहरण समान मूल अंतर पर आधारित है, finalचर को स्थिर अभिव्यक्ति / चर के रूप में माना जाता है, और effectively finalनहीं है।

जावा में, स्ट्रिंग इंटर्निंग निरंतर अभिव्यक्तियों पर आधारित है, इसलिए

"a" + "b" + "c" == "abc"

है trueके रूप में अच्छी तरह से (न वास्तविक कोड में इस संरचना का उपयोग करें)।

देखें JLS§3.10.5 :

इसके अलावा, एक स्ट्रिंग शाब्दिक हमेशा कक्षा स्ट्रिंग के एक ही उदाहरण को संदर्भित करता है। ऐसा इसलिए है क्योंकि स्ट्रिंग शाब्दिक - या, अधिक सामान्यतः , स्ट्रिंग्स जो निरंतर अभिव्यक्तियों ( .215.29 ) के मान हैं - "इंटर्न" हैं, इसलिए अद्वितीय उदाहरणों को साझा करने के लिए, विधि का उपयोग करके String.intern( .512.5 )।

अनदेखी करना आसान है क्योंकि यह मुख्य रूप से शाब्दिक के बारे में बात कर रहा है, लेकिन यह वास्तव में निरंतर अभिव्यक्तियों पर भी लागू होता है।


8
समस्या यह है कि आप ... ? a : 'c'वैसा ही व्यवहार करने की अपेक्षा करेंगे चाहे aवह परिवर्तनशील या स्थिर हो । स्पष्ट रूप से अभिव्यक्ति के साथ कुछ भी गलत नहीं है। --- इसके विपरीत, a + "b" == "ab"एक खराब अभिव्यक्ति है , क्योंकि स्ट्रिंग्स का उपयोग करके तुलना करने की आवश्यकता है equals()( मैं जावा में स्ट्रिंग्स की तुलना कैसे करूं? )। तथ्य यह है कि यह "गलती से" काम करता है जब aएक स्थिर होता है , स्ट्रिंग स्ट्रिंगर्स के इंटर्निंग का सिर्फ एक उद्धरण होता है।
एंड्रियास

5
@ और हां, लेकिन ध्यान दें कि स्ट्रिंग इंटर्निंग जावा की स्पष्ट रूप से परिभाषित विशेषता है। यह एक संयोग नहीं है जो कल या अलग JVM में बदल सकता है। किसी भी मान्य जावा कार्यान्वयन में "a" + "b" + "c" == "abc"होना चाहिए true
ज़बूज़र्ड

10
सच है, यह एक अच्छी तरह से परिभाषित क्वर्क है, लेकिन a + "b" == "ab"अभी भी एक गलत अभिव्यक्ति है । यहां तक ​​कि अगर आपको पता है कि aयह एक स्थिर है , तो कॉल न करने के लिए बहुत अधिक त्रुटि है equals()। या हो सकता है कि नाजुक एक बेहतर शब्द है, यानी भविष्य में कोड बनाए रखने पर भी गिरने की संभावना है।
एंड्रियास

2
ध्यान दें कि प्रभावी रूप से अंतिम चर के प्राथमिक डोमेन में भी, अर्थात लैम्ब्डा अभिव्यक्ति में उनका उपयोग, अंतर रनटाइम व्यवहार को बदल सकता है, अर्थात यह एक कैप्चरिंग और एक गैर-कैप्चरिंग लैम्ब्डा अभिव्यक्ति के बीच अंतर कर सकता है, एक सिंगलटन के बाद का मूल्यांकन। , लेकिन पूर्व एक नई वस्तु का उत्पादन कर रहा है। दूसरे शब्दों में, (final) String str = "a"; Stream.of(null, null). <Runnable>map( x -> () -> System.out.println(str)) .reduce((a,b) -> () -> System.out.println(a == b)) .ifPresent(Runnable::run);इसका परिणाम तब बदलता है जब strवह है (नहीं) final
होल्गर

7

एक अन्य पहलू यह है कि यदि चर को विधि के शरीर में अंतिम घोषित किया जाता है, तो पैरामीटर के रूप में पारित अंतिम चर से एक अलग व्यवहार होता है।

public void testFinalParameters(final String a, final String b) {
  System.out.println(a + b == "ab");
}

...
testFinalParameters("a", "b"); // Prints false

जबकि

public void testFinalVariable() {
   final String a = "a";
   final String b = "b";
   System.out.println(a + b == "ab");  // Prints true
}

...
testFinalVariable();

यह इसलिए होता है क्योंकि संकलक जानता है कि का उपयोग कर चर हमेशा होगा कि इतने मूल्य और समस्याओं के बिना आपस में बदल जा सकता है। अलग-अलग, अगर परिभाषित नहीं किया गया है या इसे परिभाषित किया गया है, लेकिन इसका मान रनटाइम पर दिया गया है (जैसा कि ऊपर के उदाहरण में जहां पैरामीटर है) कंपाइलर को इसके उपयोग से पहले कुछ भी नहीं पता है। इसलिए रनटाइम पर कॉन्सेप्टेशन होता है और इंटर्न पूल का उपयोग न करते हुए एक नया स्ट्रिंग जनरेट किया जाता है।final String a = "a"a"a"a"a"afinalfinala


मूल रूप से व्यवहार है: यदि कंपाइलर जानता है कि एक चर एक स्थिर है तो इसे निरंतर का उपयोग करने के रूप में उपयोग कर सकते हैं।

यदि चर को अंतिम रूप से परिभाषित नहीं किया गया है (या यह अंतिम है लेकिन इसका मान रनटाइम पर परिभाषित किया गया है) तो संकलक के पास इसे एक स्थिर के रूप में भी संभालने का कोई कारण नहीं है यदि इसका मूल्य स्थिर के बराबर है और इसका मान कभी नहीं बदला जाता है।


4
इसमें कुछ भी अजूबा नहीं है :)
dbl

2
यह सवाल का दूसरा पहलू है।
डेविड लोरेंजो मैरिनो

5
finelकिसी पैरामीटर पर लागू किया गया कीवर्ड, finalस्थानीय वेरिएबल, आदि पर लागू होने से अलग शब्दार्थ है ...
dbl

6
पैरामीटर का उपयोग करना यहां अनावश्यक अड़चन है। आप बस कर सकते हैं final String a; a = "a";और एक ही व्यवहार प्राप्त कर सकते हैं
yawkat
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.