স্ট্রিমের উপাদানগুলিকে কীভাবে একটি নতুন তালিকায় সংগ্রহ করতে হয় তা সংগ্রাহকের জাভাদোক দেখায়। এমন কোনও ওয়ান-লাইনার রয়েছে যা ফলাফলগুলি বিদ্যমান অ্যারেলিস্টে যুক্ত করে?
স্ট্রিমের উপাদানগুলিকে কীভাবে একটি নতুন তালিকায় সংগ্রহ করতে হয় তা সংগ্রাহকের জাভাদোক দেখায়। এমন কোনও ওয়ান-লাইনার রয়েছে যা ফলাফলগুলি বিদ্যমান অ্যারেলিস্টে যুক্ত করে?
উত্তর:
দ্রষ্টব্য: নোসিদের উত্তরটি দেখায় যে কীভাবে বিদ্যমান সংগ্রহকে ব্যবহার করে যুক্ত করতে হয় forEachOrdered()
। বিদ্যমান সংগ্রহগুলিকে পরিবর্তনের জন্য এটি একটি কার্যকর এবং কার্যকর কৌশল। আমার উত্তরগুলি সম্বোধন করে যে Collector
কোনও বিদ্যমান সংগ্রহকে রূপান্তর করতে আপনি কেন ব্যবহার করবেন না ।
সংক্ষিপ্ত উত্তর হল কোন , অন্তত সাধারণভাবে না, আপনি একটি ব্যবহার করা উচিত নয় Collector
একটি বিদ্যমান সংগ্রহ পরিবর্তন করতে।
কারণটি হ'ল সংগ্রাহকরা থ্রেড-নিরাপদ নয় এমন সংগ্রহগুলি ছাড়িয়েও সমান্তরালতা সমর্থন করার জন্য ডিজাইন করেছিলেন। তারা যেভাবে এটি করে তা হ'ল প্রতিটি থ্রেড মধ্যবর্তী ফলাফলের নিজস্ব সংগ্রহের জন্য স্বাধীনভাবে পরিচালনা করা। প্রতিটি থ্রেডের নিজস্ব সংগ্রহটি যেভাবে হয় তা কল করা Collector.supplier()
যা প্রতিবার নতুন সংগ্রহ ফেরত পাঠানো দরকার ।
অন্তর্বর্তী ফলাফলের এই সংগ্রহগুলি আবার একত্রে থ্রেড-সীমাবদ্ধ ফ্যাশনে মার্জ করা হবে, যতক্ষণ না একক ফলাফল সংগ্রহ হয়। এটি collect()
অপারেশনের চূড়ান্ত ফলাফল ।
বাল্ডার এবং অ্যাসিলিয়াসের একটি দম্পতি উত্তর Collectors.toCollection()
একটি সরবরাহকারীকে নতুন তালিকার পরিবর্তে বিদ্যমান তালিকার পরিবর্তে ব্যবহার করার এবং তার পরে পাস করার পরামর্শ দিয়েছে । এটি সরবরাহকারী উপর প্রয়োজনীয়তা লঙ্ঘন করে, যা এটি প্রতিবার একটি নতুন, খালি সংগ্রহ ফেরত দেয়।
এটি সরল মামলার ক্ষেত্রে কাজ করবে, যেমন তাদের উত্তরের উদাহরণগুলি প্রমাণ করে। তবে এটি ব্যর্থ হবে, বিশেষত যদি স্ট্রিমটি সমান্তরালে চালিত হয়। (গ্রন্থাগারের ভবিষ্যতের সংস্করণটি অপ্রত্যাশিত কিছু উপায়ে পরিবর্তিত হতে পারে যার ফলে এটি ক্রমহ্রাসের ক্ষেত্রেও ব্যর্থ হতে পারে))
আসুন একটি সহজ উদাহরণ গ্রহণ করুন:
List<String> destList = new ArrayList<>(Arrays.asList("foo"));
List<String> newList = Arrays.asList("0", "1", "2", "3", "4", "5");
newList.parallelStream()
.collect(Collectors.toCollection(() -> destList));
System.out.println(destList);
আমি যখন এই প্রোগ্রামটি চালাই, আমি প্রায়শই একটি পেয়ে যাই ArrayIndexOutOfBoundsException
। এটি কারণ একাধিক থ্রেড অপারেটিং করছে ArrayList
, একটি থ্রেড-অনিরাপদ ডেটা কাঠামো। ঠিক আছে, আসুন এটি সিঙ্ক্রোনাইজ করা যাক:
List<String> destList =
Collections.synchronizedList(new ArrayList<>(Arrays.asList("foo")));
এটি আর ব্যতিক্রম ব্যর্থ হবে না। তবে প্রত্যাশিত ফলাফলের পরিবর্তে:
[foo, 0, 1, 2, 3]
এটি এর মতো অদ্ভুত ফলাফল দেয়:
[foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0]
এটি উপরে বর্ণিত থ্রেড-সীমাবদ্ধ জমা / মার্জিং অপারেশনের ফলাফল। সমান্তরাল স্ট্রিমের সাথে, প্রতিটি থ্রেড সরবরাহকারীকে মধ্যবর্তী জমে তার নিজস্ব সংগ্রহ পেতে কল করে। আপনি যদি সরবরাহকারীকে পাস করেন যা একই সংগ্রহটি ফেরত দেয় , প্রতিটি থ্রেড তার ফলাফলগুলি সেই সংকলনে সংযুক্ত করে। যেহেতু থ্রেডগুলির মধ্যে কোনও অর্ডার নেই, ফলাফলগুলি কিছু স্বেচ্ছাসেবী ক্রমে সংযোজন করা হবে।
তারপরে, যখন এই মধ্যবর্তী সংগ্রহগুলি একত্রীকরণ করা হয়, এটি মূলত তালিকাটিকে নিজের সাথে মার্জ করে। তালিকাগুলি ব্যবহার করে মার্জ করা হয়েছে List.addAll()
, যা বলছে যে অপারেশন চলাকালীন উত্স সংগ্রহটি পরিবর্তন করা হলে ফলাফল অপরিজ্ঞাত। এই ক্ষেত্রে, ArrayList.addAll()
একটি অ্যারে-অনুলিপি অপারেশন করে, তাই এটি নিজেই সদৃশ হয়ে যায়, যা কোনওটি প্রত্যাশা করবে তা সাজানো। (নোট করুন যে অন্যান্য তালিকা প্রয়োগকারীগুলির সম্পূর্ণ ভিন্ন আচরণ থাকতে পারে)) যাইহোক, এটি গন্তব্যের অদ্ভুত ফলাফল এবং নকল উপাদানগুলি ব্যাখ্যা করে।
আপনি বলতে পারেন, "আমি কেবলমাত্র আমার ধারাটি ধারাবাহিকভাবে চালানো নিশ্চিত করব" এবং এগিয়ে গিয়ে এই জাতীয় কোড লিখব
stream.collect(Collectors.toCollection(() -> existingList))
যাহাই হউক না কেন। আমি এটি করার বিরুদ্ধে সুপারিশ করব। আপনি যদি স্ট্রিমটি নিয়ন্ত্রণ করেন তবে নিশ্চিত হয়ে নিন যে এটি সমান্তরালে চলবে না। আমি আশা করি যে স্ট্রিমাগুলি সংগ্রহের পরিবর্তে হস্তান্তরিত হবে এমন এক ধরণের প্রোগ্রামিংয়ের উত্থান হবে। যদি কেউ আপনাকে একটি স্ট্রিম দেয় এবং আপনি এই কোডটি ব্যবহার করেন তবে স্ট্রিমটি সমান্তরাল হয়ে গেলে এটি ব্যর্থ হবে। খারাপ, কারো আপনি একটি অনুক্রমিক প্রবাহ হাতে পারে এবং এই কোড কিছুদিনের জন্য সূক্ষ্ম কাজ, সব পরীক্ষার ইত্যাদি পাস হবে তারপর, কিছু সময় নির্বিচারে পরিমাণ পরে, সিস্টেমের মধ্যে অন্যত্র কোড সমান্তরাল স্ট্রিম যা হবে ব্যবহার করতে পরিবর্তন করতে পারে আপনার কোড বিরতি।
ঠিক আছে, তারপরে sequential()
আপনি এই কোডটি ব্যবহার করার আগে যে কোনও স্ট্রিমটিতে কল করতে ভুলবেন না তা নিশ্চিত করুন:
stream.sequential().collect(Collectors.toCollection(() -> existingList))
অবশ্যই, আপনি প্রতিবার এটি করতে মনে করবেন, তাই না? :-) যাক বলে দিন। তারপরে, পারফরম্যান্স টিম ভাববে যে তাদের সমস্ত সাবধানে তৈরি করা সমান্তরাল বাস্তবায়ন কেন কোনও গতিপথ সরবরাহ করছে না। এবং আবার তারা এটিকে আপনার কোডে সন্ধান করবে যা পুরো স্ট্রিমটিকে ধারাবাহিকভাবে চালাতে বাধ্য করছে।
এটা করবেন না।
toCollection
পদ্ধতির সরবরাহকারী যুক্তিটি প্রতিবার আমাকে নতুন করে এবং খালি সংগ্রহ ফিরিয়ে আনবে to আমি সত্যিই কোর জাভা ক্লাসগুলির জাভাদোক চুক্তিটি ভাঙতে চাই।
forEachOrdered
। পার্শ্ব প্রতিক্রিয়াগুলির মধ্যে ইতিমধ্যে উপাদান রয়েছে কিনা তা বিবেচনা না করে বিদ্যমান সংগ্রহে উপাদান যুক্ত করা অন্তর্ভুক্ত। যদি আপনি চান একটি স্ট্রীমের উপাদানের একটি মধ্যে স্থাপন করা নতুন সংগ্রহ, ব্যবহার collect(Collectors.toList())
বা toSet()
বা toCollection()
।
আমি যতদূর দেখতে পাচ্ছি, এখনও পর্যন্ত অন্যান্য সমস্ত উত্তর একটি বিদ্যমান প্রবাহে উপাদান যুক্ত করতে একজন সংগ্রাহক ব্যবহার করেছে। যাইহোক, একটি সংক্ষিপ্ত সমাধান আছে, এবং এটি ক্রমিক এবং সমান্তরাল উভয় প্রবাহের জন্য কাজ করে। আপনি পদ্ধতির রেফারেন্সের সাথে মিশ্রণে প্রত্যেকটি অর্ডার করা পদ্ধতিটি সহজভাবে ব্যবহার করতে পারেন ।
List<String> source = ...;
List<Integer> target = ...;
source.stream()
.map(String::length)
.forEachOrdered(target::add);
কেবলমাত্র সীমাবদ্ধতা হ'ল সেই উত্স এবং লক্ষ্যটি আলাদা আলাদা তালিকা, কারণ যতক্ষণ না আপনি প্রবাহিত হবেন ততক্ষণ প্রবাহের উত্সে পরিবর্তন আনতে পারবেন না।
নোট করুন যে এই সমাধানটি ক্রমিক এবং সমান্তরাল উভয় স্ট্রিমের জন্য কাজ করে। তবে এটি সম্মতিতে কোনও উপকার করে না। পদ্ধতি রেফারেন্স পাস অর্ডারের সর্বদা ক্রমান্বয়ে কার্যকর করা হবে।
forEach(existing::add)
একটি উত্তরে সম্ভাবনা হিসাবে অন্তর্ভুক্ত করেছি । আমারও যুক্ত করা উচিত forEachOrdered
ছিল ...
forEachOrdered
পরিবর্তে কোন কারণ ব্যবহার করেছেন forEach
?
forEachOrdered
উভয় প্রবাহের জন্য কাজ করে। বিপরীতে, সমান্তরাল স্ট্রিমগুলির জন্য একই সাথে পাস ফাংশন অবজেক্টটি কার্যকর করতে পারে। এই ক্ষেত্রে, ফাংশন অবজেক্টটি অবশ্যই সঠিকভাবে সিঙ্ক্রোনাইজ করা উচিত, যেমন একটি ব্যবহার করে । forEach
Vector<Integer>
target::add
। যে থ্রেড থেকে পদ্ধতিটি চাওয়া হয়েছে তা নির্বিশেষে কোনও ডেটা রেস নেই । আমি আপনাকে জানতে হবে আশা করি।
সংক্ষিপ্ত উত্তরটি হ'ল (বা হওয়া উচিত নয়)। সম্পাদনা: হ্যাঁ, এটি সম্ভব (নীচে আসলিয়াদের উত্তর দেখুন), তবে পড়া চালিয়ে যান। EDIT2: তবে স্টুয়ার্ট মার্কসের উত্তরটি দেখুন অন্য কোনও কারণে আপনি এখনও এটি না করা উচিত!
দীর্ঘ উত্তর:
জাভা 8-এ এই কনস্ট্রাক্টের উদ্দেশ্য হ'ল ফাংশনাল প্রোগ্রামিংয়ের কিছু ধারণা চালু করা ভাষায় ; ফাংশনাল প্রোগ্রামিংয়ে, ডেটা স্ট্রাকচারগুলি সাধারণত পরিবর্তিত হয় না, পরিবর্তে, পুরানোগুলি থেকে নতুন তৈরি করা হয় যেমন মানচিত্র, ফিল্টার, ভাঁজ / হ্রাস এবং আরও অনেকের রূপান্তর মাধ্যমে।
আপনার যদি পুরানো তালিকাটি অবশ্যই পরিবর্তন করতে হয় তবে কেবল ম্যাপ করা আইটেমগুলিকে একটি নতুন তালিকায় সংগ্রহ করুন:
final List<Integer> newList = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
এবং তারপরে list.addAll(newList)
আবার করুন: যদি আপনার অবশ্যই হয়।
(বা পুরানো এবং নতুনটির সাথে মিল রেখে একটি নতুন তালিকা তৈরি করুন এবং এটিকে list
ভেরিয়েবলের কাছে ফেরত দিন - এটি এফপির চেয়ে আরও কিছুটা বেশি addAll
)
এপিআই হিসাবে: যদিও এপিআই এটির অনুমতি দেয় (আবার, অ্যাসিলিয়াসদের উত্তর দেখুন) আপনার এটিকে নির্বিশেষে না করে চেষ্টা করা উচিত, কমপক্ষে সাধারণভাবে। দৃষ্টান্তের (এফপি) লড়াই না করা এবং এটি লড়াই করার চেয়ে এটি শেখার চেষ্টা করা ভাল (যদিও জাভা সাধারণত কোনও এফপি ভাষা নয়), এবং একেবারে প্রয়োজন হলে কেবল "ডের্তিয়ার" কৌশল অবলম্বন করা ভাল।
সত্যই দীর্ঘ উত্তর: (উদাহরণস্বরূপ যদি আপনি কোনও এফপি ভূমিকা / বইটি সন্ধান করার এবং পড়ার প্রচেষ্টা অন্তর্ভুক্ত করেন)
বিদ্যমান তালিকাগুলি সংশোধন করা কেন সাধারণভাবে একটি খারাপ ধারণা এবং এটি কম রক্ষণাবেক্ষণের কোডের দিকে পরিচালিত করে তা খুঁজে বের করার জন্য - আপনি যদি স্থানীয় ভেরিয়েবলটি সংশোধন না করেন এবং আপনার অ্যালগরিদম সংক্ষিপ্ত এবং / বা তুচ্ছ না হয় যা কোড রক্ষণাবেক্ষণের প্রশ্নটির বাইরে নয় — ফাংশনাল প্রোগ্রামিং (এখানে শত শত) এর একটি ভাল ভূমিকা আবিষ্কার করুন এবং পড়া শুরু করুন। একটি "পূর্বরূপ" ব্যাখ্যাটি এরকম কিছু হবে: ডেটা পরিবর্তন করতে না পারার জন্য এটি আরও গাণিতিকভাবে সুরক্ষিত এবং সহজতর (আপনার প্রোগ্রামের বেশিরভাগ অংশে) এবং আপনার মস্তিষ্কে একবার উচ্চ স্তরের এবং কম প্রযুক্তিগত (পাশাপাশি আরও মানববান্ধব) হয়ে যায় পুরানো শৈলীর অপরিহার্য চিন্তাভাবনা থেকে দূরে রূপান্তর) প্রোগ্রাম লজিকের সংজ্ঞা।
এরিক অলিক ইতিমধ্যে খুব ভাল কারণ দিয়েছেন, কেন আপনি সম্ভবত কোনও স্ট্রিমের উপাদানগুলি কোনও বিদ্যমান তালিকায় সংগ্রহ করতে চান না।
যাইহোক, আপনি যদি সত্যিই এই কার্যকারিতাটির প্রয়োজন হয় তবে আপনি নীচের ওয়ান-লাইনার ব্যবহার করতে পারেন।
কিন্তু স্টুয়ার্ট মার্কস তার উত্তরে যেমন ব্যাখ্যা করেছেন, আপনার কখনই এটি করা উচিত নয় , যদি স্রোতগুলি সমান্তরাল স্রোত হতে পারে - আপনার নিজের ঝুঁকিতে ব্যবহার করুন ...
list.stream().collect(Collectors.toCollection(() -> myExistingList));
আপনাকে কেবল আসল তালিকাটি রেফারেন্স করতে হবে যেটি Collectors.toList()
ফিরে আসে।
এখানে একটি ডেমো রয়েছে:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Reference {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(list);
// Just collect even numbers and start referring the new list as the original one.
list = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(list);
}
}
এবং কীভাবে আপনি কেবলমাত্র একটি লাইনে নতুন তৈরি উপাদানগুলিকে আপনার মূল তালিকায় যুক্ত করতে পারেন তা এখানে।
List<Integer> list = ...;
// add even numbers from the list to the list again.
list.addAll(list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList())
);
এই ফাংশনাল প্রোগ্রামিং প্যারাডিজম এটিই সরবরাহ করে।
আমি পুরাতন তালিকা এবং নতুন তালিকাটিকে স্ট্রিম হিসাবে একত্রিত করব এবং ফলাফলগুলি গন্তব্য তালিকায় সংরক্ষণ করব। সমান্তরালে খুব ভাল কাজ করে।
স্টুয়ার্ট মার্কস প্রদত্ত গৃহীত উত্তরের উদাহরণটি আমি ব্যবহার করব:
List<String> destList = Arrays.asList("foo");
List<String> newList = Arrays.asList("0", "1", "2", "3", "4", "5");
destList = Stream.concat(destList.stream(), newList.stream()).parallel()
.collect(Collectors.toList());
System.out.println(destList);
//output: [foo, 0, 1, 2, 3, 4, 5]
আশা করি এটা সাহায্য করবে.
Collection
"