Java8: java.lang.Object से किसी विधि के लिए डिफ़ॉल्ट विधि को परिभाषित करना क्यों वर्जित है


130

डिफ़ॉल्ट तरीके हमारे जावा टूलबॉक्स में एक अच्छा नया उपकरण हैं। हालांकि, मैंने एक इंटरफ़ेस लिखने की कोशिश defaultकी जो toStringविधि के एक संस्करण को परिभाषित करता है । जावा मुझे बताता है कि यह निषिद्ध है, क्योंकि घोषित तरीके एड java.lang.Objectनहीं हो सकते हैं default। यह एक केस क्यों है?

मुझे पता है कि "बेस क्लास हमेशा जीतता है" नियम है, इसलिए डिफ़ॉल्ट रूप से (दंड?), किसी भी तरीके से किसी भी तरीके से defaultलागू Objectकिया जाएगा Object। हालाँकि, मुझे इस बात का कोई कारण नहीं दिख रहा है कि ऐनक से तरीकों का अपवाद क्यों नहीं होना चाहिए Object। विशेष रूप से toStringइसके लिए डिफ़ॉल्ट कार्यान्वयन के लिए बहुत उपयोगी हो सकता है।

तो, क्या कारण है कि जावा डिजाइनरों ने defaultतरीकों को ओवरराइड करने के तरीकों की अनुमति नहीं देने का फैसला किया है Object?


1
मैं अब अपने बारे में बहुत अच्छा महसूस कर रहा हूं, इसे 100 गुना बढ़ा दिया है और इस तरह से यह एक गोल्ड बैज है। अच्छा प्रश्न!
यूजीन

जवाबों:


186

यह अभी तक उन भाषा डिजाइन मुद्दों में से एक है जो "स्पष्ट रूप से एक अच्छा विचार" लगता है जब तक आप खुदाई शुरू नहीं करते हैं और आपको पता चलता है कि वास्तव में यह एक बुरा विचार है।

इस मेल में इस विषय पर बहुत कुछ है (और अन्य विषयों पर भी।) कई डिजाइन बल थे जो हमें वर्तमान डिजाइनों में लाने के लिए अभिसरित थे:

  • विरासत मॉडल को सरल रखने की इच्छा;
  • तथ्य यह है कि एक बार जब आप स्पष्ट उदाहरणों को देखते हैं (उदाहरण के लिए, AbstractListएक इंटरफ़ेस में बदलना ), तो आपको एहसास होता है कि विरासत में मिला समान / हैशकोड /String दृढ़ता से एकल विरासत और स्थिति से जुड़ा हुआ है, और इंटरफेस बहुतायत से विरासत में मिले हैं और स्टेटलेस हैं;
  • कि इसने संभावित रूप से कुछ आश्चर्यजनक व्यवहारों के लिए दरवाजा खोल दिया।

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

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

AbstractListउदाहरण को बाहर निकालना आसान है ; यह प्यारा होगा यदि हम छुटकारा पा सकते हैं AbstractListऔर Listइंटरफ़ेस में व्यवहार डाल सकते हैं । लेकिन एक बार जब आप इस स्पष्ट उदाहरण से आगे बढ़ जाते हैं, तो कई अन्य अच्छे उदाहरण नहीं मिलते हैं। मूल रूप से, AbstractListएकल वंशानुक्रम के लिए डिज़ाइन किया गया है। लेकिन इंटरफेस को कई विरासत के लिए डिज़ाइन किया जाना चाहिए।

इसके अलावा, कल्पना कीजिए कि आप इस वर्ग को लिख रहे हैं:

class Foo implements com.libraryA.Bar, com.libraryB.Moo { 
    // Implementation of Foo, that does NOT override equals
}

FooSupertypes पर लेखक दिखता है, बराबरी का कोई कार्यान्वयन देखता है, और है कि संदर्भ समानता प्राप्त करने के लिए निष्कर्ष निकाला है, सभी वह जरूरत से विरासत बराबर है Object। फिर, अगले सप्ताह, बार "मददगार" के लिए पुस्तकालय अनुरक्षक एक डिफ़ॉल्ट equalsकार्यान्वयन जोड़ता है । ओह! अब Fooएक अन्य रखरखाव डोमेन "सहायक रूप से" एक सामान्य विधि के लिए एक डिफ़ॉल्ट जोड़कर एक इंटरफ़ेस द्वारा शब्दार्थ को तोड़ दिया गया है।

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

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


13
मुझे खुशी है कि आपने इसे समझाने के लिए समय लिया और मैं उन सभी कारकों की सराहना करता हूं, जिन पर विचार किया गया था। मैं मानता हूं कि यह खतरनाक होगा hashCodeऔर equals, लेकिन मुझे लगता है कि यह बहुत उपयोगी होगा toString। उदाहरण के लिए, कुछ Displayableइंटरफ़ेस एक String display()पद्धति को परिभाषित कर सकते हैं , और यह एक आधारभूत वर्ग को लागू करने या विस्तारित करने के लिए हर एक की आवश्यकता के बजाय बॉयलरप्लेट की एक टन को परिभाषित करने default String toString() { return display(); }में सक्षम होगा । DisplayableDisplayabletoString()DisplayableToString
ब्रैंडन

8
@ ब्रैंडन आप सही हैं कि इनहेरिटिंग () को इनहेरिट करने की अनुमति देना उसी तरह खतरनाक नहीं होगा , जैसे कि यह बराबर () और हैशकोड () के लिए होगा। दूसरी ओर, अब यह सुविधा और भी अधिक अनियमित होगी - और आप अभी भी विरासत नियमों के बारे में सभी एक ही अतिरिक्त जटिलता को उकसाएंगे, इस एक विधि के लिए ... लाइन को साफ-सुथरा बनाने के लिए बेहतर लगता है जहां हमने किया था ।
ब्रायन गोएटज

5
@gexicide यदि toString()केवल इंटरफ़ेस विधियों पर आधारित है, तो आप बस default String toStringImpl()इंटरफ़ेस की तरह कुछ जोड़ सकते हैं , और toString()प्रत्येक उपवर्ग में ओवरराइड को इंटरफ़ेस कार्यान्वयन कह सकते हैं - थोड़ा बदसूरत, लेकिन काम करता है, और कुछ भी नहीं से बेहतर है। :) ऐसा करने का एक और तरीका है Objects.hash(), कुछ ऐसा बनाना , Arrays.deepEquals()और Arrays.deepToString()। @ ब्रायनगेटज़ के जवाब के लिए 1 +1 करें!
सियु चिंग पोंग -आसुका केंजी- 7

3
एक लैम्ब्डा के toString () का डिफ़ॉल्ट व्यवहार वास्तव में अप्रिय है। मुझे पता है कि लैंबडा फैक्ट्री को बहुत ही सरल और तेज बनाया गया है, लेकिन वास्तव में उपयोगी नहीं है। default toString()फंक्शनल इंटरफेस में ओवरराइड होने से हमें कुछ भी करने की इजाजत नहीं मिलेगी - कम से कम कुछ ऐसा करें कि फंक्शन के सिग्नेचर और इंप्लीमेंटर के पैरेंट क्लास को थूक दें। इससे भी बेहतर, अगर हम कुछ पुनरावर्ती प्रधान रणनीतियों को सहन करने के लिए ला सकते हैं जो हम लंबोदर का वास्तव में अच्छा विवरण प्राप्त करने के लिए क्लोजर के माध्यम से चल सकते हैं, और इस प्रकार लंबोदर सीखने की अवस्था में काफी सुधार कर सकते हैं।
ग्रोस्तव २४'१५

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

30

यह तरीकों के लिए इंटरफेस में डिफ़ॉल्ट तरीकों को परिभाषित करने के लिए मना किया जाता है java.lang.Object, क्योंकि डिफ़ॉल्ट तरीके कभी भी "पहुंच योग्य" नहीं होंगे।

इंटरफ़ेस को लागू करने वाली कक्षाओं में डिफ़ॉल्ट इंटरफ़ेस विधियों को अधिलेखित किया जा सकता है और विधि के वर्ग कार्यान्वयन में इंटरफ़ेस कार्यान्वयन की तुलना में एक उच्च मिसाल है, भले ही विधि एक सुपरक्लास में लागू हो। चूंकि सभी कक्षाएं विरासत में मिली हैं java.lang.Object, इसलिए इन तरीकों में java.lang.Objectइंटरफ़ेस में डिफ़ॉल्ट विधि पर पूर्वता होगी और इसके बजाय इनवॉइस किया जाएगा।

Oracle से ब्रायन Goetz इस मेलिंग सूची पोस्ट में डिजाइन निर्णय पर कुछ और विवरण प्रदान करता है ।


3

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

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

इनमें से कोई भी वस्तु केString और अन्य तरीकों पर लागू नहीं होता है। सीधे शब्दों में कहें तो डिफ़ॉल्ट तरीके को डिफ़ॉल्ट व्यवहार प्रदान करने के लिए डिज़ाइन किया गया था जहां कोई अन्य परिभाषा नहीं है। अन्य मौजूदा कार्यान्वयन के साथ "प्रतिस्पर्धा" करने वाले कार्यान्वयन प्रदान करने के लिए नहीं।

"बेस क्लास हमेशा जीतता है" नियम के अपने ठोस कारण भी हैं। यह माना जाता है कि कक्षाएं वास्तविक कार्यान्वयन को परिभाषित करती हैं , जबकि इंटरफेस डिफ़ॉल्ट कार्यान्वयन को परिभाषित करते हैं, जो कुछ हद तक कमजोर हैं।

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

सभी और सभी, आपके द्वारा प्रस्तावित समाधान संभवतः पेशेवरों की तुलना में अधिक विपक्ष लाएगा।


मुझे मेरा पोस्ट करते समय gexicide के जवाब के दूसरे पैराग्राफ पर ध्यान नहीं दिया। इसमें एक लिंक है जो समस्या को और अधिक विस्तार से बताता है।
मर्विन

1

तर्क बहुत सरल है, ऐसा इसलिए है क्योंकि ऑब्जेक्ट सभी जावा कक्षाओं के लिए आधार वर्ग है। इसलिए यहां तक ​​कि अगर हमारे पास ऑब्जेक्ट का तरीका कुछ इंटरफ़ेस में डिफ़ॉल्ट विधि के रूप में परिभाषित किया गया है, तो यह बेकार हो जाएगा क्योंकि ऑब्जेक्ट की विधि हमेशा उपयोग की जाएगी। यही कारण है कि भ्रम से बचने के लिए, हमारे पास डिफ़ॉल्ट तरीके नहीं हो सकते हैं जो ऑब्जेक्ट क्लास के तरीकों को ओवरराइड कर रहे हैं।


1

बहुत ही पांडित्यपूर्ण उत्तर देने के लिए, defaultकिसी सार्वजनिक विधि के लिए एक विधि को परिभाषित करना केवल निषिद्ध है java.lang.Object। विचार करने के लिए 11 तरीके हैं, जिन्हें इस प्रश्न का उत्तर देने के लिए तीन तरीकों से वर्गीकृत किया जा सकता है।

  1. के छह Objectपद्धतियां नहीं हो सकतीं defaultतरीकों क्योंकि वे कर रहे हैं finalऔर सभी पर ओवरराइड नहीं किया जा सकता है: getClass(), notify(), notifyAll(), wait(), wait(long), और wait(long, int)
  2. के तीन Objectतरीकों हो सकता है नहीं defaultब्रायन गोएज़ द्वारा ऊपर दिए गए कारणों के लिए तरीके: equals(Object), hashCode(), और toString()
  3. Objectविधियों में से दो में विधियां हो सकती हैं default, हालांकि इस तरह के चूक के मूल्य सबसे अच्छे हैं: clone()और finalize()

    public class Main {
        public static void main(String... args) {
            new FOO().clone();
            new FOO().finalize();
        }
    
        interface ClonerFinalizer {
            default Object clone() {System.out.println("default clone"); return this;}
            default void finalize() {System.out.println("default finalize");}
        }
    
        static class FOO implements ClonerFinalizer {
            @Override
            public Object clone() {
                return ClonerFinalizer.super.clone();
            }
            @Override
            public void finalize() {
                ClonerFinalizer.super.finalize();
            }
        }
    }

।क्या बात है? आपने अभी भी WHY के भाग का जवाब क्यों नहीं दिया - "तो, क्या कारण है कि जावा डिजाइनरों ने ऑब्जेक्ट के तरीकों को ओवरराइड करने के लिए डिफ़ॉल्ट तरीकों की अनुमति नहीं देने का फैसला किया है?"
2_17
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.