जब एक डिफ़ॉल्ट विधि के साथ एक इंटरफ़ेस आरम्भ किया जाता है?


94

इस प्रश्न का उत्तर देने के लिए जावा लैंग्वेज स्पेसिफिकेशन के माध्यम से खोज करते हुए , मैंने सीखा कि

किसी कक्षा को आरंभीकृत करने से पहले, उसके प्रत्यक्ष सुपरक्लास को आरंभीकृत किया जाना चाहिए, लेकिन कक्षा द्वारा लागू किए गए इंटरफेस को प्रारंभ नहीं किया जाता है। इसी तरह, इंटरफेस शुरू होने से पहले एक इंटरफेस के सुपरइन्फॉर्स को इनिशियलाइज़ नहीं किया जाता है।

मेरी अपनी जिज्ञासा के लिए, मैंने इसकी कोशिश की और, जैसा कि अपेक्षित था, इंटरफ़ेस InterfaceTypeको आरंभीकृत नहीं किया गया था।

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        foo.method();
    }
}

class InterfaceTypeImpl implements InterfaceType {
    @Override
    public void method() {
        System.out.println("implemented method");
    }
}

class ClassInitializer {
    static {
        System.out.println("static initializer");
    }
}

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public void method();
}

यह कार्यक्रम प्रिंट करता है

implemented method

हालाँकि, यदि इंटरफ़ेस एक defaultविधि की घोषणा करता है , तो प्रारंभ होता है। InterfaceTypeदिए गए इंटरफ़ेस पर विचार करें

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public default void method() {
        System.out.println("default method");
    }
}

इसके बाद ऊपर का वही कार्यक्रम प्रिंट होगा

static initializer  
implemented method

दूसरे शब्दों में, staticइंटरफ़ेस के क्षेत्र को इनिशियलाइज़ किया गया है ( विस्तृत इनिशियलाइज़ेशन प्रक्रिया में चरण 9 ) और staticइनिशियलाइज़ किए जा रहे टाइप के इनिशियलाइज़र को निष्पादित किया जाता है। इसका मतलब है कि इंटरफ़ेस को इनिशियलाइज़ किया गया था।

मुझे यह बताने के लिए जेएलएस में कुछ भी नहीं मिला कि ऐसा होना चाहिए। मुझे गलत मत समझो, मैं समझता हूं कि ऐसा तब होना चाहिए जब कार्यान्वयन वर्ग विधि के लिए कार्यान्वयन प्रदान नहीं करता है, लेकिन अगर यह होता है तो क्या होगा? क्या यह स्थिति जावा लैंग्वेज स्पेसिफिकेशन से गायब है, क्या मुझे कुछ याद आया, या क्या मैं इसे गलत तरीके से व्याख्या कर रहा हूं?


4
मेरा अनुमान होगा - इस तरह के इंटरफेस को प्रारंभिक क्रम के संदर्भ में अमूर्त वर्ग माना जाता है। मैंने इसे एक टिप्पणी के रूप में लिखा है क्योंकि मुझे यकीन नहीं है कि यह सही कथन है :)
एलेक्सी मालेव

यह जेएलएस की धारा 12.4 में होना चाहिए, लेकिन ऐसा प्रतीत नहीं होता है। मैं कहूंगा कि यह गायब है।
वारेन ड्यू

1
कोई बात नहीं .... ज्यादातर समय जब वे समझ नहीं पाते हैं या उनके पास कोई स्पष्टीकरण नहीं होता है, तो वे डाउनवोट हो जाएंगे :( यह आमतौर पर एसओ पर होता है।
NeverGiveUp161

मैंने सोचा कि interfaceजावा में किसी भी ठोस विधि को परिभाषित नहीं करना चाहिए। इसलिए मुझे आश्चर्य है कि InterfaceTypeकोड संकलित किया गया है।
मैक्सजूम

जवाबों:


85

यह एक बहुत ही दिलचस्प मुद्दा है!

ऐसा लगता है कि जेएलएस खंड 12.4.1 को निश्चित रूप से कवर करना चाहिए। हालाँकि, Oracle JDK और OpenJDK (javac और HotSpot) का व्यवहार यहाँ निर्दिष्ट की गई बातों से भिन्न है। विशेष रूप से, इस खंड से उदाहरण 12.4.1-3 इंटरफ़ेस प्रारंभ को कवर करता है। उदाहरण इस प्रकार है:

interface I {
    int i = 1, ii = Test.out("ii", 2);
}
interface J extends I {
    int j = Test.out("j", 3), jj = Test.out("jj", 4);
}
interface K extends J {
    int k = Test.out("k", 5);
}
class Test {
    public static void main(String[] args) {
        System.out.println(J.i);
        System.out.println(K.j);
    }
    static int out(String s, int i) {
        System.out.println(s + "=" + i);
        return i;
    }
}

इसका अपेक्षित उत्पादन है:

1
j=3
jj=4
3

और वास्तव में मुझे अपेक्षित आउटपुट मिलता है। हालाँकि, यदि कोई डिफ़ॉल्ट विधि इंटरफ़ेस में जोड़ी जाती है I,

interface I {
    int i = 1, ii = Test.out("ii", 2);
    default void method() { } // causes initialization!
}

आउटपुट में परिवर्तन होता है:

1
ii=2
j=3
jj=4
3

जो स्पष्ट रूप से इंगित करता है कि इंटरफ़ेस Iको प्रारंभ किया जा रहा है जहां यह पहले नहीं था! डिफ़ॉल्ट विधि की मात्र उपस्थिति आरंभीकरण को गति देने के लिए पर्याप्त है। डिफ़ॉल्ट विधि को कॉल या ओवरराइड या यहां तक ​​कि उल्लेख नहीं करना पड़ता है, और न ही एक अमूर्त विधि की उपस्थिति शुरू होती है।

मेरा अनुमान है कि हॉटस्पॉट कार्यान्वयन invokevirtualकॉल के महत्वपूर्ण पथ में वर्ग / इंटरफ़ेस इनिशियलाइज़ेशन जाँच को जोड़ने से बचना चाहता था । जावा 8 और डिफ़ॉल्ट विधियों से पहले, invokevirtualइंटरफ़ेस में कोड निष्पादित करना कभी समाप्त नहीं कर सकता था, इसलिए यह उत्पन्न नहीं हुआ। कोई सोच सकता है कि यह वर्ग / इंटरफ़ेस तैयारी चरण ( JLS 12.3.2 ) का हिस्सा है जो विधि तालिकाओं जैसी चीजों को आरंभ करता है। लेकिन शायद यह बहुत दूर चला गया और गलती से इसके बजाय पूर्ण रूप से आरंभीकरण हुआ।

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

प्रकटीकरण : मैं OpenJDK पर ओरेकल के लिए काम करता हूं। अगर लोगों को लगता है कि यह मुझे इस सवाल से जुड़े होने का अनुचित लाभ देता है, तो मैं इसके बारे में लचीला होना चाहता हूं।


6
मैंने आधिकारिक स्रोतों के लिए कहा। मुझे नहीं लगता कि इससे अधिक आधिकारिक हो जाता है। सारे घटनाक्रम को देखने के लिए इसे दो दिन का समय दें।
सोतीरियोस डेलिमोलिस

48
@StuartMarks " अगर लोगों को लगता है कि यह मुझे एक अनुचित लाभ देता है आदि " => हम यहां सवालों के जवाब पाने के लिए हैं और यह एक सही जवाब है!
assylias

2
एक साइड नोट: JVM स्पेक में एक विवरण होता है जो JLS से मिलता-जुलता है : docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5 इसे भी अपडेट किया जाना चाहिए ।
मार्को 13

2
@assylias और Sotirios, आपकी टिप्पणी के लिए धन्यवाद। उन्होंने एसिलियास की टिप्पणी पर 14 अपवाहों (इस लेखन के रूप में) के साथ, मेरी चिंताओं को किसी भी अनुचित अनुचित के बारे में बताया है।
स्टुअर्ट मार्क्स

1
@SotiriosDelimanolis कुछ जोड़े हैं जो प्रासंगिक लगते हैं, JDK-8043275 और JDK-8043190 , और वे 8u40 में तय किए गए हैं। हालाँकि, व्यवहार समान प्रतीत होता है। इसके साथ कुछ JVM स्पेक बदलाव भी हुए थे, इसलिए शायद यह फिक्स "पुराने इनिशियलाइज़ेशन ऑर्डर को बहाल करने" के अलावा कुछ और है।
स्टुअर्ट मार्क्स

13

इंटरफ़ेस को इनिशियलाइज़ नहीं किया जाता है क्योंकि निरंतर फ़ील्ड InterfaceType.init, जिसे नॉन कॉस्ट वैल्यू (विधि कॉल) द्वारा इनिशियलाइज़ किया जा रहा है, कहीं भी उपयोग नहीं किया जाता है।

यह संकलन के समय में जाना जाता है कि इंटरफ़ेस का निरंतर क्षेत्र कहीं भी उपयोग नहीं किया जाता है, और इंटरफ़ेस में कोई डिफ़ॉल्ट विधि नहीं है (जावा -8 में) इसलिए इंटरफ़ेस को प्रारंभ या लोड करने की कोई आवश्यकता नहीं है।

इंटरफ़ेस को निम्नलिखित मामलों में आरंभीकृत किया जाएगा,

  • आपके कोड में निरंतर फ़ील्ड का उपयोग किया जाता है।
  • इंटरफ़ेस में एक डिफ़ॉल्ट विधि है (Java 8)

डिफ़ॉल्ट विधियों के मामले में , आप लागू कर रहे हैं InterfaceType। इसलिए, यदि InterfaceTypeकोई भी डिफ़ॉल्ट विधियां होंगी, तो यह लागू करने वाली कक्षा में इंप्रैक्टिकल (प्रयुक्त) होगी । और प्रारंभिक चित्र में होगा।

लेकिन, यदि आप इंटरफ़ेस के निरंतर क्षेत्र तक पहुंच रहे हैं (जो कि सामान्य तरीके से आरंभ किया जाता है), इंटरफ़ेस प्रारंभ की आवश्यकता नहीं है।

निम्नलिखित कोड पर विचार करें।

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        System.out.println(InterfaceType.init);
        foo.method();
    }
}

class InterfaceTypeImpl implements InterfaceType {
    @Override
    public void method() {
        System.out.println("implemented method");
    }
}

class ClassInitializer {
    static {
        System.out.println("static initializer");
    }
}

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public void method();
}

उपरोक्त मामले में, इंटरफ़ेस को प्रारंभ और लोड किया जाएगा क्योंकि आप फ़ील्ड का उपयोग कर रहे हैं InterfaceType.init

मैं डिफ़ॉल्ट विधि का उदाहरण नहीं दे रहा हूं जैसा कि आपने पहले ही अपने प्रश्न में दिया था।

जावा भाषा विनिर्देश और उदाहरण JLS 12.4.1 में दिया गया है (उदाहरण में डिफ़ॉल्ट विधियाँ नहीं हैं।)


मुझे डिफ़ॉल्ट तरीकों के लिए जेएलएस नहीं मिल रहा है, दो संभावनाएं हो सकती हैं

  • जावा लोग डिफ़ॉल्ट विधि के मामले पर विचार करना भूल गए। (विशिष्टता डॉक बग।)
  • वे केवल डिफ़ॉल्ट विधियों को इंटरफ़ेस के गैर-स्थिर सदस्य के रूप में संदर्भित करते हैं। (लेकिन उल्लेख नहीं किया गया है, फिर से विनिर्देशन बग बग।)

मैं डिफ़ॉल्ट विधि के लिए संदर्भ ढूंढ रहा हूं। फ़ील्ड केवल यह प्रदर्शित करने के लिए थी कि इंटरफ़ेस आरंभिक था या नहीं।
सोतीरियो डेलिमोलिसिन

@SotiriosDelimanolis मैंने डिफ़ॉल्ट विधि के उत्तर में कारण का उल्लेख किया है ... लेकिन दुर्भाग्य से कोई भी JLS डिफ़ॉल्ट विधि के लिए अभी तक नहीं मिला है।
बग बग नहीं

दुर्भाग्य से, यही मैं ढूंढ रहा हूं। मुझे ऐसा लगता है कि आपका उत्तर सिर्फ उन चीजों को दोहरा रहा है जो मैंने पहले ही प्रश्न में बताई हैं। यदि किसी इंटरफ़ेस में इनिशियलाइज़ किया जाएगा, तो उसमें एक defaultविधि और एक वर्ग होता है जो इंटरफ़ेस इम्प्लीमेंट करता है।
सोतीरियोस डेलिमोलिस

मुझे लगता है कि जावा लोग डिफ़ॉल्ट विधि के मामले पर विचार करना भूल गए, या वे केवल डिफ़ॉल्ट तरीकों को इंटरफ़ेस के गैर-स्थिर सदस्य के रूप में संदर्भित करते हैं (मेरी धारणा, किसी भी डॉक्टर में नहीं मिल सकती)।
बग

1
@ किशनसरसेखजजर: इंटरफ़ेस में गैर स्थिर क्षेत्र से आपका क्या तात्पर्य है? इंटरफ़ेस में कोई भी चर / क्षेत्र डिफ़ॉल्ट रूप से स्थिर अंतिम है।
लोकेश

10

InstanceKlass.cpp OpenJDK से फ़ाइल प्रारंभ विधि शामिल InstanceKlass::initialize_implहै कि से मेल खाती विस्तृत प्रारंभ प्रक्रिया JLS में है, जो तुलनात्मक रूप से पाया जाता है प्रारंभ JVM युक्ति में अनुभाग।

इसमें एक नया चरण शामिल है जो JLS में उल्लिखित नहीं है और कोड में संदर्भित JVM पुस्तक में नहीं है:

// refer to the JVM book page 47 for description of steps
...

if (this_oop->has_default_methods()) {
  // Step 7.5: initialize any interfaces which have default methods
  for (int i = 0; i < this_oop->local_interfaces()->length(); ++i) {
    Klass* iface = this_oop->local_interfaces()->at(i);
    InstanceKlass* ik = InstanceKlass::cast(iface);
    if (ik->has_default_methods() && ik->should_be_initialized()) {
      ik->initialize(THREAD);
    ....
    }
  }
}

इसलिए इस इनिशियलाइज़ेशन को स्पष्ट रूप से एक नए चरण 7.5 के रूप में लागू किया गया है । यह इंगित करता है कि इस कार्यान्वयन ने कुछ विनिर्देशन का पालन किया, लेकिन ऐसा लगता है कि वेबसाइट पर लिखित विनिर्देश तदनुसार अद्यतन नहीं किए गए हैं।

EDIT: एक संदर्भ के रूप में, प्रतिबद्ध (अक्टूबर 2012 से!) जहां संबंधित कदम को कार्यान्वयन में शामिल किया गया है: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/473552c84362

EDIT2: संयोगवश, मुझे यह दस्तावेज़ हॉटस्पॉट में डिफ़ॉल्ट तरीकों के बारे में मिला जिसमें अंत में एक दिलचस्प पक्ष नोट है:

3.7 विविध

क्योंकि इंटरफेस में अब बाईटकोड है, हमें उस समय उन्हें इनिशियलाइज़ करना होगा कि एक इम्प्लीमेंटिंग क्लास इनिशियलाइज़ हो।


1
इसे खोदने के लिए धन्यवाद। (+1) यह हो सकता है कि नया "चरण 7.5" अनजाने में कल्पना से हटा दिया गया था, या इसे प्रस्तावित और अस्वीकार कर दिया गया था और इसे हटाने के लिए कार्यान्वयन कभी भी तय नहीं किया गया था।
स्टुअर्ट मार्क्स

1

मैं एक ऐसा मामला बनाने की कोशिश करूँगा कि इंटरफ़ेस इनिशियलाइज़ेशन में किसी भी साइड-चैनल साइड इफेक्ट्स का कारण नहीं होना चाहिए जो उपप्रकार पर निर्भर करता है, इसलिए, चाहे यह बग हो या न हो, या जिस तरह से जावा इसे ठीक करता है, उसे कोई फर्क नहीं पड़ता। जिस क्रम में इंटरफेस को इनिशियलाइज़ किया जाता है।

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

class Foo{
    static{
        Bank.deposit($1000);
...

किसी भी उपवर्ग की Fooअपेक्षा होगी कि वे उप-कोड कोड में कहीं भी, बैंक में $ 1000 देखेंगे। इसलिए सुपरक्लास को उपवर्ग से पहले आरंभीकृत किया जाता है।

क्या हमें सुपरिन्टेफेस के लिए भी ऐसा ही नहीं करना चाहिए? दुर्भाग्य से, सुपरन्टेफस का क्रम महत्वपूर्ण नहीं माना जाता है, इसलिए उन्हें परिभाषित करने के लिए कोई अच्छी तरह से परिभाषित आदेश नहीं है।

इसलिए हम बेहतर है कि इंटरफेस इनिशियलाइज़ेशन में इस तरह के साइड इफेक्ट्स को स्थापित न करें। आखिरकार, interfaceइन सुविधाओं (स्थिर क्षेत्रों / विधियों) के लिए नहीं है जिन्हें हम सुविधा के लिए ढेर करते हैं।

इसलिए यदि हम उस सिद्धांत का पालन करते हैं, तो यह हमारे लिए कोई चिंता का विषय नहीं होगा कि किस क्रम में इंटरफेस को आरंभीकृत किया गया है।

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