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


186

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

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चर अपने चर को कैसे बदलता है?


17
Xसदस्य को संदर्भित करने का यह तरीका सुपर क्लास कंस्ट्रक्टर के समाप्त होने से पहले एक उपवर्ग के सदस्य को संदर्भित करने जैसा है, यह आपकी समस्या है और इसकी परिभाषा नहीं है final
दानीऊ

4
JLS से:A blank final instance variable must be definitely assigned (§16.9) at the end of every constructor (§8.8) of the class in which it is declared; otherwise a compile-time error occurs.
इवान

1
@ इवान, यह निरंतर के बारे में नहीं है लेकिन उदाहरण चर के बारे में है। लेकिन क्या आप अध्याय जोड़ सकते हैं?
एक्सलह

9
बस एक नोट के रूप में: उत्पादन कोड में कभी भी ऐसा न करें। यह हर किसी के लिए सुपर भ्रामक है अगर कोई व्यक्ति JLS में खामियों का फायदा उठाने लगे।
जाबुज़ार्ड

13
FYI करें आप C # में भी यही स्थिति बना सकते हैं। C # वादे जो लगातार घोषणाओं में रहते हैं, संकलन समय पर पकड़े जाएंगे, लेकिन पठनीय घोषणाओं के बारे में ऐसे कोई वादे नहीं करते हैं , और व्यवहार में आप उन स्थितियों में पहुंच सकते हैं जहां क्षेत्र का प्रारंभिक शून्य मान किसी अन्य फ़ील्ड इनिशलाइज़र द्वारा देखा जाता है। यदि ऐसा करने पर दर्द होता है, तो ऐसा न करें । संकलक आपको नहीं बचाएगा।
एरिक लिपर्ट

जवाबों:


217

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

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


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

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

private static Object x;

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

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

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

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

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

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

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

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


अंतिम चर

अब हम at4.12.4final से एक नज़र डालते हैं :

अंतिम चर

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


व्याख्या

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

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

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

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

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

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

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

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

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

  • संदर्भ एक असाइनमेंट अभिव्यक्ति के बाएं हाथ की ओर नहीं है ( the15.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 ) है।


1
@ और हां, वर्ग चर, धन्यवाद। हाँ, यह काम करेगा अगर कुछ अतिरिक्त नियम नहीं होंगे जो इस तरह की पहुंच को प्रतिबंधित करते हैं: .38.3.3कक्षा चर (पहली प्रविष्टि) के लिए निर्दिष्ट चार बिंदुओं पर एक नज़र डालें । ओपीएस उदाहरण में विधि दृष्टिकोण उन नियमों से नहीं पकड़ा जाता है, इसलिए हम Xविधि से पहुंच सकते हैं । मैं इतना बुरा नहीं मानूंगा। यह सिर्फ इस बात पर निर्भर करता है कि जेएलएस चीजों को विस्तार से काम करने के लिए कैसे परिभाषित करता है। मैं उस तरह के कोड का उपयोग कभी नहीं करूंगा, यह सिर्फ JLS में कुछ नियमों का शोषण कर रहा है।
ज़बुज़ार्ड

4
समस्या यह है कि आप कंस्ट्रक्टर से उदाहरण के तरीकों को कॉल कर सकते हैं, कुछ ऐसा जिसकी अनुमति नहीं होनी चाहिए। दूसरी ओर, सुपर को कॉल करने से पहले स्थानीय लोगों को असाइन करना, जो उपयोगी और सुरक्षित होगा, अस्वीकृत है। जाओ पता लगाओ।
मोनिका

1
@ और शायद आप यहां केवल एक ही हैं जो वास्तव में उल्लेख किया गया है forwards references(जो कि जेएलएस का भी हिस्सा हैं)। यह बिना इस सरल जवाब के बहुत सरल है। stackoverflow.com/a/49371279/1059372
यूजीन

1
"पहले असाइनमेंट फिर संदर्भ बदलता है।" इस मामले में यह एक संदर्भ प्रकार नहीं है, बल्कि एक आदिम प्रकार है।
फाबियन

1
यह उत्तर सही है, अगर थोड़ा लंबा है। :-) मुझे लगता है कि tl; dr यह है कि OP ने एक ट्यूटोरियल का हवाला देते हुए कहा कि "[एक अंतिम] फ़ील्ड नहीं बदल सकता है," JLS नहीं। जबकि ओरेकल के ट्यूटोरियल काफी अच्छे हैं, वे सभी एज मामलों को कवर नहीं करते हैं। ओपी के प्रश्न के लिए, हमें अंतिम की वास्तविक, जेएलएस परिभाषा पर जाने की आवश्यकता है - और यह परिभाषा यह दावा नहीं करती है (कि ओपी सही रूप से चुनौती देता है) कि अंतिम क्षेत्र का मान कभी नहीं बदल सकता है।
यशवित

22

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

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

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


3
इसके बारे में मुश्किल यह है कि यदि आप मान निर्दिष्ट नहीं करते हैं, तो यह डिफ़ॉल्ट मान के साथ असाइन नहीं किया जाएगा, लेकिन यदि आपने इसका उपयोग "अंतिम" मान प्रदान करने के लिए किया है, तो यह होगा ...
AxelH

2
@ AxelH मैं देख रहा हूं कि आपको इससे क्या मतलब है। लेकिन यह है कि यह कैसे काम करना चाहिए अन्यथा दुनिया पतन;)।
सुरेश अट्टा

20

बग नहीं।

जब पहली कॉल से कॉल किया scaleजाता है

private static final long X = scale(10);

यह मूल्यांकन करने की कोशिश करता है return X * valueXअभी तक कोई मान निर्दिष्ट नहीं किया गया है और इसलिए डिफ़ॉल्ट मान longका उपयोग किया जाता है (जो है 0)।

ताकि कोड की लाइन का मूल्यांकन X * 10यानी 0 * 10जो है 0


8
मुझे नहीं लगता कि ओपी भ्रमित हैं। क्या confuses है X = scale(10) + 3। चूंकि X, जब विधि से संदर्भित है, है 0। लेकिन बाद में यह है 3। इसलिए ओपी को लगता है कि Xइसे दो अलग-अलग मूल्य दिए गए हैं, जिनसे टकराव होगा final
ज़बुजार्ड

4
@Zabuza यहreturn X * valueXlong0 " यह मूल्यांकन करने की कोशिश करता है के साथ व्याख्या नहीं है अभी तक एक मूल्य नहीं सौंपा गया है और इसलिए जो एक के लिए डिफ़ॉल्ट मान लेता है " यह नहीं कहा Xगया है कि डिफ़ॉल्ट मान के साथ असाइन किया गया है, लेकिन Xयह "प्रतिस्थापित" है (कृपया उस शब्द को उद्धृत न करें?)) डिफ़ॉल्ट मान द्वारा।
एक्सलह

14

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

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 द्वारा अनुमति दी जाती है (अधिक सटीक होने के लिए यह निषिद्ध नहीं है), इसलिए यह ठीक काम करता है


अच्छा उत्तर! मैं सिर्फ इस बारे में उत्सुक हूं कि ऐनक दूसरे मामले को संकलित करने की अनुमति क्यों देता है। क्या यह अंतिम क्षेत्र की "असंगत" स्थिति को देखने का एकमात्र तरीका है?
एंड्रयू टोबिल्को

@ और इसने मुझे काफी समय तक परेशान किया है, मैं इसके सी ++ या सी के बारे में सोचने के लिए इच्छुक हूं (ऐसा नहीं है अगर यह सच है)
यूजीन

@ और: क्योंकि करने के लिए अन्यथा ट्यूरिंग अपूर्णता प्रमेय को हल करने के लिए किया जाएगा।
जोशुआ

9
@ जोशुआ: मुझे लगता है कि आप यहां विभिन्न अवधारणाओं को मिला रहे हैं: (1) हॉल्टिंग प्रॉब्लम, (2) डिसीजन प्रॉब्लम, (3) गोडेल का अधूरा प्रमेय, और (4) ट्यूरिंग-पूर्ण प्रोग्रामिंग लैंग्वेजेज। कंपाइलर लेखक समस्या को हल करने का प्रयास नहीं करते हैं "क्या यह चर निश्चित रूप से उपयोग किए जाने से पहले सौंपा गया है?" क्योंकि समस्या हल करने की समस्या के समतुल्य है, और हम जानते हैं कि हम ऐसा नहीं कर सकते।
एरिक लिपर्ट

4
@EricLippert: हाहा ऊप्स। अधूरापन और रुकने की समस्या मेरे दिमाग में एक ही जगह है।
जोशुआ

4

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

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

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. इनिशलाइज़र फंक्शन scale(10)को static finalफील्ड असाइन करने के लिए कहता है X
  4. scale(long)समारोह रन वर्ग आंशिक रूप से अप्रारंभीकृत मूल्य के पढ़ने आरंभ नहीं हो जाता है, जबकि Xलंबे समय तक या 0 के डिफ़ॉल्ट है।
  5. का मान 0 * 10असाइन किया गया है Xऔर वर्ग लोडर पूरा करता है।
  6. JVM सार्वजनिक स्थैतिक शून्य मुख्य विधि को कॉल करता है scale(5)जो 5 Xको 0 के प्रारंभिक मूल्य से 5 गुणा करता है ।

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


3

किसी संकलन के त्रुटि रहित परिणाम को पढ़ने के लिए एक वस्तु के अनधिकृत क्षेत्र को पढ़ना। दुर्भाग्य से जावा के लिए, यह नहीं है।

मुझे लगता है कि यह मूल कारण है कि यह मामला "छिपी" गहरी है कि परिभाषा में वस्तुओं का त्वरित और निर्माण कैसे किया जाता है, हालांकि मुझे मानक का विवरण नहीं पता है।

एक अर्थ में, अंतिम अ-परिभाषित है क्योंकि यह इस समस्या के कारण इसका घोषित उद्देश्य भी पूरा नहीं करता है। हालाँकि, यदि आपकी सभी कक्षाएं ठीक से लिखी गई हैं, तो आपको यह समस्या नहीं है। मतलब सभी फ़ील्ड हमेशा सभी कंस्ट्रक्टर्स में सेट की जाती हैं और कोई भी चीज़ अपने कंस्ट्रक्टर्स को बुलाए बिना कभी नहीं बनाई जाती है। यह स्वाभाविक है जब तक कि आपको क्रमबद्ध पुस्तकालय का उपयोग नहीं करना है।

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