Java-তে Functional Interface (কার্যকরী ইন্টারফেস) হলো Lambda এক্সপ্রেশন ও Stream API-এর ভিত্তি। অনেক জুনিয়র ডেভেলপার Lambda ও Functional Programming শিখতে গিয়ে ইন্টারফেসের নিয়মকানুন নিয়ে confused হন। এই টিউটোরিয়ালে আমরা Functional Interface কী, কেন দরকার, এবং কীভাবে নিজের মতো করে তৈরি ও ব্যবহার করবেন — তা ধাপে ধাপে শিখব।

ভূমিকা (Background)

Functional Interface হলো এমন একটি Java ইন্টারফেস, যাতে ঠিক একটি মাত্র abstract method থাকে। Java 8-এর আগে আমরা Anonymous Inner Class ব্যবহার করে একক মেথডের বাস্তবায়ন করতাম। কিন্তু Functional Interface Lambda এক্সপ্রেশন ও Method Reference-এর মাধ্যমে সেই কোডকে অনেক বেশি সংক্ষিপ্ত ও পরিষ্কার করে তোলে।

@FunctionalInterface অ্যানোটেশন ব্যবহার করে আমরা নিশ্চিত করতে পারি যে ইন্টারফেসটি Functional Interface-এর নিয়ম মেনে চলছে। Java-তে বিল্ট-ইন কিছু Functional Interface আছে, যেমন Runnable, Callable, Comparator, এবং java.util.function প্যাকেজের Function, Predicate, Consumer, Supplier

পূর্বশর্ত (Prerequisites)

এই টিউটোরিয়াল অনুসরণ করতে আপনার প্রয়োজন হবে:

  • Java 8 বা তার উপরের ভার্সন (Functional Interface ও Lambda Java 8-এ এসেছে)
  • একটি IDE (IntelliJ IDEA, Eclipse, বা VS Code) অথবা কমান্ড লাইন
  • Java-তে ইন্টারফেসabstract class সম্পর্কে প্রাথমিক ধারণা
  • Lambda এক্সপ্রেশন সম্পর্কে সামান্য জানা থাকলে ভালো, তবে এখান থেকে শিখতে পারবেন

যদি Lambda এক্সপ্রেশন এখনও না জানেন, তাহলে চিন্তা নেই — এই টিউটোরিয়ালে Functional Interface তৈরি করার সময় Lambda-এর ব্যবহারও দেখিয়ে দেব।

ধাপে ধাপে বাস্তবায়ন (Step-by-Step Implementation)

১. Functional Interface-এর সংজ্ঞা ও নিয়ম

Functional Interface-এর মূল নিয়ম হলো:

  1. ইন্টারফেসে শুধু একটি abstract method থাকতে হবে
  2. default methodstatic method থাকতে পারে (এগুলো abstract method হিসেবে গণ্য হয় না)
  3. @FunctionalInterface অ্যানোটেশন দিয়ে চিহ্নিত করলে কম্পাইলার ভুল হলে error দেবে
@FunctionalInterface
public interface MyCalculator {
    int calculate(int a, int b);  // একমাত্র abstract method
}

উপরের কোডটি একটি Functional Interface। calculate মেথডটি abstract। এখন আপনি এই ইন্টারফেস ব্যবহার করে বিভিন্ন গাণিতিক অপারেশন করতে পারেন।

২. নিজের Functional Interface তৈরি করা

আপনার নিজের Functional Interface তৈরি করতে নিচের ধাপগুলো অনুসরণ করুন:

@FunctionalInterface
public interface StringProcessor {
    String process(String input);
}

এটি একটি সাধারণ Functional Interface যা একটি String ইনপুট নেয় এবং প্রক্রিয়াজাত করে String রিটার্ন করে। এখন আমরা Lambda এক্সপ্রেশন ব্যবহার করে এর বাস্তবায়ন করব:

public class FunctionalInterfaceDemo {
    public static void main(String[] args) {
        // Lambda ব্যবহার করে বাস্তবায়ন
        StringProcessor toUpperCase = (input) -> input.toUpperCase();
        StringProcessor reverse = (input) -> new StringBuilder(input).reverse().toString();
        
        System.out.println(toUpperCase.process("hello"));   // HELLO
        System.out.println(reverse.process("hello"));       // olleh
    }
}

এখানে toUpperCasereverse দুটি ভিন্ন Lambda এক্সপ্রেশন StringProcessor ইন্টারফেসের process মেথডকে বাস্তবায়ন করছে। Lambda-এর বাম পাশে প্যারামিটার, ডান পাশে মেথডের বডি।

৩. বিল্ট-ইন Functional Interface ব্যবহার করা

Java অনেক বিল্ট-ইন Functional Interface সরবরাহ করে। সবচেয়ে গুরুত্বপূর্ণ চারটি হলো:

ইন্টারফেস মেথড সিগনেচার ব্যবহার
Predicate<T> boolean test(T t) শর্ত যাচাই
Function<T,R> R apply(T t) ইনপুট থেকে আউটপুট
Consumer<T> void accept(T t) ডেটা প্রক্রিয়াজাতকরণ
Supplier<T> T get() মান সরবরাহ
import java.util.function.*;

public class BuiltInFunctionalInterfaceDemo {
    public static void main(String[] args) {
        // Predicate: সংখ্যা পজিটিভ কিনা
        Predicate<Integer> isPositive = n -> n > 0;
        System.out.println(isPositive.test(5));   // true
        System.out.println(isPositive.test(-3));    // false
        
        // Function: স্ট্রিং-এর দৈর্ঘ্য বের করা
        Function<String, Integer> getLength = str -> str.length();
        System.out.println(getLength.apply("Java")); // 4
        
        // Consumer: প্রিন্ট করা
        Consumer<String> print = msg -> System.out.println(msg);
        print.accept("Hello Functional Interface!"); // Hello Functional Interface!
        
        // Supplier: র্যান্ডম সংখ্যা
        Supplier<Double> random = () -> Math.random();
        System.out.println(random.get());          // 0.123456...
    }
}

এই উদাহরণে চারটি বিল্ট-ইন Functional Interface-এর ব্যবহার দেখানো হয়েছে। Predicate boolean রিটার্ন করে, Function ইনপুট থেকে আউটপুট তৈরি করে, Consumer কিছু করে কিন্তু কিছু রিটার্ন করে না, আর Supplier কোনো ইনপুট ছাড়াই আউটপুট দেয়।

৪. Method Reference দিয়ে Functional Interface ব্যবহার

Method Reference হলো Lambda-এর সংক্ষিপ্ত রূপ। যখন Lambda শুধু একটি existing method কল করে, তখন Method Reference ব্যবহার করা যায়।

import java.util.function.*;

public class MethodReferenceDemo {
    public static void main(String[] args) {
        // Static method reference
        Function<String, Integer> parseInt = Integer::parseInt;
        System.out.println(parseInt.apply("123") + 10); // 133
        
        // Instance method reference
        Consumer<String> print = System.out::println;
        print.accept("Method Reference চমৎকার!"); 
        
        // Constructor reference
        Supplier<StringBuilder> create = StringBuilder::new;
        StringBuilder sb = create.get();
        sb.append("Hello ");
        sb.append("from Constructor Reference");
        System.out.println(sb.toString()); // Hello from Constructor Reference
    }
}

Method Reference ব্যবহার করলে কোড আরও পরিষ্কার হয়। Integer::parseInt বলতে Integer ক্লাসের parseInt স্ট্যাটিক মেথডকে বোঝায়।

সম্পূর্ণ উদাহরণ (Complete Example)

নিচে একটি পূর্ণাঙ্গ উদাহরণ দেওয়া হলো যা আপনি কপি করে রান করতে পারেন:

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

// নিজস্ব Functional Interface
@FunctionalInterface
interface Calculator {
    int operate(int a, int b);
}

public class CompleteFunctionalInterfaceDemo {
    public static void main(String[] args) {
        // 1. নিজস্ব Functional Interface ব্যবহার
        Calculator add = (a, b) -> a + b;
        Calculator multiply = (a, b) -> a * b;
        
        System.out.println("যোগ: " + add.operate(10, 5));      // 15
        System.out.println("গুণ: " + multiply.operate(10, 5)); // 50
        
        // 2. বিল্ট-ইন Functional Interface ব্যবহার
        List<String> names = Arrays.asList("Rahim", "Karim", "Jamal", "Kamal");
        
        // Predicate: নাম 'K' দিয়ে শুরু কিনা
        Predicate<String> startsWithK = name -> name.startsWith("K");
        
        // Consumer: ফিল্টার করা নাম প্রিন্ট করা
        Consumer<String> printName = System.out::println;
        
        // Stream API-তে Functional Interface ব্যবহার
        names.stream()
             .filter(startsWithK)    // Predicate
             .forEach(printName);     // Consumer
        
        // 3. Function chaining
        Function<String, String> toUpper = String::toUpperCase;
        Function<String, String> addExclamation = s -> s + "!";
        
        Function<String, String> compose = toUpper.andThen(addExclamation);
        System.out.println(compose.apply("hello")); // HELLO!
        
        // 4. Supplier ব্যবহার করে র্যান্ডম নাম
        Supplier<String> randomName = () -> {
            String[] namesArray = {"Rahim", "Karim", "Jamal"};
            return namesArray[new Random().nextInt(3)];
        };
        
        System.out.println("র্যান্ডম নাম: " + randomName.get());
    }
}

আউটপুট:

যোগ: 15
গুণ: 50
Karim
Kamal
HELLO!
র্যান্ডম নাম: Jamal

এই উদাহরণে আমরা নিজস্ব Calculator ইন্টারফেস, বিল্ট-ইন Predicate, Consumer, Function, ও Supplier — সবকিছু একসাথে ব্যবহার করেছি। Stream API-তে filterforEach মেথড Functional Interface গ্রহণ করে।

সাধারণ সমস্যা ও সমাধান (Common Issues)

১. একাধিক abstract method থাকলে error

Functional Interface-এ একটির বেশি abstract method থাকলে কম্পাইলার error দেবে।

@FunctionalInterface
interface WrongInterface {
    void method1();
    void method2();  // error: FunctionalInterface-এ একাধিক abstract method
}

সমাধান: নিশ্চিত করুন যে শুধু একটি abstract method আছে। যদি একাধিক মেথডের প্রয়োজন হয়, তাহলে সাধারণ ইন্টারফেস ব্যবহার করুন।

২. @FunctionalInterface অ্যানোটেশন না দিলেও কাজ করে

অ্যানোটেশন বাধ্যতামূলক নয়, কিন্তু দিলে কম্পাইলার চেক করে। অ্যানোটেশন না দিলেও একটি abstract method থাকলে Functional Interface হিসেবে কাজ করবে।

// @FunctionalInterface ছাড়াও কাজ করে
interface SimpleInterface {
    void doSomething();
}

পরামর্শ: সবসময় @FunctionalInterface দিন — এটি কোডের উদ্দেশ্য পরিষ্কার করে।

৩. Lambda এক্সপ্রেশনে প্যারামিটার টাইপ মিস করা

Lambda-তে প্যারামিটার টাইপ উল্লেখ করা ঐচ্ছিক। কম্পাইলার টাইপ ইনফার করতে পারে।

// দুটোই valid
Calculator add1 = (int a, int b) -> a + b;
Calculator add2 = (a, b) -> a + b;  // টাইপ ইনফারেন্স

টিপ: যখন টাইপ স্পষ্ট, তখন টাইপ বাদ দেওয়া ভালো — কোড ছোট হয়।

৪. Functional Interface-এ checked exception হ্যান্ডলিং

Functional Interface-এর abstract method checked exception throw করলে Lambda-তে হ্যান্ডল করতে হবে।

@FunctionalInterface
interface ThrowingFunction {
    String apply(String input) throws Exception;
}

// ব্যবহার:
ThrowingFunction func = (s) -> {
    if (s == null) throw new Exception("Null input");
    return s.toUpperCase();
};

সমাধান: ইন্টারফেসে throws clause যোগ করুন অথবা Lambda-তে try-catch ব্যবহার করুন।

সারসংক্ষেপ (Summary)

Functional Interface Java Functional Programming-এর ভিত্তি। এখানে আমরা যা শিখলাম:

  • Functional Interface হলো একটি ইন্টারফেস যাতে ঠিক একটি abstract method থাকে
  • Lambda এক্সপ্রেশনMethod Reference ব্যবহার করে সহজেই বাস্তবায়ন করা যায়
  • বিল্ট-ইন Functional Interface (Predicate, Function, Consumer, Supplier) দৈনন্দিন কাজে লাগে
  • Stream API-র সাথে Functional Interface ব্যবহার করলে কোড অনেক পরিষ্কার ও সংক্ষিপ্ত হয়
  • @FunctionalInterface অ্যানোটেশন ব্যবহার করা ভালো অভ্যাস

আপনার Java কোডে Functional Interface ব্যবহার শুরু করুন। প্রথমে বিল্ট-ইন ইন্টারফেসগুলো দিয়ে অনুশীলন করুন, তারপর নিজের ইন্টারফেস তৈরি করুন। Lambda ও Functional Interface আয়ত্ত করলে Java 8-এর পরের সব ফিচার (Stream, Optional, CompletableFuture) সহজে বুঝতে পারবেন।

Share