ল্যাম্বদার ভিতরে থেকে স্থানীয় ভেরিয়েবল পরিবর্তন করা


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
@ কিরিলিওম তারা বেনাম শ্রেণীর জন্য সিনট্যাকটিক চিনি নয়। Lambdas ফণা অধীন পদ্ধতি হ্যান্ডলগুলি ব্যবহার
ডাইঅক্সিন

উত্তর:


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 };? তুমি কি এটা ব্যাখ্যা করতে পারবে. আপনাকে ধন্যবাদ
ম্রবেলা

@ এমরবেলা ঠিক কী বুঝতে পারছেন না? আমি কি সেই নির্দিষ্ট বিটটি পরিষ্কার করতে পারি?
অলিভিয়ার গ্রাগোয়ার

1
@ এম্বেলা অ্যারে রেফারেন্স দ্বারা পাস করা হয়। আপনি যখন একটি অ্যারে পাস করেন, আপনি আসলে সেই অ্যারের জন্য একটি মেমরি ঠিকানায় যাচ্ছেন। পূর্ণসংখ্যার মতো আদিম ধরণের মান দ্বারা প্রেরণ করা হয়, যার অর্থ মানটির একটি অনুলিপি প্রবেশ করানো হয়। মূল্য অনুসারে মূল মান এবং আপনার পদ্ধতিতে প্রেরিত কপির মধ্যে কোনও সম্পর্ক নেই - অনুলিপিটি ম্যানিপুলেট করা মূলটির সাথে কিছুই করে না। রেফারেন্স দিয়ে পাস করার অর্থ আসল মান এবং পদ্ধতিতে প্রেরণ করা একরকম একটি - এটি আপনার পদ্ধতিতে ম্যানিপুলেট করা পদ্ধতির বাইরের মানকে বদলে দেবে।
গোয়েন্দা

@ অলিভার গ্রাওগোয়ার এটি সত্যিই আমার আড়ালটি সংরক্ষণ করেছিল। আমি জাভা 10 সমাধানটি "ভার" দিয়ে ব্যবহার করেছি। এটি কীভাবে কাজ করে তা ব্যাখ্যা করতে পারেন? আমি সর্বত্র গুগলড করেছি এবং এই নির্দিষ্ট ব্যবহারের সাথে এটিই আমি খুঁজে পেয়েছি। আমার অনুমান যে যেহেতু একটি ল্যাম্বডা কেবলমাত্র তার পরিসীমা বাইরে থেকে চূড়ান্ত বস্তুগুলিকে (কার্যকরভাবে) অনুমতি দেয়, তাই "ভার" অবজেক্টটি মূলত কম্পাইলারকে এটি চূড়ান্তভাবে অনুমান করাতে বোকা বানাবে, যেহেতু এটি ধরে নিয়েছে যে কেবল চূড়ান্ত অবজেক্টের বাইরেও রেফারেন্স দেওয়া হবে। আমি জানি না, এটি আমার সেরা অনুমান। আপনি যদি কোনও ব্যাখ্যা সরবরাহ করতে আগ্রহী হন তবে আমি এটির প্রশংসা করব, যেহেতু আমার কোড কেন কাজ করে তা জানতে চাই :)
ওকে

1
@ ওকার এটি কাজ করে কারণ জাভা মাইক্লাস $ 1 ক্লাসটি তৈরি করবে নিম্নরূপ: class MyClass$1 { int value; }একটি সাধারণ ক্লাসে, এর অর্থ হল আপনি একটি প্যাকেজ-প্রাইভেট ভেরিয়েবল নামের ক্লাস তৈরি করেন value। এটি একই, তবে ক্লাসটি আসলে আমাদের কাছে বেনামে রচিত, সংকলক বা জেভিএমের কাছে নয়। যেমন যদি এটা ছিল জাভা কোড কম্পাইল হবে MyClass$1 wrapper = new MyClass$1();। এবং বেনাম শ্রেণি হয়ে ওঠে আরও একটি ক্লাস। শেষ পর্যন্ত, আমরা এটিকে পঠনযোগ্য করে তুলতে কেবল তার উপরে সিনট্যাকটিকাল চিনি যুক্ত করেছি। এছাড়াও, ক্লাসটি প্যাকেজ-প্রাইভেট ক্ষেত্র সহ একটি অভ্যন্তর শ্রেণি। সেই ক্ষেত্রটি ব্যবহারযোগ্য।
অলিভিয়ার গ্রাগোয়ার

14

এটি একটি এক্সওয়াই সমস্যার সাথে মোটামুটি কাছাকাছি । অর্থাৎ, জিজ্ঞাসিত প্রশ্নটি মূলত কীভাবে কোনও ল্যাম্বডা থেকে বন্দী স্থানীয় ভেরিয়েবলকে রূপান্তর করতে পারে। তবে হাতের আসল কাজ হ'ল তালিকার উপাদানগুলি কীভাবে সংখ্যায়িত করা যায়।

আমার অভিজ্ঞতায়, 80% এরও বেশি সময় ধরে সেখানে কোনও ল্যাম্বডা থেকে বন্দী স্থানীয়কে কীভাবে রূপান্তর করা যায় সে সম্পর্কে একটি প্রশ্ন রয়েছে, আরও এগিয়ে যাওয়ার আরও ভাল উপায় আছে। সাধারণত এটি হ্রাস জড়িত জড়িত, কিন্তু এই ক্ষেত্রে তালিকা সূচকগুলিতে একটি স্ট্রিম চালানোর কৌশলটি ভালভাবে প্রয়োগ হয়:

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

3
ভাল সমাধান তবে কেবল যদি listএকটি RandomAccessতালিকা থাকে
ZhekaKozlov

আমি জানতে আগ্রহী হই যে অগ্রগতি বার্তার সমস্যা যদি প্রতিটি কে পুনরাবৃত্তির ব্যবহারিকভাবে কোনও ডেডিকেটেড কাউন্টার ছাড়াই সমাধান করা যায়, তবে এই ক্ষেত্রে: stream.forEach (e -> {doSomething (e); if (++ ctr% 1000 = = 0) log.info ( "আমি প্রক্রিয়াজাত করে থাকেন {} উপাদান", CTR);} আমি আরো ব্যবহারিক উপায় (হ্রাস এটা করতে পারে না দেখতে না, কিন্তু বিশেষ করে সমান্তরাল স্ট্রিম সঙ্গে আরো বাগাড়ম্বরপূর্ণ হবে,)।
zakmck

14

যদি আপনার কেবলমাত্র বাহ্য থেকে ল্যাম্বডায় প্রবেশ করতে হয় এবং এটি না বেরিয়ে আসে, তবে আপনি ল্যাম্বদার পরিবর্তে নিয়মিত বেনাম শ্রেণিতে এটি করতে পারেন:

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

1
... এবং যদি আপনাকে ফলাফলটি পড়ার দরকার হয় তবে কী হবে? ফলাফলটি শীর্ষ স্তরের কোডে দৃশ্যমান নয়।
লুক উশরউড

@ লুকুশারউড: আপনি ঠিক বলেছেন। এটি কেবলমাত্র যদি আপনার বাইরে থেকে ল্যাম্বডায় ডেটা পাস করার প্রয়োজন হয়, এটি বের না করে। আপনার যদি এটি বের করার দরকার হয় তবে আপনার ল্যাম্বডায় কোনও পরিবর্তনীয় বস্তুর যেমন একটি অ্যারে, বা চূড়ান্ত নয় এমন পাবলিক ফিল্ডগুলির সাথে সম্পর্কিত কোনও রেফারেন্স ক্যাপচার করা উচিত এবং এটিকে বস্তুতে সেট করে ডেটা পাস করতে হবে।
newacct

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 () এবং পিক () এর মতো অল্প সংখ্যক স্ট্রিম অপারেশন কেবল পাশের মাধ্যমেই পরিচালনা করতে পারে -প্রভাব; এগুলি যত্ন সহকারে ব্যবহার করা উচিত


0

আমার কিছুটা আলাদা সমস্যা ছিল ForEach- এ স্থানীয় ভেরিয়েবলকে বাড়ানোর পরিবর্তে, আমাকে স্থানীয় ভেরিয়েবলের জন্য একটি বিষয় বরাদ্দ করতে হবে।

আমি এটি একটি ব্যক্তিগত অভ্যন্তরীণ ডোমেন শ্রেণীর সংজ্ঞা দিয়ে সমাধান করেছি যা আমি তালিকা (পুনরুত্থান) করতে চাইছি এবং দেশটি তালিকা (ফাউন্ডকাউন্ট্রি) থেকে প্রাপ্ত প্রত্যাশা আউটপুট উভয়কেই আবৃত করে। তারপরে জাভা 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

হ্যাঁ, আপনি ল্যাম্বডাসের অভ্যন্তর থেকে স্থানীয় ভেরিয়েবলগুলি সংশোধন করতে পারেন (অন্য উত্তরগুলির দ্বারা দেখানো পথে), তবে আপনার এটি করা উচিত নয়। লাম্বডাস প্রোগ্রামিংয়ের কার্যকরী শৈলীর জন্য রয়েছে এবং এর অর্থ: কোনও পার্শ্ব প্রতিক্রিয়া নেই। আপনি যা করতে চান তা খারাপ স্টাইল হিসাবে বিবেচিত হয়। সমান্তরাল স্রোতের ক্ষেত্রে এটি বিপজ্জনক।

পার্শ্ব প্রতিক্রিয়া ছাড়াই আপনার সমাধান পাওয়া উচিত বা লুপের জন্য একটি traditionalতিহ্যবাহী ব্যবহার করা উচিত।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.