জাভা 8-এ অকার্যকর (অকার্যকর নয়) পদ্ধতির জন্য কীভাবে ফাংশন প্রকার নির্দিষ্ট করতে হবে?


143

আমি প্রথম শ্রেণির নাগরিক হিসাবে কীভাবে কাজ করে তা জানার জন্য আমি জাভা 8 এর সাথে ঘুরেছি। আমার কাছে নিম্নলিখিত স্নিপেট রয়েছে:

package test;

import java.util.*;
import java.util.function.*;

public class Test {

    public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
      list.forEach(functionToBlock(myFunction));
    }

    public static void displayInt(Integer i) {
      System.out.println(i);
    }


    public static void main(String[] args) {
      List<Integer> theList = new ArrayList<>();
      theList.add(1);
      theList.add(2);
      theList.add(3);
      theList.add(4);
      theList.add(5);
      theList.add(6);
      myForEach(theList, Test::displayInt);
    }
}

আমি যা করার চেষ্টা করছি তা হল একটি পদ্ধতি রেফারেন্স ব্যবহার করে displayIntপদ্ধতিতে পাস করা method myForEachসংকলক করতে নিম্নলিখিত ত্রুটি উত্পন্ন করে:

src/test/Test.java:9: error: cannot find symbol
      list.forEach(functionToBlock(myFunction));
                   ^
  symbol:   method functionToBlock(Function<Integer,Void>)
  location: class Test
src/test/Test.java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
      myForEach(theList, Test::displayInt);
      ^
  required: List<Integer>,Function<Integer,Void>
  found: List<Integer>,Test::displayInt
  reason: argument mismatch; bad return type in method reference
      void cannot be converted to Void

সংকলক অভিযোগ করে যে void cannot be converted to VoidmyForEachকোড কম্পাইল করে এমন স্বাক্ষরে ফাংশন ইন্টারফেসের ধরণটি কীভাবে নির্দিষ্ট করতে হয় তা আমি জানি না । আমি জানি যে আমি কেবল ফিরে আসার প্রকার পরিবর্তন displayIntকরতে পারি Voidএবং তারপরে ফিরে আসি null। তবে এমন পরিস্থিতি থাকতে পারে যেখানে আমি অন্য কোথাও যে পদ্ধতিতে যেতে চাই তার পদ্ধতিটি পরিবর্তন করা সম্ভব নয়। displayIntএটির মতো পুনঃব্যবহার করার কোনও সহজ উপায় আছে ?

উত্তর:


244

আপনি ভুল ইন্টারফেস টাইপ ব্যবহার করার চেষ্টা করছেন। টাইপ ফাংশন কারণ এটি একটি প্যারামিটার পায় এবং একটি ফিরতি মূল্য আছে এই ক্ষেত্রে উপযুক্ত নয়। পরিবর্তে আপনার ভোক্তা ব্যবহার করা উচিত (পূর্বে ব্লক হিসাবে পরিচিত)

ফাংশন টাইপ হিসাবে হিসাবে ঘোষণা করা হয়

interface Function<T,R> {
  R apply(T t);
}

তবে, আপনি যে সন্ধান করছেন তার সাথে গ্রাহক প্রকারটি সামঞ্জস্যপূর্ণ:

interface Consumer<T> {
   void accept(T t);
}

যেমন, ভোক্তা এমন পদ্ধতির সাথে সামঞ্জস্যপূর্ণ যা একটি টি পায় এবং কোনও কিছুই শূন্য করে না (বাতিল হয় না)। এবং এই আপনি চান কি।

উদাহরণস্বরূপ, যদি আমি একটি তালিকায় সমস্ত উপাদান প্রদর্শন করতে চাই তবে আমি কেবল ল্যাম্বডা এক্সপ্রেশন দিয়ে এর জন্য একটি গ্রাহক তৈরি করতে পারতাম:

List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );

আপনি উপরে দেখতে পারেন যে এই ক্ষেত্রে, ল্যাম্বডা এক্সপ্রেশন একটি প্যারামিটার গ্রহণ করে এবং তার কোনও ফেরতের মান নেই।

এখন, আমি যদি এই জাতীয় ব্যবহারের জন্য ল্যাম্বডা এক্সপ্রেশনের পরিবর্তে কোনও পদ্ধতি রেফারেন্স ব্যবহার করতে চাইতাম তবে আমার এমন একটি পদ্ধতি দরকার যা একটি স্ট্রিং গ্রহণ করে এবং বাতিল করে দেয়, ঠিক?

আমি পদ্ধতি রেফারেন্স বিভিন্ন ধরনের ব্যবহার করতে পারে, কিন্তু এই ক্ষেত্রে ব্যবহার করে একটি বস্তু পদ্ধতি রেফারেন্স এর সুবিধা নিয়ে যাক printlnপদ্ধতি System.outবস্তুর, এভাবে:

Consumer<String> block = System.out::println

বা আমি সহজভাবে করতে পারে

allJedi.forEach(System.out::println);

printlnকারণ এটি একটি মান গ্রহণ করে এবং একটি রিটার্ন টাইপ অকার্যকর হয়েছে, ঠিক পদ্ধতি উপযুক্ত acceptকনজিউমার মধ্যে পদ্ধতি।

সুতরাং, আপনার কোডে আপনার পদ্ধতির স্বাক্ষরটি কিছুটা এইরকম পরিবর্তন করতে হবে:

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}

এবং তারপরে আপনার ক্ষেত্রে স্থির পদ্ধতি রেফারেন্স ব্যবহার করে একটি ভোক্তা তৈরি করতে সক্ষম হবেন:

myForEach(theList, Test::displayInt);

শেষ পর্যন্ত, আপনি এমনকি আপনার myForEachপদ্ধতি পুরোপুরি পরিত্রাণ পেতে এবং সহজভাবে করতে পারে:

theList.forEach(Test::displayInt);

প্রথম শ্রেণির নাগরিক হিসাবে কাজ সম্পর্কে

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


1
যাইহোক, BlockConsumer
জেডিকে

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

7
আর সঠিক সংস্করণ Function<Void, Void>হয় Runnable
অরেঞ্জডোগ

2
@ ওরেঞ্জডগ এটি সম্পূর্ণ সত্য নয়। মন্তব্যে Runnableএবং Callableইন্টারফেসে এটি লেখা আছে যে তারা থ্রেডের সাথে একত্রে ব্যবহৃত হবে বলে মনে করা হচ্ছে । এর বিরক্তিকর পরিণতি হ'ল কিছু স্থির বিশ্লেষণ সরঞ্জাম (সোনারের মতো) আপনি run()সরাসরি পদ্ধতিতে কল দিলে অভিযোগ করবে ।
ডেনিস

পুরোপুরি জাভা ব্যাকগ্রাউন্ড থেকে আসছি, আমি অবাক হয়েছি যে জাভা জাভা 8-এর পর থেকে ফাংশনাল ইন্টারফেস এবং ল্যাম্বডাস চালু হওয়ার পরেও জাভা প্রথম শ্রেণির নাগরিক হিসাবে ফাংশনগুলি আচরণ করতে পারে না?
বিবেক শেঠি

21

যখন আপনাকে কোনও ক্রিয়াকলাপটি আর্গুমেন্ট হিসাবে গ্রহণ করতে হবে যা কোনও আর্গুমেন্ট গ্রহণ করে না এবং কোনও ফলাফল (অকার্যকর) দেয় না, তখন আমার মতে এটির মতো কিছু থাকা ভাল তবেই ভাল When

  public interface Thunk { void apply(); }

কোথাও আপনার কোড। আমার ক্রিয়ামূলক প্রোগ্রামিং কোর্সে এই ধরনের ফাংশন বর্ণনা করতে 'থাঙ্ক' শব্দটি ব্যবহৃত হত। এটি java.util.function এ কেন নয় তা আমার বোধগম্যতার বাইরে।

অন্যান্য ক্ষেত্রে আমি দেখতে পেলাম যে java.util.function এর সাথে আমার এমন স্বাক্ষরের সাথে কিছু মিল আছে - যখন ইন্টারফেসের নামকরণ আমার কোডটিতে ফাংশনটির ব্যবহারের সাথে মেলে না তখনও এটি সর্বদা সঠিক মনে হয় না। আমার ধারণা এটি একই ধরণের বিন্দু যা এখানে 'রান্নেবল' - যা থ্রেড শ্রেণীর সাথে সম্পর্কিত একটি শব্দ - যা আমার তাঁর স্বাক্ষরযুক্ত থাকতে পারে, সে সম্পর্কে পাঠককে বিভ্রান্ত করার সম্ভাবনা রয়েছে regarding


বিশেষত Runnableচেক করা ব্যতিক্রমগুলিকে অনুমতি দেয় না, এটি অনেকগুলি অ্যাপ্লিকেশনের জন্য অকেজো করে তোলে। যেহেতু Callable<Void>হয় না দরকারী, আপনার নিজের এটি নিজের বলে মনে করতে হবে। কুশ্রী।
জেসি গ্লিক 21

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

0

সেট রিটার্ন টাইপ Voidপরিবর্তে voidএবংreturn null

// Modify existing method
public static Void displayInt(Integer i) {
    System.out.println(i);
    return null;
}

অথবা

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});

-2

আমি মনে করি আপনার পরিবর্তে কনজিউমার ইন্টারফেসটি ব্যবহার করা উচিত Function<T, R>

একজন গ্রাহক মূলত একটি কার্যকরী ইন্টারফেস যা কোনও মান গ্রহণ করতে এবং কিছুই ফিরিয়ে আনার জন্য নকশাকৃত হয় (অর্থাত বাতিল)

আপনার ক্ষেত্রে, আপনি নিজের কোডে অন্য কোথাও এই উপভোক্তা তৈরি করতে পারেন:

Consumer<Integer> myFunction = x -> {
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...
}

তারপরে আপনি myForEachনীচের স্নিপেট সহ আপনার কোডটি প্রতিস্থাপন করতে পারেন :

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 
{
  list.forEach(x->myFunction.accept(x));
}

আপনি আমার ফাংশনটিকে প্রথম শ্রেণির অবজেক্ট হিসাবে দেখেন।


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