জাভা 8 - একটি তালিকা রূপান্তর করার সর্বোত্তম উপায়: মানচিত্র বা ভবিষ্যদ্বাণী?


188

আমার একটি তালিকা রয়েছে myListToParseযেখানে আমি উপাদানগুলিকে ফিল্টার করতে এবং প্রতিটি উপাদানটিতে একটি পদ্ধতি প্রয়োগ করতে এবং ফলাফলটিকে অন্য তালিকায় যুক্ত করতে চাই myFinalList

জাভা 8 এর সাথে আমি লক্ষ্য করেছি যে আমি এটি 2 টি বিভিন্ন উপায়ে করতে পারি। আমি তাদের মধ্যে আরও কার্যকর উপায় জানতে এবং কেন একটি উপায় অন্য পথের চেয়ে ভাল তা জানতে চাই।

আমি তৃতীয় উপায় সম্পর্কে কোনও পরামর্শের জন্য উন্মুক্ত।

পদ্ধতি 1:

myFinalList = new ArrayList<>();
myListToParse.stream()
        .filter(elt -> elt != null)
        .forEach(elt -> myFinalList.add(doSomething(elt)));

পদ্ধতি 2:

myFinalList = myListToParse.stream()
        .filter(elt -> elt != null)
        .map(elt -> doSomething(elt))
        .collect(Collectors.toList()); 

55
দ্বিতীয়টি। একটি সঠিক ফাংশনটির কোনও পার্শ্ব প্রতিক্রিয়া থাকতে হবে না, আপনার প্রথম প্রয়োগে আপনি বাহ্যিক বিশ্বের পরিবর্তন করছেন।
ধন্যবাদফোরআলটফিশ

37
কেবল স্টাইলের বিষয়, তবে elt -> elt != nullObjects::nonNull
এটির মাধ্যমে

2
@ the8472 এর চেয়ে আরও ভাল হবে যে সংগ্রহের মধ্যে প্রথমে কোনও নাল মান নেই এবং এটির Optional<T>পরিবর্তে এর সাথে মিলিয়ে ব্যবহার করুন flatMap
Herman

2
পছন্দ করেছেন এর চেয়ে তুচ্ছ কোনও কিছুর জন্য হুডের নীচে সমান্তরাল প্রবাহ সেটআপ করার জন্য কাজটি এই নির্মাণকে নিঃশব্দ ব্যবহার করে তুলবে।
এমকে

2
নোট করুন যে আপনি এটি .map(this::doSomething)ধরে রেখে লিখতে পারেন যে doSomethingএটি একটি অ স্থিতিকাল পদ্ধতি। যদি এটি স্থির থাকে তবে আপনি thisশ্রেণীর নাম দিয়ে প্রতিস্থাপন করতে পারেন ।
Herman

উত্তর:


153

কোনও পারফরম্যান্সের পার্থক্য সম্পর্কে চিন্তা করবেন না, তারা সাধারণত এই ক্ষেত্রে ন্যূনতম হতে চলেছেন।

পদ্ধতি 2 কারণ ভাল

  1. এটিতে ল্যাম্বডা এক্সপ্রেশনের বাইরে থাকা একটি সংগ্রহকে রূপান্তর করতে হবে না,

  2. এটি আরও পঠনযোগ্য কারণ সংগ্রহ পাইপলাইনে সম্পাদিত বিভিন্ন পদক্ষেপগুলি ধারাবাহিকভাবে লেখা হয়: প্রথমে একটি ফিল্টার অপারেশন, তারপরে একটি মানচিত্র পরিচালনা, তারপরে ফলাফল সংগ্রহ করা (সংগ্রহ পাইপলাইনের সুবিধা সম্পর্কে আরও তথ্যের জন্য, মার্টিন ফোলারের দুর্দান্ত নিবন্ধটি দেখুন ),

  3. মানগুলি যেভাবে Collectorব্যবহৃত হয় তা প্রতিস্থাপনের মাধ্যমে আপনি সহজেই পরিবর্তন করতে পারবেন । কিছু ক্ষেত্রে আপনার নিজের লেখার প্রয়োজন হতে পারে Collectorতবে তার পরে সুবিধাটি হ'ল আপনি সহজেই এটি পুনরায় ব্যবহার করতে পারেন।


43

আমি বিদ্যমান উত্তরের সাথে একমত যে দ্বিতীয় ফর্মটি ভাল কারণ এর কোনও পার্শ্ব প্রতিক্রিয়া নেই এবং সমান্তরাল করা সহজ (কেবলমাত্র একটি সমান্তরাল প্রবাহ ব্যবহার করুন)।

পারফরম্যান্স অনুসারে, আপনি সমান্তরাল স্ট্রিমগুলি ব্যবহার শুরু না করা পর্যন্ত এগুলি সমতুল্য। সেক্ষেত্রে মানচিত্রটি আরও অনেক ভাল পারফর্ম করবে। মাইক্রো মানদণ্ডের ফলাফলগুলি নীচে দেখুন :

Benchmark                         Mode  Samples    Score   Error  Units
SO28319064.forEach                avgt      100  187.310 ± 1.768  ms/op
SO28319064.map                    avgt      100  189.180 ± 1.692  ms/op
SO28319064.mapWithParallelStream  avgt      100   55,577 ± 0,782  ms/op

আপনি একই পদ্ধতিতে প্রথম উদাহরণটি উত্সাহিত করতে পারবেন না কারণ প্রত্যেকটি একটি টার্মিনাল পদ্ধতি - এটি শূন্য হয়ে যায় - সুতরাং আপনাকে রাষ্ট্রীয় ল্যাম্বডা ব্যবহার করতে বাধ্য করা হবে। কিন্তু যে সত্যিই একটি খারাপ ধারণা যদি সমান্তরাল স্ট্রিম ব্যবহার করা হয়

পরিশেষে মনে রাখবেন যে আপনার দ্বিতীয় স্নিপেটটি পদ্ধতি রেফারেন্স এবং স্থিতিশীল আমদানির সাথে খুব স্বচ্ছলভাবে লেখা যেতে পারে:

myFinalList = myListToParse.stream()
    .filter(Objects::nonNull)
    .map(this::doSomething)
    .collect(toList()); 

1
পারফরম্যান্স সম্পর্কে, যদি আপনি সমান্তরাল স্ট্রিম ব্যবহার করেন তবে আপনার ক্ষেত্রে "মানচিত্র" সত্যই "forEach" এর উপর জয়ী হবে। আমার বেঞ্চম্যাকসটি মিলি সেকেন্ডে: SO28319064.forEach: 187,310 ± 1,768 এমএস / ওপেন - SO28319064.map: 189,180 ± 1,692 এমএস / অপ - এসও 28319064.map সমান্তরাল স্ট্রিম: 55,577 ± 0,782 এমএস / অপ
জিউসেপ

2
@ জিউসেপ্পে বার্টোন, এটি অ্যাসিলিয়াসের উপর নির্ভর করে, তবে আমার মতে আপনার সম্পাদনাটি মূল লেখকের অভিপ্রায়ের বিরোধিতা করে। আপনি যদি নিজের উত্তর যুক্ত করতে চান তবে বিদ্যমানটিকে এত বেশি সম্পাদনা করার পরিবর্তে এটি যুক্ত করা ভাল। এছাড়াও এখন মাইক্রোবেঞ্চমার্কের লিঙ্কটি ফলাফলের সাথে প্রাসঙ্গিক নয়।
তাগীর ভালিভ

5

স্ট্রিমগুলি ব্যবহারের অন্যতম প্রধান সুবিধা হ'ল এটি একটি ঘোষণামূলক উপায়ে ডেটা প্রক্রিয়া করার ক্ষমতা দেয় যা প্রোগ্রামিংয়ের কার্যকরী শৈলী ব্যবহার করে। এটি নিখরচায় মাল্টি-থ্রেডিংয়ের ক্ষমতাও দেয় যার অর্থ আপনার স্ট্রিমকে একই সাথে তৈরি করতে কোনও অতিরিক্ত মাল্টি-থ্রেডেড কোড লেখার দরকার নেই।

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

দ্বিতীয় উপায়টি কার্যকরী প্রোগ্রামিং দৃষ্টিকোণ থেকে পছন্দ করা হয় যেহেতু মানচিত্রের ফাংশনটি স্টেটলেস ল্যাম্বদা ফাংশন গ্রহণ করতে পারে। আরও স্পষ্টভাবে, ল্যাম্বদা মানচিত্রের কার্যক্রমে পাস হওয়া উচিত

  1. হস্তক্ষেপ না করা, অর্থাত্ ফাংশনটি স্ট্রিমের উত্সকে পরিবর্তন না করা উচিত যদি এটি অ-সমবর্তী হয় (যেমন ArrayList)।
  2. সমান্তরাল প্রক্রিয়াকরণ করার সময় অপ্রত্যাশিত ফলাফল এড়াতে রাষ্ট্রহীন (থ্রেড শিডিংয়ের পার্থক্যজনিত কারণে)।

দ্বিতীয় পদ্ধতির সাথে আরেকটি সুবিধা হ'ল যদি স্ট্রিমটি সমান্তরাল হয় এবং সংগ্রাহক একযোগে এবং আনর্ডারড হয় তবে এই বৈশিষ্ট্যগুলি একসাথে সংগ্রহের জন্য হ্রাস অপারেশনকে দরকারী ইঙ্গিত প্রদান করতে পারে।


4

আপনি যদি গ্রিপস সংগ্রহগুলি ব্যবহার করেন তবে আপনি collectIf()পদ্ধতিটি ব্যবহার করতে পারেন ।

MutableList<Integer> source =
    Lists.mutable.with(1, null, 2, null, 3, null, 4, null, 5);

MutableList<String> result = source.collectIf(Objects::nonNull, String::valueOf);

Assert.assertEquals(Lists.immutable.with("1", "2", "3", "4", "5"), result);

এটি আগ্রহের সাথে মূল্যায়ন করে এবং স্ট্রিম ব্যবহারের চেয়ে কিছুটা দ্রুত হওয়া উচিত।

দ্রষ্টব্য: আমি গ্রহগ্রাহ সংগ্রহের প্রতিশ্রুতিবদ্ধ।


1

আমি দ্বিতীয় উপায় পছন্দ।

আপনি যখন প্রথম উপায়ে ব্যবহার করেন, আপনি যদি পারফরম্যান্সের উন্নতির জন্য একটি সমান্তরাল স্ট্রিম ব্যবহার করার সিদ্ধান্ত নেন, তখন উপাদানগুলি আউটপুট তালিকায় যুক্ত হবে এমন ক্রমের উপর আপনার কোনও নিয়ন্ত্রণ থাকবে না forEach

আপনি যখন ব্যবহার করবেন toList, আপনি একটি সমান্তরাল স্ট্রিম ব্যবহার করলেও স্ট্রিমস এপিআই অর্ডার সংরক্ষণ করবে।


আমি নিশ্চিত না যে এটি সঠিক পরামর্শ: তিনি যদি কোনও প্যারালাল স্ট্রিম ব্যবহার করতে চান তবে তারপরে ক্রমটি সংরক্ষণ করে রাখার forEachOrderedপরিবর্তে forEachতিনি ব্যবহার করতে পারেন। তবে forEachরাজ্যগুলির দলিল হিসাবে , এনকাউন্টার অর্ডার সংরক্ষণ করা সমান্তরালতার সুবিধাটিকে ত্যাগ করে। আমি সন্দেহ করি যে toListতখনকার ক্ষেত্রেও এটি ছিল ।
হারমান

0

একটি তৃতীয় বিকল্প রয়েছে - ব্যবহার করে stream().toArray()- কেন স্ট্রিমের টোললিস্ট পদ্ধতি নেই তার অধীনে মন্তব্যগুলি দেখুন । এটি forEach () বা সংগ্রহ () সংগ্রহের চেয়ে ধীরে ধীরে এবং কম অভিব্যক্তিপূর্ণ হিসাবে দেখা দেয়। এটি পরে জেডিকে তৈরিতে অনুকূলিত হতে পারে, তাই এটি কেবল ক্ষেত্রে এখানে যুক্ত করুন।

অভিমানী List<String>

    myFinalList = Arrays.asList(
            myListToParse.stream()
                    .filter(Objects::nonNull)
                    .map(this::doSomething)
                    .toArray(String[]::new)
    );

একটি মাইক্রো মাইক্রো বেঞ্চমার্ক, 1 এম এন্ট্রি, 20% নাল এবং ডসোমেটিং-এ সরল রূপান্তর সহ ()

private LongSummaryStatistics benchmark(final String testName, final Runnable methodToTest, int samples) {
    long[] timing = new long[samples];
    for (int i = 0; i < samples; i++) {
        long start = System.currentTimeMillis();
        methodToTest.run();
        timing[i] = System.currentTimeMillis() - start;
    }
    final LongSummaryStatistics stats = Arrays.stream(timing).summaryStatistics();
    System.out.println(testName + ": " + stats);
    return stats;
}

ফলাফল হয়

সমান্তরাল:

toArray: LongSummaryStatistics{count=10, sum=3721, min=321, average=372,100000, max=535}
forEach: LongSummaryStatistics{count=10, sum=3502, min=249, average=350,200000, max=389}
collect: LongSummaryStatistics{count=10, sum=3325, min=265, average=332,500000, max=368}

অনুক্রমিক:

toArray: LongSummaryStatistics{count=10, sum=5493, min=517, average=549,300000, max=569}
forEach: LongSummaryStatistics{count=10, sum=5316, min=427, average=531,600000, max=571}
collect: LongSummaryStatistics{count=10, sum=5380, min=444, average=538,000000, max=557}

SIZEDনাল এবং ফিল্টার ছাড়াই সমান্তরাল (সুতরাং স্ট্রিমটি হ'ল ) টু অ্যারাইস এর ক্ষেত্রে সেরা পারফরম্যান্স পেয়েছে এবং .forEach()গ্রাহক অ্যারেলিস্টে "ইনডেক্সআউটআউটবাউন্ডস" এর সাথে ব্যর্থ হয়, তার সাথে প্রতিস্থাপন করতে হয়েছিল.forEachOrdered()

toArray: LongSummaryStatistics{count=100, sum=75566, min=707, average=755,660000, max=1107}
forEach: LongSummaryStatistics{count=100, sum=115802, min=992, average=1158,020000, max=1254}
collect: LongSummaryStatistics{count=100, sum=88415, min=732, average=884,150000, max=1014}

0

পদ্ধতি 3 হতে পারে।

আমি সর্বদা যুক্তি আলাদা রাখতে পছন্দ করি।

Predicate<Long> greaterThan100 = new Predicate<Long>() {
            @Override
            public boolean test(Long currentParameter) {
                return currentParameter > 100;
            }
        };

        List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
        List<Long> resultList = sourceLongList.parallelStream().filter(greaterThan100).collect(Collectors.toList());

0

যদি তৃতীয় পরিশ্রমী লাইব্রেরি ব্যবহার করা ঠিক থাকে তবে সাইক্লোপস- রিঅ্যাক্টটি এই কার্যকারিতাটির সাথে অলস প্রসারিত সংগ্রহগুলি সংজ্ঞায়িত করে example উদাহরণস্বরূপ আমরা কেবল লিখতে পারি

লিস্টএক্স মাইলিস্টটোপার্স;

ListX myFinalList = myListToParse.filter (elt -> elt! = Null) .map (elt -> doSomething (elt));

মাইফিনাল তালিকাটি প্রথম অ্যাক্সেস না হওয়া পর্যন্ত মূল্যায়ন করা হয় না (এবং সেখানে বস্তুগত তালিকাটি ক্যাশেড এবং পুনরায় ব্যবহারের পরে)।

[প্রকাশ আমি সাইক্লোপস-রিএ্যাক্টের প্রধান বিকাশকারী]

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