अनाम वर्ग में केवल अंतिम चर क्यों सुलभ हैं?


354
  1. aकेवल यहां अंतिम हो सकता है। क्यों? मैं इसे निजी सदस्य के रूप में रखे बिना विधि aमें फिर से कैसे भरोसा कर सकता हूं onClick()?

    private void f(Button b, final int a){
        b.addClickHandler(new ClickHandler() {
    
            @Override
            public void onClick(ClickEvent event) {
                int b = a*5;
    
            }
        });
    }
    
  2. 5 * aजब यह क्लिक किया जाता है तो मैं कैसे वापस आ सकता हूं ? मेरा मतलब,

    private void f(Button b, final int a){
        b.addClickHandler(new ClickHandler() {
    
            @Override
            public void onClick(ClickEvent event) {
                 int b = a*5;
                 return b; // but return type is void 
            }
        });
    }

1
मुझे नहीं लगता कि जावा अनाम कक्षाएं उस प्रकार का लंबोदर क्लोजर प्रदान करती हैं जिसकी आप अपेक्षा करते हैं, लेकिन कोई व्यक्ति मुझे सही कर रहा है अगर मैं गलत हूं ...
user541686

4
आप क्या हासिल करने का प्रयास कर रहे हैं? "F" समाप्त होने पर क्लिक हैंडलर निष्पादित किया जा सकता है।
इवान डबरोव

@Lambert यदि आप ऑनक्लिक विधि में एक का उपयोग करना चाहते हैं तो यह अंतिम होना है @ इवान कैसे एफ () विधि क्लिक करने पर व्यवहार कर सकती है () विधि वापस लौटने पर क्लिक करें

2
यही मेरा मतलब है - यह पूर्ण बंद का समर्थन नहीं करता है क्योंकि यह गैर-अंतिम चर तक पहुंच की अनुमति नहीं देता है।
user541686

4
नोट: जावा 8 के रूप में, आपके चर को केवल प्रभावी रूप से अंतिम रूप
पीटर लॉरी

जवाबों:


489

जैसा कि टिप्पणियों में कहा गया है, यह कुछ जावा 8 में अप्रासंगिक हो जाता है, जहां finalनिहित हो सकता है। केवल एक प्रभावी रूप से अंतिम चर का उपयोग एक अनाम आंतरिक वर्ग या लैम्ब्डा अभिव्यक्ति में किया जा सकता है।


यह मूल रूप से जावा के बंद होने के तरीके के कारण है ।

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

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

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

यदि आप जावा और सी # क्लोजर के बीच अधिक विस्तृत तुलना में रुचि रखते हैं, तो मेरे पास एक लेख है जो इसमें और आगे जाता है। मैं इस जवाब में जावा पक्ष पर ध्यान केंद्रित करना चाहता था :)


4
@ इवान: लाइक सी #, मूल रूप से। यह जटिलता की एक उचित डिग्री के साथ आता है, यदि आप सी # के समान कार्यक्षमता चाहते हैं, जहां विभिन्न स्कोप से चर कई बार अलग-अलग संख्याओं में "त्वरित" हो सकते हैं।
जॉन स्कीट


11
जावा 7 के लिए यह सब सच था, ध्यान रखें कि जावा 8 के साथ, क्लोजर पेश किए गए हैं और अब एक वर्ग के गैर-अंतिम क्षेत्र को अपने आंतरिक वर्ग से एक्सेस करना वास्तव में संभव है।
मैथियास बदर

22
@ मैथियासबैडर: वाक़ई? मैंने सोचा कि यह अभी भी अनिवार्य रूप से एक ही तंत्र है, संकलक अब केवल अनुमान लगाने के लिए पर्याप्त चतुर है final(लेकिन यह अभी भी प्रभावी रूप से अंतिम होने की आवश्यकता है)।
थिलो

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

41

एक चाल है जो बाहरी दायरे में अनाम वर्ग को डेटा अपडेट करने की अनुमति देती है।

private void f(Button b, final int a) {
    final int[] res = new int[1];
    b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            res[0] = a * 5;
        }
    });

    // But at this point handler is most likely not executed yet!
    // How should we now res[0] is ready?
}

हालाँकि, सिंक्रनाइज़ेशन समस्याओं के कारण यह ट्रिक बहुत अच्छी नहीं है। यदि हैंडलर को बाद में मंगवाया जाता है, तो आपको 1) रेस को एक्सेस करने के लिए एक्सेस को सिंक्रोनाइज़ करना होगा यदि हैंडलर को अलग-अलग थ्रेड से मंगवाया गया हो 2) किसी तरह के फ्लैग या इंडिकेशन की आवश्यकता होती है जिसे रिस अपडेट किया गया था

यह ट्रिक ठीक काम करती है, हालांकि, अगर अनाम वर्ग को तुरंत उसी थ्रेड में लागू किया जाता है। पसंद:

// ...

final int[] res = new int[1];
Runnable r = new Runnable() { public void run() { res[0] = 123; } };
r.run();
System.out.println(res[0]);

// ...

2
आपके उत्तर के लिए धन्यवाद। मुझे यह सब पता है और मेरा समाधान इससे बेहतर है। मेरा सवाल है "केवल अंतिम क्यों"?

6
फिर इसका उत्तर यह है कि वे कैसे लागू होते हैं :)
इवान डबरोव

1
धन्यवाद। मैंने ऊपर की तरकीब का इस्तेमाल खुद ही किया था। मुझे यकीन नहीं था कि यह एक अच्छा विचार है। यदि जावा इसकी अनुमति नहीं देता है, तो एक अच्छा कारण हो सकता है। आपका उत्तर स्पष्ट करता है कि मेरा List.forEachकोड सुरक्षित है।
RuntimeException 10

"केवल अंतिम क्यों" के पीछे तर्क की अच्छी चर्चा के लिए stackoverflow.com/q/12830611/2073130 पढ़ें ।
lcn

कई वर्कअराउंड हैं। मेरा है: अंतिम int resf = res; प्रारंभ में मैंने ऐरे एप्रोच का उपयोग किया था, लेकिन मुझे लगता है कि इसमें बहुत अधिक बोझिल वाक्य रचना है। AtomicReference शायद थोड़ा धीमा है (एक वस्तु आवंटित करता है)।
जक्कम

17

एक अनाम वर्ग एक आंतरिक वर्ग है और सख्त नियम आंतरिक कक्षाओं पर लागू होता है (JLS 8.1.3) :

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

मुझे अभी तक jls या jvms पर कोई कारण या स्पष्टीकरण नहीं मिला है, लेकिन हम जानते हैं, कि संकलक प्रत्येक आंतरिक वर्ग के लिए एक अलग वर्ग फ़ाइल बनाता है और यह सुनिश्चित करना होता है, कि इस वर्ग फ़ाइल पर घोषित तरीके ( बाइट कोड स्तर पर) कम से कम स्थानीय चर के मूल्यों तक पहुंच है।

( जॉन का पूरा जवाब है - मैं इसे एक अनिर्धारित रखता हूं क्योंकि कोई व्यक्ति जेएलएस नियम में दिलचस्पी ले सकता है)


11

आप लौटे मूल्य प्राप्त करने के लिए एक वर्ग स्तर चर बना सकते हैं। मेरा मतलब

class A {
    int k = 0;
    private void f(Button b, int a){
        b.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(ClickEvent event) {
            k = a * 5;
        }
    });
}

अब आप K का मान प्राप्त कर सकते हैं और जहां चाहें वहां इसका उपयोग कर सकते हैं।

आपका उत्तर क्यों है:

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

क्योंकि एक स्थानीय आंतरिक वर्ग न तो किसी वर्ग या पैकेज का सदस्य होता है, न ही इसे एक्सेस स्तर के साथ घोषित किया जाता है। (स्पष्ट रहें, हालांकि, इसके अपने सदस्यों के पास एक सामान्य वर्ग की तरह पहुंच स्तर है।)


मैंने उल्लेख किया कि "इसे निजी सदस्य के रूप में रखे बिना"

6

खैर, जावा में, एक चर न केवल एक पैरामीटर के रूप में अंतिम हो सकता है, बल्कि एक वर्ग-स्तरीय क्षेत्र के रूप में, जैसे

public class Test
{
 public final int a = 3;

या एक स्थानीय चर के रूप में, जैसे

public static void main(String[] args)
{
 final int a = 3;

आप का उपयोग करना चाहते हैं और एक गुमनाम वर्ग से एक चर को संशोधित हैं, तो आप चर एक बनाने के लिए चाहते हो सकता है वर्ग-स्तर में चर संलग्न वर्ग।

public class Test
{
 public int a;
 public void doSomething()
 {
  Runnable runnable =
   new Runnable()
   {
    public void run()
    {
     System.out.println(a);
     a = a+1;
    }
   };
 }
}

आपके पास अंतिम के रूप में एक चर नहीं हो सकता है और इसे एक नया मूल्य दे सकता है। finalइसका मतलब बस इतना है: मूल्य अपरिवर्तनीय और अंतिम है।

और चूंकि यह अंतिम है, जावा इसे सुरक्षित रूप से स्थानीय अनाम कक्षाओं में कॉपी कर सकता है । आपको इंट के संदर्भ में कुछ नहीं मिल रहा है (विशेषकर चूंकि आपके पास जावा में प्राइमिटिव के संदर्भ नहीं हो सकते हैं, सिर्फ ऑब्जेक्ट के संदर्भ में )।

यह सिर्फ आपके अनाम वर्ग में कहे जाने वाले अंतर्निहित int के मूल्य से अधिक की नकल करता है।


3
मैं "क्लास-लेवल वेरिएबल" के साथ जुड़ता हूं static। शायद यह अधिक स्पष्ट है यदि आप इसके बजाय "उदाहरण चर" का उपयोग करते हैं।
एलजेन्सो

1
ठीक है, मैंने वर्ग-स्तर का उपयोग किया क्योंकि तकनीक उदाहरण और स्थिर चर दोनों के साथ काम करेगी।
ज़च एल

हम पहले से ही जानते हैं कि फाइनल सुलभ हैं लेकिन हम जानना चाहते हैं कि क्यों? क्या आप कृपया कुछ और स्पष्टीकरण क्यों जोड़ सकते हैं?
सौरभ ओझा

6

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


ऐसा नहीं है कि इसे C # जैसी भाषाओं में कैसे लागू किया जाता है जो इस सुविधा का समर्थन करते हैं। वास्तव में, कंपाइलर चर को स्थानीय चर से उदाहरण चर में बदल देता है, या यह इन चर के लिए एक अतिरिक्त डेटा संरचना बनाता है जो बाहरी वर्ग के दायरे को प्रभावित कर सकता है। हालांकि, स्थानीय चर की "कई प्रतियाँ" नहीं हैं
माइक 13

माइक76 मैं सी # के कार्यान्वयन पर एक नज़र नहीं पड़ा है, लेकिन स्काला दूसरी बात करता है जिसका आपने उल्लेख किया है मुझे लगता है: यदि किसी Intको बंद करने के लिए पुन: असाइन किया जा रहा है, तो उस चर को IntRef( उदाहरण के लिए एक परस्पर Integerआवरण) के रूप में बदल दें। प्रत्येक चर पहुंच को उसके अनुसार फिर से लिखा जाता है।
Adowrath

3

इस प्रतिबंध के औचित्य को समझने के लिए, निम्नलिखित कार्यक्रम पर विचार करें:

public class Program {

    interface Interface {
        public void printInteger();
    }
    static Interface interfaceInstance = null;

    static void initialize(int val) {
        class Impl implements Interface {
            @Override
            public void printInteger() {
                System.out.println(val);
            }
        }
        interfaceInstance = new Impl();
    }

    public static void main(String[] args) {
        initialize(12345);
        interfaceInstance.printInteger();
    }
}

InterfaceInstance के बाद स्मृति में रहता है इनिशियलाइज़ विधि रिटर्न, लेकिन पैरामीटर वैल नहीं करता है। JVM अपने दायरे से बाहर किसी स्थानीय चर तक नहीं पहुँच सकता है, इसलिए Java इंटरफ़ेस के भीतर एक ही नाम के निहित क्षेत्र में वैल के मूल्य की प्रतिलिपि बनाकर प्रिंटइंटर काम को बाद में करता हैInterfaceInstance कहा जाता है कि कब्जा कर लिया स्थानीय पैरामीटर का मान। यदि पैरामीटर अंतिम नहीं था (या प्रभावी रूप से अंतिम) तो इसका मूल्य बदल सकता है, कैप्चर किए गए मूल्य के साथ सिंक से बाहर हो सकता है, संभवतः अनजाने व्यवहार हो सकता है।


2

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


2

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

( http://en.wikipedia.org/wiki/Final_%28Java%29 )



0

जैसा कि जॉन ने कार्यान्वयन विवरण का उत्तर दिया है एक अन्य संभावित उत्तर यह होगा कि जेवीएम रिकॉर्ड में लिखना नहीं चाहता है जिसने उसकी सक्रियता को समाप्त कर दिया है।

उपयोग के मामले पर विचार करें जहां आपका लैम्ब्डा लागू होने के बजाय, किसी स्थान पर संग्रहीत किया जाता है और बाद में चलता है।

मुझे याद है कि स्मॉलटाक में आपको इस तरह का संशोधन करने पर एक अवैध स्टोर मिला होगा।


0

इस कोड को आज़माएं,

सरणी सूची बनाएं और उसके अंदर मान डालें और उसे वापस लौटाएँ:

private ArrayList f(Button b, final int a)
{
    final ArrayList al = new ArrayList();
    b.addClickHandler(new ClickHandler() {

         @Override
        public void onClick(ClickEvent event) {
             int b = a*5;
             al.add(b);
        }
    });
    return al;
}

ओपी कुछ कारणों की आवश्यकता के कारण पूछ रहा है। इसलिए आपको यह बताना चाहिए कि आपका कोड इसे कैसे संबोधित कर रहा है
नितिनसिंह

0

जावा अनाम वर्ग जावास्क्रिप्ट बंद होने के समान है, लेकिन जावा इसे अलग तरीके से लागू करता है। (एंडरसन के जवाब की जाँच करें)

इसलिए जावा डेवलपर को अजीब व्यवहार के साथ भ्रमित न करने के लिए जो जावास्क्रिप्ट पृष्ठभूमि से आने वालों के लिए हो सकता है। मुझे लगता है कि इसीलिए वे हमें इस्तेमाल करने के लिए मजबूर करते हैंfinal , यह जेवीएम सीमा नहीं है।

आइए नीचे दिए गए जावास्क्रिप्ट उदाहरण को देखें:

var add = (function () {
  var counter = 0;

  var func = function () {
    console.log("counter now = " + counter);
    counter += 1; 
  };

  counter = 100; // line 1, this one need to be final in Java

  return func;

})();


add(); // this will print out 100 in Javascript but 0 in Java

जावास्क्रिप्ट में, counterमान 100 होगा, क्योंकि एक ही हैcounter शुरुआत से अंत तक चर है।

लेकिन जावा में, यदि कोई नहीं है final, तो यह प्रिंट आउट होगा 0, क्योंकि जब आंतरिक ऑब्जेक्ट बनाया जा रहा है, तो 0मूल्य को आंतरिक वर्ग ऑब्जेक्ट के छिपे हुए गुणों की नकल किया जाता है। (यहां दो पूर्णांक चर हैं, एक स्थानीय विधि में, दूसरा आंतरिक कक्षा में छिपा हुआ गुण है)

तो आंतरिक वस्तु निर्माण (जैसे पंक्ति 1) के बाद कोई भी परिवर्तन, यह आंतरिक वस्तु को प्रभावित नहीं करेगा। तो यह दो अलग-अलग परिणामों और व्यवहार (जावा और जावास्क्रिप्ट के बीच) के बीच भ्रम पैदा करेगा।

मेरा मानना ​​है कि यही कारण है कि जावा इसे अंतिम रूप देने के लिए मजबूर करता है, इसलिए डेटा शुरुआत से अंत तक 'सुसंगत' है।


0

एक आंतरिक वर्ग के अंदर जावा अंतिम चर

भीतर का वर्ग ही उपयोग कर सकता है

  1. बाहरी वर्ग से संदर्भ
  2. अंतिम स्थानीय चर दायरे से बाहर हैं जो एक संदर्भ प्रकार (उदाहरण के लिए) हैं Object ...)
  3. मान (आदिम) (जैसे int...) प्रकार को अंतिम संदर्भ प्रकार से लपेटा जा सकता है । IntelliJ IDEAआप इसे एक तत्व सरणी में गुप्त रखने में मदद कर सकते हैं

जब एक non static nested( inner class) [के बारे में] संकलक द्वारा उत्पन्न किया जाता है - एक नया वर्ग - <OuterClass>$<InnerClass>.classबनाया जाता है और बाध्य मापदंडों को कंस्ट्रक्टर में पारित किया जाता है [स्थानीय स्तर पर विभाजन] । यह बंद करने के समान है

अंतिम चर वह चर है जिसे पुन: असाइन नहीं किया जा सकता है। अंतिम संदर्भ चर अभी भी एक राज्य को संशोधित करके बदला जा सकता है

यह संभव था कि यह अजीब होगा क्योंकि एक प्रोग्रामर के रूप में आप इस तरह से बना सकते हैं

//Not possible 
private void foo() {

    MyClass myClass = new MyClass(); //address 1
    int a = 5;

    Button button = new Button();

    //just as an example
    button.addClickHandler(new ClickHandler() {


        @Override
        public void onClick(ClickEvent event) {

            myClass.something(); //<- what is the address ?
            int b = a; //<- 5 or 10 ?

            //illusion that next changes are visible for Outer class
            myClass = new MyClass();
            a = 15;
        }
    });

    myClass = new MyClass(); //address 2
    int a = 10;
}

-2

हो सकता है कि यह ट्रिक आपको एक आइडिया दे

Boolean var= new anonymousClass(){
    private String myVar; //String for example
    @Overriden public Boolean method(int i){
          //use myVar and i
    }
    public String setVar(String var){myVar=var; return this;} //Returns self instane
}.setVar("Hello").method(3);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.