java - क्या अंतिम बीमार परिभाषित है?




final class-variables (4)

सबसे पहले, एक पहेली: निम्नलिखित कोड क्या प्रिंट करता है?

public class RecursiveStatic {
    public static void main(String[] args) {
        System.out.println(scale(5));
    }

    private static final long X = scale(10);

    private static long scale(long value) {
        return X * value;
    }
}

उत्तर:

0

नीचे स्पॉइलर।

यदि आप X को स्केल (लॉन्ग) में प्रिंट करते हैं और X = scale(10) + 3 फिर से परिभाषित करते हैं, तो प्रिंट X = 0 तब X = 3 । इसका मतलब है कि X अस्थायी रूप से 0 सेट है और बाद में 3 सेट है। यह final उल्लंघन है!

स्थिर संशोधक, अंतिम संशोधक के संयोजन में, स्थिरांक को परिभाषित करने के लिए भी उपयोग किया जाता है। अंतिम संशोधक इंगित करता है कि इस क्षेत्र का मान नहीं बदल सकता है

स्रोत: https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html [जोर]

मेरा प्रश्न: यह एक बग है? क्या final बीमार परिभाषित है?

यहाँ वह कोड है जिसमें मुझे दिलचस्पी है। X को दो अलग-अलग मान दिए गए हैं: 0 और 3 । मैं इसे final उल्लंघन मानता हूं।

public class RecursiveStatic {
    public static void main(String[] args) {
        System.out.println(scale(5));
    }

    private static final long X = scale(10) + 3;

    private static long scale(long value) {
        System.out.println("X = " + X);
        return X * value;
    }
}

इस प्रश्न को जावा स्टैटिक फाइनल फील्ड इनिशियलाइज़ेशन ऑर्डर के संभावित डुप्लिकेट के रूप में चिह्नित किया गया है। मेरा मानना ​​है कि यह प्रश्न कोई डुप्लिकेट नहीं है क्योंकि दूसरा प्रश्न इनिशियलाइज़ेशन के आदेश को संबोधित करता है जबकि मेरा प्रश्न साइकल इनिशियलाइज़ेशन को final टैग के साथ जोड़ देता है। अकेले दूसरे प्रश्न से, मैं यह नहीं समझ पाऊँगा कि मेरे प्रश्न का कोड त्रुटि क्यों नहीं करता है।

यह विशेष रूप से उस आउटपुट को देखकर स्पष्ट होता है जो ernesto को मिलता है: जब a को final साथ टैग किया जाता है, तो उसे निम्न आउटपुट मिलता है:

a=5
a=5

जो मेरे प्रश्न का मुख्य भाग शामिल नहीं करता है: final चर अपने चर को कैसे बदलता है?


एक बहुत ही दिलचस्प खोज। इसे समझने के लिए हमें जावा लैंग्वेज स्पेसिफिकेशन ( JLS ) में खुदाई करने की आवश्यकता है।

कारण यह है कि final केवल एक असाइनमेंट की अनुमति देता है। हालाँकि, डिफ़ॉल्ट मान कोई असाइनमेंट नहीं है। वास्तव में, ऐसे प्रत्येक चर (वर्ग चर, उदाहरण चर, सरणी घटक) असाइनमेंट से पहले शुरुआत से ही अपने डिफ़ॉल्ट मूल्य की ओर इशारा करते हैं । पहले असाइनमेंट फिर संदर्भ बदलता है।

कक्षा चर और डिफ़ॉल्ट मान

निम्नलिखित उदाहरण पर एक नज़र डालें:

private static Object x;

public static void main(String[] args) {
    System.out.println(x); // Prints 'null'
}

हमने स्पष्ट रूप से x लिए एक मान निर्दिष्ट नहीं किया है, हालांकि यह null करने के लिए इंगित करता है, यह डिफ़ॉल्ट मूल्य है। इसकी तुलना §4.12.5 :

चर के प्रारंभिक मान

प्रत्येक वर्ग चर , उदाहरण चर, या सरणी घटक एक डिफ़ॉल्ट मान के साथ आरम्भ किया जाता है जब इसे बनाया जाता है ( §15.9 , §15.10.2 )

ध्यान दें कि यह केवल उन प्रकार के चरों के लिए है, जैसे हमारे उदाहरण में। यह स्थानीय चर के लिए नहीं है, निम्न उदाहरण देखें:

public static void main(String[] args) {
    Object x;
    System.out.println(x);
    // Compile-time error:
    // variable x might not have been initialized
}

उसी JLS पैराग्राफ से:

एक स्थानीय चर ( §14.4 , §14.14 ) को उपयोग करने से पहले स्पष्ट रूप से एक मूल्य दिया जाना चाहिए, या तो आरंभीकरण ( §14.4 ) या असाइनमेंट ( §15.26 ) द्वारा, एक तरह से जिसे निश्चित असाइनमेंट के नियमों का उपयोग करके सत्यापित किया जा सकता है ( §) 16 (निश्चित असाइनमेंट) )।

अंतिम चर

अब हम §4.12.4 से final पर एक नज़र §4.12.4 :

अंतिम चर

एक चर को अंतिम घोषित किया जा सकता है। एक अंतिम चर केवल एक बार सौंपा जा सकता है। यह एक संकलित-समय की त्रुटि है यदि अंतिम चर असाइन किया गया है जब तक कि असाइनमेंट ( Ass16 (निश्चित असाइनमेंट) ) से पहले इसे निश्चित रूप से अनसाइन नहीं किया गया है

व्याख्या

अब अपने उदाहरण पर वापस आते हैं, थोड़ा संशोधित:

public static void main(String[] args) {
    System.out.println("After: " + X);
}

private static final long X = assign();

private static long assign() {
    // Access the value before first assignment
    System.out.println("Before: " + X);

    return X + 1;
}

यह आउटपुट करता है

Before: 0
After: 1

हमने जो सीखा है, उसे याद करें। विधि के अंदर चर X को अभी तक एक मान नहीं सौंपा गया था। इसलिए, यह एक वर्ग चर है क्योंकि यह डिफ़ॉल्ट मान को इंगित करता है और जेएलएस के अनुसार उन चर हमेशा अपने डिफ़ॉल्ट मानों (स्थानीय चर के विपरीत) को तुरंत इंगित करते हैं। असाइनमेंट विधि के बाद वेरिएबल X को मान 1 सौंपा गया है और final होने के कारण हम इसे और नहीं बदल सकते। तो final कारण निम्नलिखित काम नहीं करेगा:

private static long assign() {
    // Assign X
    X = 1;

    // Second assign after method will crash
    return X + 1;
}

जेएलएस में उदाहरण

@Andrew के लिए धन्यवाद मुझे एक JLS पैराग्राफ़ मिला जो इस परिदृश्य को बिल्कुल कवर करता है, यह इसे प्रदर्शित भी करता है।

लेकिन पहले एक नजर डालते हैं

private static final long X = X + 1;
// Compile-time error:
// self-reference in initializer

यह अनुमति क्यों नहीं है, जबकि विधि से पहुंच है? §8.3.3 पर एक नज़र §8.3.3 जो फ़ील्ड तक पहुँच प्राप्त करने के बारे में बात करती है यदि फ़ील्ड अभी तक आरंभीकृत नहीं किया गया है।

यह वर्ग चर के लिए प्रासंगिक कुछ नियमों को सूचीबद्ध करता है:

वर्ग या इंटरफ़ेस C में घोषित वर्ग चर f साधारण नाम के संदर्भ के लिए, यह एक संकलित समय त्रुटि है :

  • संदर्भ C एक वर्ग चर आरंभीकरण या C ( §8.7 ) के एक स्थिर इनिशियलाइज़र में दिखाई देता C ; तथा

  • संदर्भ या तो स्वयं के घोषणाकर्ता के आरंभीकरण में या f के f के बाईं ओर एक बिंदु पर दिखाई देता है; तथा

  • संदर्भ एक असाइनमेंट अभिव्यक्ति के बाएं हाथ की ओर नहीं है ( §15.26 ); तथा

  • संदर्भ को घेरने वाला अंतरतम वर्ग या इंटरफ़ेस C

यह सरल है, X = X + 1 उन नियमों से पकड़ा जाता है, विधि का उपयोग नहीं। वे इस परिदृश्य को सूचीबद्ध करते हैं और एक उदाहरण देते हैं:

विधियों द्वारा पहुंच की जाँच इस तरह से नहीं की जाती है, इसलिए:

class Z {
    static int peek() { return j; }
    static int i = peek();
    static int j = 1;
}
class Test {
    public static void main(String[] args) {
        System.out.println(Z.i);
    }
}

उत्पादन का उत्पादन:

0

क्योंकि i लिए वैरिएबल इनिशियलाइज़र j से पहले वैरिएबल j के वैरिएबल को एक्सेस करने के लिए क्लास मेथड पीचिंग का उपयोग करता है, इसे इसके वैरिएबल इनिशियलाइज़र द्वारा इनिशियलाइज़ किया गया है, जिस बिंदु पर इसका डिफॉल्ट मान अभी भी है ( §4.12.5 )।


कक्षा स्तर के सदस्यों को वर्ग परिभाषा के भीतर कोड में आरंभीकृत किया जा सकता है। संकलित बायोटेक कक्षा के सदस्यों को इनलाइन को इनिशियलाइज़ नहीं कर सकता है। (उदाहरण के सदस्यों को इसी तरह से नियंत्रित किया जाता है, लेकिन यह प्रदान किए गए प्रश्न के लिए प्रासंगिक नहीं है।)

जब कोई निम्नलिखित जैसा कुछ लिखता है:

public class Demo1 {
    private static final long DemoLong1 = 1000;
}

उत्पन्न बाइटकोड निम्नलिखित के समान होगा:

public class Demo2 {
    private static final long DemoLong2;

    static {
        DemoLong2 = 1000;
    }
}

आरंभीकरण कोड एक स्थिर इनिशियलाइज़र के भीतर रखा जाता है जो तब चलता है जब क्लास लोडर पहली बार क्लास को लोड करता है। इस ज्ञान के साथ, आपका मूल नमूना निम्नलिखित के समान होगा:

public class RecursiveStatic {
    private static final long X;

    private static long scale(long value) {
        return X * value;
    }

    static {
        X = scale(10);
    }

    public static void main(String[] args) {
        System.out.println(scale(5));
    }
}
  1. JVM जार के प्रवेश बिंदु के रूप में RecursiveStatic को लोड करता है।
  2. वर्ग परिभाषा लोड होने पर क्लास लोडर स्थिर इनिशियलाइज़र चलाता है।
  3. static final क्षेत्र X को असाइन करने के लिए इनिशलाइज़र फ़ंक्शन scale(10) को कॉल करता है।
  4. scale(long) फ़ंक्शन चलता है जबकि वर्ग आंशिक रूप से X के अनइंस्टाल्यूटेड वैल्यू को पढ़ने के लिए आरम्भिक है जो कि लंबे या 0 का डिफ़ॉल्ट है।
  5. 0 * 10 का मान X को दिया जाता है और क्लास लोडर पूरा होता है।
  6. JVM सार्वजनिक स्टेटिक शून्य मेन मेथड कॉलिंग scale(5) चलाता है जो 0 रिटर्निंग 0 के अब इनिशियलाइज्ड X मान से 5 गुणा करता है।

स्थिर अंतिम फ़ील्ड X को केवल एक बार असाइन किया जाता है, final कीवर्ड द्वारा रखी गई गारंटी को संरक्षित करता है। असाइनमेंट में 3 जोड़ने की बाद की क्वेरी के लिए, चरण 5 से ऊपर 0 * 10 + 3 का मूल्यांकन हो जाता है जो मान 3 और मुख्य विधि 3 * 5 के परिणाम को प्रिंट करेगी जो कि मूल्य 15


यह एक बग बिल्कुल नहीं है, बस इसे आगे के संदर्भों का अवैध रूप नहीं है, इससे ज्यादा कुछ नहीं।

String x = y;
String y = "a"; // this will not compile 


String x = getIt(); // this will compile, but will be null
String y = "a";

public String getIt(){
    return y;
}

यह केवल विनिर्देश द्वारा अनुमत है।

अपना उदाहरण लेने के लिए, यह वही है जहाँ यह मेल खाता है:

private static final long X = scale(10) + 3;

आप उस scale एक आगे संदर्भ कर रहे हैं जो किसी भी तरह से अवैध नहीं है जैसा कि पहले कहा गया था, लेकिन आपको X का डिफ़ॉल्ट मान प्राप्त करने की अनुमति देता है। फिर से, इसे Spec द्वारा अनुमति दी जाती है (अधिक सटीक होने के लिए यह निषिद्ध नहीं है), इसलिए यह ठीक काम करता है


यहां फाइनल से कोई लेना देना नहीं है।

चूंकि यह उदाहरण या वर्ग स्तर पर है, इसलिए यह डिफ़ॉल्ट मान रखता है यदि अभी तक कुछ भी नहीं सौंपा गया है। यही कारण है कि आप 0 देख रहे हैं जब आप इसे बिना असाइन किए एक्सेस कर रहे हैं।

यदि आप X को पूरी तरह से असाइन किए बिना एक्सेस करते हैं, तो यह लंबे समय तक डिफ़ॉल्ट मान रखता है, जो कि 0 , इसलिए परिणाम।





static-initialization