জাভা স্ট্রিমগুলিতে কি কেবলমাত্র ডিবাগিংয়ের জন্য উঁকি দেওয়া হয়?


137

আমি জাভা স্ট্রিমগুলি সম্পর্কে পড়ছি এবং আমি পাশাপাশি যাচ্ছি নতুন জিনিসগুলি আবিষ্কার করছি। আমি যে নতুন জিনিস পেয়েছি তার মধ্যে একটি হল peek()ফাংশন। আমি উঁকি মেরে পড়া প্রায় সমস্ত কিছুই বলে এটি আপনার স্ট্রিমগুলি ডিবাগ করার জন্য ব্যবহার করা উচিত।

যদি আমার স্ট্রিম থাকে যেখানে প্রতিটি অ্যাকাউন্টের একটি ব্যবহারকারীর নাম, পাসওয়ার্ড ক্ষেত্র এবং একটি লগইন () এবং লগইন ইন () পদ্ধতি থাকে।

আমারও আছে

Consumer<Account> login = account -> account.login();

এবং

Predicate<Account> loggedIn = account -> account.loggedIn();

কেন এত খারাপ হবে?

List<Account> accounts; //assume it's been setup
List<Account> loggedInAccount = 
accounts.stream()
    .peek(login)
    .filter(loggedIn)
    .collect(Collectors.toList());

এখন যতদূর আমি এটি বলতে পারি এটি করার উদ্দেশ্যে ঠিক কী করে। এটা তোলে;

  • অ্যাকাউন্টগুলির একটি তালিকা নেয়
  • প্রতিটি অ্যাকাউন্টে লগ ইন করার চেষ্টা করে
  • লগ ইন নেই এমন কোনও অ্যাকাউন্ট ফিল্টার করে
  • লগ ইন করা অ্যাকাউন্টগুলি একটি নতুন তালিকায় সংগ্রহ করে

এরকম কিছু করার ক্ষতি কি? কোন কারণেই আমার এগিয়ে যাওয়া উচিত নয়? সবশেষে, যদি এই সমাধান না হয় তবে কী?

এর মূল সংস্করণটি। ফিল্টার () পদ্ধতিটি নীচে ব্যবহার করেছে;

.filter(account -> {
        account.login();
        return account.loggedIn();
    })

38
যে কোনও সময় আমি নিজেকে একটি বহু-লাইন ল্যাম্বডা প্রয়োজন মনে করি, আমি লাইনগুলি একটি ব্যক্তিগত পদ্ধতিতে সরিয়ে নিয়েছি এবং ল্যাম্বদার পরিবর্তে পদ্ধতির রেফারেন্সটি পাস করি।
ভিজিআর

1
কি অভিপ্রায় - আপনি সমস্ত অ্যাকাউন্ট লগ ইন করার চেষ্টা করছেন এবং যদি তারা লগইন করেন তাহলেও তাদের ফিল্টার উপর ভিত্তি করে (জাভাস্ক্রিপ্টে গার্বেজ সত্য হতে পারে)? অথবা, আপনি কি তাদের লগ ইন করতে চান, তারপরে তারা লগ ইন করেছেন কিনা তার ভিত্তিতে ফিল্টার করুন? আমি এই ক্রমে এটি জিজ্ঞাসা করছি কারণ forEachআপনি যে অপারেশনটির বিপরীতে চান তা হতে পারে peek। এটি এপিআই-তে রয়েছে বলেই এর অর্থ এটি অপব্যবহারের জন্য উন্মুক্ত নয় (যেমন Optional.of)।
মাকোটো

8
এছাড়াও নোট করুন যে আপনার কোডটি কেবল .peek(Account::login)এবং হতে পারে .filter(Account::loggedIn); কোনও গ্রাহক এবং ভবিষ্যদ্বাণী লেখার কোনও কারণ নেই যা কেবল এই জাতীয় পদ্ধতিটিকে কল করে।
জোশুয়া টেলর

2
এছাড়াও মনে রাখবেন প্রবাহ এপিআই স্পষ্টভাবে নিরুৎসাহিত পার্শ্ব প্রতিক্রিয়া মধ্যে আচরণগত পরামিতি
দিদিয়ের এল

6
দরকারী গ্রাহকদের সর্বদা পার্শ্ব-প্রতিক্রিয়া থাকে, এগুলি অবশ্যই নিরুৎসাহিত হয় না। এটি প্রকৃতপক্ষে একই বিভাগে উল্লেখ করা হয়েছে: " স্বল্প সংখ্যক স্ট্রিম অপারেশন যেমন forEach()এবং peek()কেবলমাত্র পার্শ্ব-প্রতিক্রিয়া দ্বারা পরিচালিত হতে পারে; এগুলি যত্ন সহকারে ব্যবহার করা উচিত। "। আমার মন্তব্য আরো মনে করিয়ে দিতে ছিল peekঅপারেশন (যা ডিবাগ উদ্দেশ্যে জন্য ডিজাইন করা হয়েছে) মত অন্য অপারেশন ভিতরে একই জিনিস করছেন দ্বারা প্রতিস্থাপিত করা উচিত নয় map()বা filter()
দিদিয়ের এল

উত্তর:


77

এটি থেকে মূল গ্রহণযোগ্যতা:

আপনার তাত্ক্ষণিক লক্ষ্যটি সম্পাদন করে এমনকি অনিচ্ছাকৃতভাবে এপিআই ব্যবহার করবেন না। ভবিষ্যতে এই পদ্ধতিটি ভেঙে যেতে পারে এবং এটি ভবিষ্যতের রক্ষণাবেক্ষণকারীদের কাছেও অস্পষ্ট।


এটি পৃথক ক্রিয়াকলাপ হওয়ায় একাধিক ক্রিয়াকলাপে এটি ভাঙ্গার কোনও ক্ষতি নেই। সেখানে হয় একটি অস্পষ্ট এবং অনিচ্ছাকৃত ভাবে, যা গার্গল থাকতে পারে যদি এই বিশেষ আচরণ জাভা ভবিষ্যত সংস্করণে রুপান্তরিত করা হয়েছে উপরে API ব্যবহার করে ক্ষতি।

forEachএই অপারেশনটি ব্যবহার করে রক্ষণাবেক্ষণকারীদের কাছে এটি পরিষ্কার হয়ে যাবে যে প্রতিটি উপাদানের একটি উদ্দেশ্যমূলক পার্শ্ব প্রতিক্রিয়া রয়েছে accountsএবং আপনি এমন কিছু অপারেশন করছেন যা এটির পরিবর্তন করতে পারে।

এটি এই অর্থে আরও প্রচলিত যে peekএটি একটি মধ্যবর্তী অপারেশন যা টার্মিনাল অপারেশনটি সঞ্চালিত না হওয়া পর্যন্ত পুরো সংগ্রহের উপর পরিচালিত হয় না, তবে forEachপ্রকৃতপক্ষে এটি একটি টার্মিনাল অপারেশন। এই পদ্ধতিতে, আপনি এই আচরণের বিষয়ে এবং আপনার কোডের প্রবাহকে এই প্রসঙ্গে peekযেমন আচরণ করে তবে সেভাবে আচরণ করবে কিনা এমন প্রশ্ন জিজ্ঞাসার বিপরীতে দৃ strong় যুক্তি তৈরি করতে পারেন forEach

accounts.forEach(a -> a.login());
List<Account> loggedInAccounts = accounts.stream()
                                         .filter(Account::loggedIn)
                                         .collect(Collectors.toList());

3
আপনি যদি পূর্বনির্ধারিত পদক্ষেপে লগইনটি সম্পাদন করেন তবে আপনার কোনও স্ট্রিম লাগবে না। আপনি forEachউত্স সংগ্রহটিতে সরাসরি পারফর্ম করতে পারেন :accounts.forEach(a -> a.login());
হোলার

1
@ হোলার: দুর্দান্ত পয়েন্ট। আমি এটি উত্তরে অন্তর্ভুক্ত করেছি।
মাকোটো

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

2
অবশ্যই, login()পদ্ধতিটি booleanসাফল্যের স্থিতি নির্দেশ করে এমন কোনও মান ফিরিয়ে দিলে এটি অনেক সহজ ছিল ...
হোলার

3
এটাই আমি লক্ষ্য করেছিলাম। যদি login()কোনওটি ফেরত দেয় তবে booleanআপনি এটিকে প্রাকটিক হিসাবে ব্যবহার করতে পারেন যা সবচেয়ে পরিষ্কার সমাধান। এটা এখনও একটি পার্শ্ব প্রতিক্রিয়া আছে, কিন্তু যে যতদিন এটি নন-হস্তক্ষেপ হিসাবে ঠিক আছে, অর্থাত loginএক process` Accountঅন্য login` process` উপর কোন প্রভাব আছে Account
হলগার

111

আপনার যে গুরুত্বপূর্ণ জিনিসটি বুঝতে হবে তা হ'ল স্ট্রিমগুলি টার্মিনাল ক্রিয়াকলাপ দ্বারা চালিত । টার্মিনাল ক্রিয়াকলাপ নির্ধারণ করে যে সমস্ত উপাদানকে প্রক্রিয়া করতে হবে বা আদৌ কোনও। সুতরাং collectএকটি অপারেশন যে প্রতিটি আইটেমের প্রক্রিয়া, যেহেতু হয় findAnyপ্রক্রিয়াকরণের আইটেম বন্ধ করতে পারে একবার এটি একটি মানানসই উপাদান সম্মুখীন হয়েছে।

এবং count()আইটেমগুলি প্রক্রিয়াজাতকরণ না করে কোনও স্ট্রিমের আকার নির্ধারণ করতে পারে এমন কোনও উপাদানকে প্রক্রিয়াজাত করতে পারে না। যেহেতু এটি জাভা 8-এ তৈরি করা নয়, তবে এটি জাভা 9-এ তৈরি করা হয়েছে, আপনি যখন জাভা 9-এ স্যুইচ করেন এবং count()সমস্ত আইটেম প্রক্রিয়াকরণের উপর কোড নির্ভর করে তখন অবাক হতে পারে । এটি অন্যান্য বাস্তবায়ন-নির্ভর বিবরণগুলির সাথেও যুক্ত, যেমন জাভা 9-তে, রেফারেন্স বাস্তবায়ন এমন একটি অনন্ত স্ট্রিম উত্সের আকারের পূর্বাভাস দিতে সক্ষম হবে না limitযখন এমন পূর্বাভাস রোধ করার কোনও মৌলিক সীমাবদ্ধতা নেই।

যেহেতু peek" ফলাফলটি প্রবাহ থেকে উপাদানগুলি হ'ল প্রতিটি উপাদানের উপর সরবরাহিত ক্রিয়া সম্পাদন করার অনুমতি দেয়" সুতরাং এটি উপাদানগুলির প্রক্রিয়াজাতকরণের আদেশ দেয় না তবে টার্মিনাল অপারেশনটির প্রয়োজনীয়তার উপর নির্ভর করে ক্রিয়াটি সম্পাদন করবে। এটি সূচিত করে যে আপনার যদি কোনও নির্দিষ্ট প্রক্রিয়াজাতকরণ প্রয়োজন হয় তবে আপনাকে খুব যত্ন সহকারে এটি ব্যবহার করতে হবে, উদাহরণস্বরূপ সমস্ত উপাদানগুলির উপর একটি ক্রিয়া প্রয়োগ করতে চান। টার্মিনাল অপারেশনটি সমস্ত আইটেম প্রক্রিয়া করার গ্যারান্টিযুক্ত থাকলে এটি কাজ করে তবে তারপরেও আপনাকে অবশ্যই নিশ্চিত হতে হবে যে পরবর্তী বিকাশকারী টার্মিনাল অপারেশনটি পরিবর্তন করে না (বা আপনি সেই সূক্ষ্ম দিকটি ভুলে যান)।

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

সুতরাং আপনি যে সর্বাধিক দরকারী জিনিসটি করতে পারেন তা peekহ'ল কোনও স্ট্রিম উপাদানটি প্রক্রিয়াজাত করা হয়েছে কিনা তা যা এপিআই ডকুমেন্টেশন হ'ল:

এই পদ্ধতিটি মূলত ডিবাগিং সমর্থন করার জন্য উপস্থিত রয়েছে, যেখানে আপনি উপাদানগুলি পাইপলাইনের একটি নির্দিষ্ট বিন্দু পেরিয়ে যাওয়ার সময় দেখতে চান


ওপির ব্যবহার ক্ষেত্রে ভবিষ্যত বা বর্তমানের কোন সমস্যা আছে কি? তার কোড কি সর্বদা সে যা চায় তাই করে?
ঝংইউ

9
@ বায়ো.ইও: যতদূর আমি দেখতে পাচ্ছি, এই সঠিক ফর্মটিতে কোনও সমস্যা নেই । তবে আমি যেমন ব্যাখ্যা করার চেষ্টা করেছি, এটিকে এভাবে ব্যবহার করে বোঝা যাচ্ছে আপনি এই দিকটি মনে রাখতে হবে, এমনকি যদি আপনি এক বা দু'বছর পরে কোডটিতে «বৈশিষ্ট্য অনুরোধ 9876 to অন্তর্ভুক্ত করার জন্য কোডটিতে ফিরে আসেন ...
হোলার

1
"উঁকি দেওয়া ক্রিয়াটি একটি স্বেচ্ছাসেবী ক্রমে এবং একই সাথে আহ্বান করতে পারে"। এই বিবৃতিটি কীভাবে উঁকি দেয় কাজ করে, যেমন "উপাদানগুলি গ্রাস করা হয়" এর বিপরীতে যায় না?
জোসে মার্টিনেজ

5
@ জোস মার্টিনেজ: এটি বলেছে যেহেতু " ফলাফলগুলি প্রবাহ থেকে উপাদানগুলি গ্রাস করা হয় ", এটি টার্মিনাল ক্রিয়া নয় তবে প্রক্রিয়াজাতকরণ, যদিও চূড়ান্ত ফলাফলটি সামঞ্জস্যতা হওয়া সত্ত্বেও টার্মিনাল ক্রিয়া উপাদানগুলিকে বিন্যস্ত করতে পারে। তবে আমি আরও মনে করি, এপিআই নোটের বাক্যাংশটি, " উপাদানগুলি একটি পাইপলাইনের একটি নির্দিষ্ট পয়েন্ট পেরিয়ে যাওয়ার সাথে সাথে দেখুন " এটি বর্ণনা করার ক্ষেত্রে আরও ভাল কাজ করে।
হলগার

23

সম্ভবত থাম্বের একটি নিয়ম হ'ল আপনি যদি "ডিবাগ" দৃশ্যের বাইরে উঁকি ব্যবহার করেন তবে আপনার কেবলমাত্র তাই করা উচিত যদি আপনি নিশ্চিত হন যে সমাপনী এবং মধ্যবর্তী ফিল্টারিং শর্তগুলি কি। উদাহরণ স্বরূপ:

return list.stream().map(foo->foo.getBar())
                    .peek(bar->bar.publish("HELLO"))
                    .collect(Collectors.toList());

আপনি যেখানে চান সেখানে একটি বৈধ কেস বলে মনে হচ্ছে, সমস্ত ফুগুলিকে বারে রূপান্তর করতে এবং তাদের সকলকে হ্যালো বলার জন্য একটি অপারেশনে।

এর মতো কিছু থেকে আরও দক্ষ এবং মার্জিত বলে মনে হচ্ছে:

List<Bar> bars = list.stream().map(foo->foo.getBar()).collect(Collectors.toList());
bars.forEach(bar->bar.publish("HELLO"));
return bars;

এবং আপনি দুটি বার সংগ্রহের পুনরাবৃত্তি শেষ করবেন না।


4

আমি বলব যে কোডটি বিকেন্দ্রীকরণেরpeek দক্ষতা সরবরাহ করে যা স্ট্রিম অবজেক্টগুলিকে রূপান্তর করতে পারে বা গ্লোবাল স্টেটকে (তাদের উপর ভিত্তি করে) পরিবর্তন করতে পারে , পরিবর্তে সবকিছুকে একটি সাধারণ বা রচিত ফাংশনটিতে টার্মিনাল পদ্ধতিতে পাস করার পরিবর্তে।

এখন প্রশ্ন হতে পারে: আমাদের কী স্ট্রিম অবজেক্টগুলিকে রূপান্তর করতে হবে বা ফাংশনাল স্টাইল জাভা প্রোগ্রামিংয়ের মধ্য থেকে বৈশ্বিক অবস্থা পরিবর্তন করা উচিত ?

যদি উপরোক্ত 2 প্রশ্নের কোন উত্তর হ্যাঁ (বা: কিছু কিছু ক্ষেত্রে হ্যাঁ মধ্যে) তারপর peek()হয় স্পষ্টভাবে না শুধুমাত্র ডিবাগ করার উদ্দেশ্যে জন্য , একই কারণে যে জন্য forEach()নয় শুধুমাত্র ডিবাগ করার উদ্দেশ্যে জন্য

আমার মধ্যে forEach()এবং এর মধ্যে নির্বাচন peek()করার সময়, নিম্নলিখিতগুলি নির্বাচন করা হয়: আমি কী কোডের টুকরো চাই যে রূপান্তরিত স্ট্রিমের অবজেক্টগুলিকে কোনও কমপোজেবলের সাথে সংযুক্ত করা যায়, বা আমি তাদের সরাসরি প্রবাহে সংযুক্ত করতে চাই?

আমি মনে করি peek()জাভা 9 পদ্ধতির সাথে আরও ভাল জুড়ি দেবে। উদাহরণস্বরূপ takeWhile(), ইতিমধ্যে পরিবর্তিত কোন অবজেক্টের উপর ভিত্তি করে পুনরাবৃত্তি কখন থামানো উচিত তা সিদ্ধান্ত নেওয়া দরকার, সুতরাং এটির সাথে ভাগ forEach()করা একই প্রভাব ফেলবে না।

PS আমি map()কোথাও রেফারেন্স পাইনি কারণ আমরা নতুন অবজেক্ট তৈরির পরিবর্তে অবজেক্টস (বা গ্লোবাল স্টেট) কে রূপান্তর করতে চাই, এটি ঠিক মত কাজ করে peek()


3

যদিও আমি উপরের বেশিরভাগ উত্তরের সাথে একমত, আমার কাছে একটি মামলা রয়েছে যাতে পিক ব্যবহার করা আসলে সবচেয়ে পরিষ্কার উপায় বলে মনে হয়।

আপনার ব্যবহারের ক্ষেত্রে অনুরূপ, মনে করুন আপনি কেবল সক্রিয় অ্যাকাউন্টগুলিতে ফিল্টার করতে চান এবং তারপরে এই অ্যাকাউন্টগুলিতে একটি লগইন সম্পাদন করতে চান।

accounts.stream()
    .filter(Account::isActive)
    .peek(login)
    .collect(Collectors.toList());

সংগ্রহে দু'বার পুনরাবৃত্তি না করার সময় রিডানড্যান্ট কল এড়ানোর জন্য পিক সহায়ক:

accounts.stream()
    .filter(Account::isActive)
    .map(account -> {
        account.login();
        return account;
    })
    .collect(Collectors.toList());

3
আপনাকে যা করতে হবে তা হ'ল লগইন পদ্ধতিটি সঠিকভাবে পাওয়া। আমি সত্যিই দেখতে পাচ্ছি না যে উঁকি দেওয়া কীভাবে পরিষ্কার উপায়। আপনার কোডটি পড়ছেন এমন কেউ কীভাবে জানেন যে আপনি আসলে এপিআইর অপব্যবহার করছেন। ভাল এবং পরিষ্কার কোড একটি পাঠককে কোড সম্পর্কে অনুমান করতে বাধ্য করে না।
কাবা 713

1

কার্যকরী সমাধান হ'ল অ্যাকাউন্টকে অপরিবর্তনীয় করে তোলা। সুতরাং অ্যাকাউন্ট.লগিন () অবশ্যই একটি নতুন অ্যাকাউন্ট অবজেক্ট ফিরিয়ে দিতে হবে। এর অর্থ হ'ল মানচিত্র অপারেশনটি উঁকি দেওয়ার পরিবর্তে লগইনের জন্য ব্যবহার করা যেতে পারে।

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