मेमने के अंदर से स्थानीय चर को संशोधित करना


115

forEachएक संकलन त्रुटि देता है में एक स्थानीय चर को संशोधित :

साधारण

    int ordinal = 0;
    for (Example s : list) {
        s.setOrdinal(ordinal);
        ordinal++;
    }

लंबोदर के साथ

    int ordinal = 0;
    list.forEach(s -> {
        s.setOrdinal(ordinal);
        ordinal++;
    });

किसी भी विचार यह कैसे हल करने के लिए?


8
एक आंतरिक आंतरिक वर्ग के लिए लैम्ब्डा को अनिवार्य रूप से सिंथेटिक चीनी माना जाता है, मेरा अंतर्ज्ञान यह है कि एक गैर अंतिम, स्थानीय चर पर कब्जा करना असंभव है। मैं हालांकि गलत साबित होना पसंद करूंगा।
सिंकिंगपॉइंट

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

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


2
@Quirliom वे अनाम वर्गों के लिए वाक्य रचना चीनी नहीं हैं। लैम्ब्डा हुड के तहत विधि हैंडल का उपयोग करता है
डाइऑक्सिन

जवाबों:


176

एक आवरण का उपयोग करें

किसी भी तरह का रैपर अच्छा है।

जावा 8+ के साथ , या तो एक का उपयोग करें AtomicInteger:

AtomicInteger ordinal = new AtomicInteger(0);
list.forEach(s -> {
  s.setOrdinal(ordinal.getAndIncrement());
});

... या एक सरणी:

int[] ordinal = { 0 };
list.forEach(s -> {
  s.setOrdinal(ordinal[0]++);
});

जावा 10+ के साथ :

var wrapper = new Object(){ int ordinal = 0; };
list.forEach(s -> {
  s.setOrdinal(wrapper.ordinal++);
});

ध्यान दें: यदि आप एक समानांतर धारा का उपयोग करते हैं तो बहुत सावधान रहें। आप अपेक्षित परिणाम नहीं पा सकते हैं। स्टुअर्ट जैसे अन्य समाधान उन मामलों के लिए अधिक अनुकूलित हो सकते हैं।

के अलावा अन्य प्रकार के लिए int

बेशक, यह अभी भी अन्य प्रकारों के लिए मान्य है int। आपको केवल रैपिंग प्रकार को AtomicReferenceउस प्रकार के सरणी में बदलना होगा । उदाहरण के लिए, यदि आप एक का उपयोग करते हैं String, तो बस निम्नलिखित करें:

AtomicReference<String> value = new AtomicReference<>();
list.forEach(s -> {
  value.set("blah");
});

एक सरणी का उपयोग करें:

String[] value = { null };
list.forEach(s-> {
  value[0] = "blah";
});

या जावा 10+ के साथ:

var wrapper = new Object(){ String value; }
list.forEach(s->{
  wrapper.value = "blah";
});

आपको किसी सरणी की तरह उपयोग करने की अनुमति क्यों है int[] ordinal = { 0 };? क्या आप इसे समझा सकते हैं। धन्यवाद
mrbela

@mrbela वास्तव में आप क्या नहीं समझते हैं? शायद मैं उस विशिष्ट बिट को स्पष्ट कर सकता हूं?
ओलिवियर ग्रेजायर

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

@Olivier Grégoire ने वास्तव में मेरे छिपाने को बचाया। मैंने "var" के साथ Java 10 समाधान का उपयोग किया। क्या आप बता सकते हैं कि यह कैसे काम करता है? मैंने हर जगह गुगली की है और यह एकमात्र स्थान है जिसे मैंने इस विशेष उपयोग के साथ पाया है। मेरा अनुमान है कि चूंकि एक लैम्ब्डा केवल अपने दायरे के बाहर से (प्रभावी रूप से) अंतिम वस्तुओं को अनुमति देता है, इसलिए "var" ऑब्जेक्ट मूल रूप से कंपाइलर को इनफॉरमेशन में मूर्ख बनाता है कि यह अंतिम है, क्योंकि यह मानता है कि केवल अंतिम ऑब्जेक्ट्स को ही स्कोप के बाहर रेफर किया जाएगा। मुझे नहीं पता, यह मेरा सबसे अच्छा अनुमान है। यदि आप एक स्पष्टीकरण प्रदान करना चाहते हैं तो मैं इसकी सराहना करूंगा, क्योंकि मैं जानना चाहता हूं कि मेरा कोड क्यों काम करता है :)
ओकर

1
@oaker यह काम करता है क्योंकि जावा वर्ग MyClass $ 1 को निम्नानुसार बनाएगा: class MyClass$1 { int value; }एक सामान्य वर्ग में, इसका मतलब है कि आप एक पैकेज-निजी चर नाम से एक वर्ग बनाते हैं value। यह समान है, लेकिन वर्ग वास्तव में हमारे लिए गुमनाम है, संकलक या जेवीएम के लिए नहीं। जावा कोड संकलित करेगा जैसे कि यह था MyClass$1 wrapper = new MyClass$1();। और अनाम वर्ग सिर्फ एक और वर्ग बन जाता है। अंत में, हमने इसे पढ़ने योग्य बनाने के लिए इसके ऊपर सिंटैक्टिकल शुगर मिलाया। इसके अलावा, पैकेज-निजी क्षेत्र के साथ वर्ग एक आंतरिक वर्ग है। वह क्षेत्र प्रयोग करने योग्य है।
ओलिवियर ग्रेगोइरे

14

यह एक XY समस्या के काफी करीब है । यही है, सवाल पूछा जा रहा है अनिवार्य रूप से एक लंबोदर से कैप्चर किए गए स्थानीय चर को कैसे म्यूट किया जाए। लेकिन हाथ में वास्तविक कार्य यह है कि किसी सूची के तत्वों को कैसे रखा जाए।

मेरे अनुभव में, 80% समय के ऊपर एक लंबर के भीतर से कैप्चर किए गए स्थानीय को कैसे म्यूट करना है, यह सवाल है कि आगे बढ़ने का एक बेहतर तरीका है। आमतौर पर इसमें कमी शामिल होती है, लेकिन इस मामले में सूची अनुक्रमित पर एक धारा चलाने की तकनीक अच्छी तरह से लागू होती है:

IntStream.range(0, list.size())
         .forEach(i -> list.get(i).setOrdinal(i));

3
अच्छा समाधान है, लेकिन केवल अगर listएक RandomAccessसूची है
ZhekaKozlov

मुझे यह जानने के लिए उत्सुक होना चाहिए कि क्या प्रगति संदेश की समस्या हर k पुनरावृत्तियों को एक समर्पित काउंटर के बिना व्यावहारिक रूप से हल किया जा सकता है, अर्थात, यह मामला: stream.forEach (e -> {doSomething (e); अगर; ++ ++% 1000 =) = 0) log.info ("मैंने {} तत्वों को संसाधित किया है", ctr);} मुझे अधिक व्यावहारिक तरीका नहीं दिखता है (कमी यह कर सकती है, लेकिन विशेष रूप से समानांतर धाराओं के साथ अधिक
क्रिया होगी

14

यदि आपको केवल लैंबडा में बाहर से मूल्य पारित करने की आवश्यकता है, और इसे बाहर नहीं निकालना है, तो आप इसे लैम्बडा के बजाय एक नियमित अनाम वर्ग के साथ कर सकते हैं:

list.forEach(new Consumer<Example>() {
    int ordinal = 0;
    public void accept(Example s) {
        s.setOrdinal(ordinal);
        ordinal++;
    }
});

1
... और क्या होगा यदि आपको वास्तव में परिणाम पढ़ने की आवश्यकता है? परिणाम शीर्ष स्तर पर कोड करने के लिए दृश्यमान नहीं है।
ल्यूक उशरवुड

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

7

जैसा कि लैम्डा के बाहर से उपयोग किए जाने वाले वेरिएबल्स का (अनुमानित रूप से) अंतिम होना है, आपको कुछ ऐसा उपयोग करना होगा AtomicInteger अपनी डेटा संरचना की या लिखना होगा।

Https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables देखें ।


3

यदि आप जावा 10 पर हैं, तो आप इसके लिए उपयोग कर सकते हैं var:

var ordinal = new Object() { int value; };
list.forEach(s -> {
    s.setOrdinal(ordinal.value);
    ordinal.value++;
});

3

एक AtomicIntegerसरणी का उपयोग करने के लिए (या किसी अन्य ऑब्जेक्ट को स्टोर करने में सक्षम) का एक विकल्प है:

final int ordinal[] = new int[] { 0 };
list.forEach ( s -> s.setOrdinal ( ordinal[ 0 ]++ ) );

लेकिन स्टुअर्ट का जवाब देखें : आपके मामले से निपटने का एक बेहतर तरीका हो सकता है।


2

मुझे पता है कि यह एक पुराना सवाल है, लेकिन अगर आप वर्कअराउंड के साथ सहज हैं, तो इस तथ्य पर विचार करते हुए कि बाहरी चर अंतिम होना चाहिए, आप बस यह कर सकते हैं:

final int[] ordinal = new int[1];
list.forEach(s -> {
    s.setOrdinal(ordinal[0]);
    ordinal[0]++;
});

शायद सबसे सुरुचिपूर्ण, या यहां तक ​​कि सबसे सही नहीं है, लेकिन यह करेगा।


1

आप इसे कंपाइलर को वर्कअराउंड करने के लिए रैप कर सकते हैं लेकिन कृपया याद रखें कि लैम्ब्डा में साइड इफेक्ट हतोत्साहित करते हैं।

जावदोक को उद्धृत करने के लिए

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


0

मुझे थोड़ी अलग समस्या थी। फ़ोरच में एक स्थानीय चर को बढ़ाने के बजाय, मुझे स्थानीय चर के लिए एक वस्तु आवंटित करने की आवश्यकता थी।

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

डोमेन ऑब्जेक्ट:

public class Country {

    private int id;
    private String countryName;

    public Country(int id, String countryName){
        this.id = id;
        this.countryName = countryName;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCountryName() {
        return countryName;
    }

    public void setCountryName(String countryName) {
        this.countryName = countryName;
    }
}

आवरण वस्तु:

private class CountryFound{
    private final List<Country> countryList;
    private Country foundCountry;
    public CountryFound(List<Country> countryList, Country foundCountry){
        this.countryList = countryList;
        this.foundCountry = foundCountry;
    }
    public List<Country> getCountryList() {
        return countryList;
    }
    public void setCountryList(List<Country> countryList) {
        this.countryList = countryList;
    }
    public Country getFoundCountry() {
        return foundCountry;
    }
    public void setFoundCountry(Country foundCountry) {
        this.foundCountry = foundCountry;
    }
}

Iterate ऑपरेशन:

int id = 5;
CountryFound countryFound = new CountryFound(countryList, null);
countryFound.getCountryList().forEach(c -> {
    if(c.getId() == id){
        countryFound.setFoundCountry(c);
    }
});
System.out.println("Country found: " + countryFound.getFoundCountry().getCountryName());

आप रैपर क्लास विधि "सेटकंट्रीलिस्ट" () को हटा सकते हैं और क्षेत्र को "कंट्रीलिस्ट" फाइनल बना सकते हैं, लेकिन मुझे इन विवरणों को छोड़कर त्रुटि संकलन नहीं मिला।


0

अधिक सामान्य समाधान करने के लिए, आप एक सामान्य रैपर क्लास लिख सकते हैं:

public static class Wrapper<T> {
    public T obj;
    public Wrapper(T obj) { this.obj = obj; }
}
...
Wrapper<Integer> w = new Wrapper<>(0);
this.forEach(s -> {
    s.setOrdinal(w.obj);
    w.obj++;
});

(यह अल्मीर कैम्पोस द्वारा दिए गए समाधान का एक प्रकार है)।

विशिष्ट मामले में यह एक अच्छा समाधान नहीं है, जैसा Integerकि intआपके उद्देश्य से भी बदतर है , वैसे भी यह समाधान अधिक सामान्य है जो मुझे लगता है।


0

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

आपको या तो साइड इफेक्ट के बिना एक समाधान ढूंढना चाहिए या एक पारंपरिक लूप का उपयोग करना चाहिए।

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