Java 8 Functional Interfaces: @FunctionalInterface, Predicate, Function, Consumer, Supplier | Sinhala Developer Guide

Java 8 Functional Interfaces: @FunctionalInterface, Predicate, Function, Consumer, Supplier | Sinhala Developer Guide

ආයුබෝවන් කට්ටිය! කොහොමද ඉතින්, වැඩ එහෙම ගොඩද? අද අපි කතා කරන්න යන්නේ Java programming වල ගොඩක් වැදගත්, එතකොට ගොඩක් දෙනෙක්ට තවම ටිකක් "නොතේරෙන" වගේ තියෙන topic එකක් ගැන. ඒ තමයි Java 8 වලින් ආපු Functional Interfaces. මේවා ගැන හොඳට තේරුම් ගත්තොත් ඔයාලගේ code එක කොච්චර clean වෙනවද, maintain කරන්න ලේසි වෙනවද කියලා හිතාගන්නත් බැරි වෙයි. ඒ වගේම Stream API එහෙම පාවිච්චි කරද්දි මේවා නැතුව බෑ. එහෙනම්, අපි බලමු මේ Functional Interfaces කියන්නේ මොනවද, ඇයි මේවා පාවිච්චි කරන්නේ, එතකොට කොහොමද අපේ දෛනික coding ජීවිතයට මේවා එකතු කරගන්නේ කියලා. අද Java developer කෙනෙක්ට මේවා නොදැන ඉන්න බෑ! හැමෝම ලෑස්තිද? එහෙනම් පටන් ගමු!

Functional Interfaces කියන්නේ මොනවද? (@FunctionalInterface)

හරි, මුලින්ම බලමු මේ Functional Interface කියන්නේ මොනවද කියලා. නම ඇහුවම ලොකු දෙයක් වගේ පෙනුනට, මේක සරලවම කියන්න පුළුවන්, single abstract method (SAM) එකක් විතරක් තියෙන interface එකකටයි කියලා. ඒක තමයි මෙතන main කාරණාව. Single abstract method!

මේක අපි Java 8 වලට කලින් තිබ්බ interface concept එකට වඩා වෙනස්. කලින් interface එකක ඕන තරම් abstract methods තියෙන්න පුළුවන්. ඒත් Functional Interface එකක තියෙන්න පුළුවන් එකම එක abstract method එකයි.

ඔයාලට මේක Functional Interface එකක් කියලා confirm කරගන්න පුළුවන් @FunctionalInterface annotation එක පාවිච්චි කරලා. මේක අනිවාර්යයෙන්ම දාන්න ඕන එකක් නෙවෙයි. Interface එකකට එක abstract method එකක් විතරක් තියෙනවනම් ඒක automatic Functional Interface එකක් වෙනවා. හැබැයි මේ annotation එක දැම්මම හොඳටම හොඳයි. මොකද, ඔයාලා අහම්බෙන් හරි දෙවෙනි abstract method එකක් දැම්මොත්, compiler එක error එකක් දෙනවා. ඒක අපිට ලොකු උදව්වක්.

හරි, පොඩි code example එකකින් බලමු මේක කොහොමද කියලා:

// Custom Functional Interface එකක්
@FunctionalInterface
interface MyCalculator {
    int operate(int a, int b);
    // int anotherMethod(int x); // මේක දැම්මොත් compile error එකක් එනවා
}

public class FunctionalInterfaceExample {
    public static void main(String[] args) {
        // Lambda Expression එකක් පාවිච්චි කරලා Functional Interface එක implement කරන හැටි
        MyCalculator add = (a, b) -> a + b;
        MyCalculator subtract = (a, b) -> a - b;

        System.out.println("Sum: " + add.operate(10, 5));
        System.out.println("Difference: " + subtract.operate(10, 5));
    }
}

දැක්කද? MyCalculator කියන interface එකේ operate කියන abstract method එක විතරයි තියෙන්නේ. ඒක නිසා මේක Functional Interface එකක්. මේ වගේ interfaces තමයි Lambda Expressions වලට පදනම වෙන්නේ.

ඇයි Functional Interfaces? (The "ElaKiri" of Java)

හරි, දැන් ඔයාලට හිතුණා ඇති, 'අනේ මට මේකෙන් මොකක්ද වැඩේ?' කියලා. ඒක හොඳ ප්‍රශ්නයක්. Java 8 වලට කලින්, මේ වගේ Single Abstract Method (SAM) interfaces තිබුණේ නැතුවා නෙවෙයි. Runnable, Callable, Comparator වගේ ඒවා Functional Interfaces තමයි. ඒත් Java 8 වලින් පස්සේ මේ concept එකට වැඩි අවධානයක් දීලා, ඒක Lambda Expressions එක්ක connect කරලා, code එක ලියන විදිය සම්පූර්ණයෙන්ම වෙනස් කළා. ඒක තමයි මේකේ "එළකිරි" කෑල්ල!

Functional Interfaces පාවිච්චි කරන එකෙන් අපිට ලැබෙන වාසි මෙන්න මෙහෙමයි:

  1. Cleaner, Concise Code (ලස්සනට ලියන්න පුළුවන්): Lambda Expressions පාවිච්චි කරලා code එක ලියන්න පුළුවන් වුණාම, Anonymous Inner Classes පාවිච්චි කරනවට වඩා code එක ගොඩක් කොටයි, කියවන්න ලේසියි. කෝඩ් ලයින් ගාණ අඩු වෙනවා.
  2. Enable Lambda Expressions: Functional Interfaces තමයි Lambda Expressions වලට "Target Type" එක වෙන්නේ. Lambda Expressions කියන්නේ කෙටියෙන් method implementations ලියන්න තියෙන විදියක්.
  3. Support for Stream API: Java 8 Stream API එකත් මේ Functional Interfaces මත තමයි හැදිලා තියෙන්නේ. filter(), map(), forEach() වගේ methods වලට parameter විදියට යවන්නේ Functional Interfaces.
  4. Functional Programming Style: Functional Interfaces නිසා Java වලට functional programming concepts (methods as arguments, higher-order functions) ගොඩක් පහසුවෙන් ගෙන එන්න පුළුවන් වුණා.

හරිද? දැන් තේරෙනවා ඇති මේක කොච්චර වැදගත්ද කියලා. Java වලට Modern twist එකක් දුන්නා වගේ වැඩක්.

Built-in Functional Interfaces (Standard Ones)

Java 8 එක්ක ආපු තවත් ලොකු දෙයක් තමයි java.util.function package එක. මේකේ සාමාන්‍යයෙන් අපිට ගොඩක් වෙලාවට අවශ්‍ය වෙන Functional Interfaces ගොඩක් තියෙනවා. අපිට හැම වෙලේම custom Functional Interfaces හදන්න ඕන නෑ. මේවා පාවිච්චි කළාම වැඩේ තවත් ලේසියි. ප්‍රධාන වශයෙන් පාවිච්චි වෙන Functional Interfaces 4ක් ගැන අපි කතා කරමු: Predicate, Function, Consumer, සහ Supplier.

1. Predicate

  • ප්‍රයෝජනය (Use Case): මේක පාවිච්චි කරන්නේ යම්කිසි object එකක් යම් condition එකකට අනුකූලද (true) නැද්ද (false) කියලා check කරන්න.
  • Abstract Method: boolean test(T t)
import java.util.function.Predicate;

public class PredicateExample {
    public static void main(String[] args) {
        // සංඛ්‍යාවක් 10ට වැඩියිද කියලා බලන Predicate එකක්
        Predicate<Integer> isGreaterThanTen = num -> num > 10;

        System.out.println("15 is greater than 10: " + isGreaterThanTen.test(15)); // true
        System.out.println("5 is greater than 10: " + isGreaterThanTen.test(5));   // false

        // තව Predicate Methods: and(), or(), negate()
        Predicate<Integer> isEven = num -> num % 2 == 0;
        Predicate<Integer> isGreaterThanFiveAndEven = isGreaterThanTen.and(isEven); // chaining

        System.out.println("12 is > 10 and Even: " + isGreaterThanFiveAndEven.test(12)); // true
    }
}

මේක filter() method එකට ගොඩක් පාවිච්චි වෙනවා.

2. Function

  • ප්‍රයෝජනය (Use Case): මේක පාවිච්චි කරන්නේ යම්කිසි input එකක් (T type) අරගෙන, ඒක වෙනත් output එකක් (R type) බවට convert (transform) කරන්න.
  • Abstract Method: R apply(T t)
import java.util.function.Function;

public class FunctionExample {
    public static void main(String[] args) {
        // String එකක් අරන් ඒකේ දිග (length) දෙන Function එකක්
        Function<String, Integer> stringLength = str -> str.length();

        System.out.println("Length of 'Hello': " + stringLength.apply("Hello")); // 5

        // සංඛ්‍යාවකට 5ක් එකතු කරන Function එකක්
        Function<Integer, Integer> addFive = num -> num + 5;
        System.out.println("10 + 5: " + addFive.apply(10)); // 15
    }
}

මේක map() method එකට ගොඩක් පාවිච්චි වෙනවා.

3. Consumer

  • ප්‍රයෝජනය (Use Case): මේක පාවිච්චි කරන්නේ යම්කිසි input එකක් (T type) අරගෙන, ඒකෙන් මොකක් හරි side-effect එකක් (e.g., printing, saving to DB) කරන්න. ඒකෙන් කිසිම return value එකක් නැහැ.
  • Abstract Method: void accept(T t)
import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        // String එකක් console එකේ print කරන Consumer එකක්
        Consumer<String> printMessage = msg -> System.out.println("Message: " + msg);

        printMessage.accept("Hello, Functional Interfaces!");

        // List එකක තියෙන හැම element එකක්ම print කරනවා
        java.util.List<String> names = java.util.Arrays.asList("Amal", "Kamal", "Nimal");
        names.forEach(name -> System.out.println("Name: " + name)); // forEach එක Consumer එකක් ගන්නවා
    }
}

මේක forEach() method එකට පාවිච්චි වෙනවා.

4. Supplier

  • ප්‍රයෝජනය (Use Case): මේක පාවිච්චි කරන්නේ කිසිම input එකක් නැතුව යම්කිසි value එකක් (T type) generate කරන්න (supply කරන්න).
  • Abstract Method: T get()
import java.util.function.Supplier;
import java.time.LocalDateTime;

public class SupplierExample {
    public static void main(String[] args) {
        // වත්මන් වෙලාව දෙන Supplier එකක්
        Supplier<LocalDateTime> currentTimeSupplier = () -> LocalDateTime.now();

        System.out.println("Current Time: " + currentTimeSupplier.get());

        // Random Number එකක් දෙන Supplier එකක්
        Supplier<Double> randomNumberSupplier = () -> Math.random();
        System.out.println("Random Number: " + randomNumberSupplier.get());
    }
}

මේක Lazy Initialization වගේ දේවල් වලට පාවිච්චි කරන්න පුළුවන්. ඒ කියන්නේ, අපිට value එකක් අවශ්‍ය වෙන්නේ ඒක ඉල්ලන වෙලාවට විතරයි.

මේවා තමයි ප්‍රධාන Built-in Functional Interfaces. මේවාට අමතරව BiPredicate, BiFunction, BinaryOperator, UnaryOperator වගේ තවත් ඒවා තියෙනවා. ඒවා ගැනත් හොයලා බලන්න.

Practical Usage and Tips (වැඩ කරන විදි)

හරි, දැන් අපි මේවාගේ theory එකයි, built-in ඒවායි ගැන දැනගත්තා. දැන් බලමු මේවා ප්‍රායෝගිකව කොහොමද පාවිච්චි කරන්නේ කියලා. ඇත්තටම මේවාගෙන් වැඩක් ගන්න පුළුවන් Stream API එකත් එක්ක එකතු වුණාම තමයි.

Stream API එකත් එක්ක Functional Interfaces

අපි පොඩි scenario එකක් ගමු. අපිට Student objects list එකක් තියෙනවා. ඒකෙන් අපිට අවශ්‍යයි "Computer Science" major එක කරන, එතකොට grade එක "A" ට වඩා වැඩි students ලාගේ නම් (names) විතරක් ගන්න. මේක අපි Functional Interfaces එක්ක Streams පාවිච්චි කරලා කරමු.

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Function;
import java.util.stream.Collectors;

class Student {
    String name;
    String major;
    char grade;

    public Student(String name, String major, char grade) {
        this.name = name;
        this.major = major;
        this.grade = grade;
    }

    public String getName() { return name; }
    public String getMajor() { return major; }
    public char getGrade() { return grade; }

    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", major='" + major + '\'' + ", grade=" + grade + '}';
    }
}

public class PracticalExample {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("Amal Perera", "Computer Science", 'A'));
        students.add(new Student("Kamal Silva", "Software Engineering", 'B'));
        students.add(new Student("Nimal Fernando", "Computer Science", 'B'));
        students.add(new Student("Sunil Jayasundara", "Computer Science", 'A'));
        students.add(new Student("Gayani Bandara", "Information Technology", 'C'));

        // Predicate: "Computer Science" major එකද කියලා බලනවා
        Predicate<Student> isCSMajor = student -> student.getMajor().equals("Computer Science");

        // Predicate: Grade එක 'A'ද කියලා බලනවා
        Predicate<Student> hasAGrade = student -> student.getGrade() == 'A';

        // Function: Student object එකකින් studentගේ නම ගන්නවා
        Function<Student, String> getStudentName = Student::getName; // Method reference!

        // Stream API එක පාවිච්චි කරලා data filter කරලා map කරනවා
        List<String> csStudentsWithAGrade = students.stream()
                .filter(isCSMajor.and(hasAGrade)) // Predicate chaining
                .map(getStudentName)              // Function use
                .collect(Collectors.toList());

        System.out.println("CS Students with 'A' Grade:");
        csStudentsWithAGrade.forEach(System.out::println); // Consumer use (Method reference)

        // මේකම Lambda Expression වලින් කෙලින්ම ලිව්වොත්
        List<String> anotherWay = students.stream()
                .filter(s -> s.getMajor().equals("Computer Science") && s.getGrade() == 'A')
                .map(Student::getName)
                .collect(Collectors.toList());

        System.out.println("\nAnother way (Lambda Expressions directly):");
        anotherWay.forEach(System.out::println);
    }
}

දැක්කද? මේක කොච්චර elegant ද කියලා! කලින් මේ වගේ දෙයක් කරන්න for loops, if-else statements ගොඩක් ලියන්න තිබුණා. දැන් එකම line කිහිපයකින්, කියවන්න ලේසි විදියට වැඩේ කරගන්න පුළුවන්.

Tips for Using Functional Interfaces:

  1. Readability: Code එක කියවන්න ලේසි වෙන්න, Lambda Expressions කොට වෙන්න ලියන්න පුරුදු වෙන්න. සමහර වෙලාවට method references (Student::getName) පාවිච්චි කරන එක තවත් හොඳයි.
  2. Don't Over-Engineer: හැමදේටම Functional Interfaces පාවිච්චි කරන්න යන්න එපා. සමහර වෙලාවට සාමාන්‍ය for loop එකක් හෝ simple method එකක් හොඳයි.
  3. Understand java.util.function: මේ package එකේ තියෙන interfaces ගැන හොඳට දැනුවත් වෙන්න. ඒවා පාවිච්චි කරන එකෙන් time save කරගන්න පුළුවන්.
  4. Practice: මේවා මුලින් අලුත් වගේ දැනෙන්න පුළුවන්. හැබැයි practice කරන තරමට ඉක්මනින්ම ගොඩ දාගන්න පුළුවන්.

Conclusion

ඉතින් යාලුවනේ, අද අපි Java 8 Functional Interfaces ගැන විස්තරාත්මකව කතා කළා. Functional Interfaces කියන්නේ මොනවද, ඇයි මේවා වැදගත්, එතකොට Predicate, Function, Consumer, Supplier වගේ built-in interfaces මොනවද, කොහොමද ඒවා Stream API එකත් එක්ක පාවිච්චි කරන්නේ කියලත් අපි බැලුවා.

Java ecosystem එකේ modern development වලට මේ concept එක නැතුවම බෑ. ඔබ Stream API, reactive programming, හෝ Spring Boot වගේ framework වල වැඩ කරනවා නම්, මේවා අනිවාර්යයෙන්ම දැනගෙන ඉන්න ඕන.

මතක තියාගන්න, coding කියන්නේ ඉගෙන ගන්න එක. අද ඔයාලා අලුත් දෙයක් ඉගෙන ගත්තා. මේ concepts ඔයාලගේ project වලට apply කරන්න උත්සාහ කරන්න. එතකොට තමයි මේක හොඳටම ඔලුවට යන්නේ.

ඔයාලට මේ article එක ගැන අදහස් තියෙනවා නම්, ප්‍රශ්න තියෙනවා නම්, නැත්නම් මේ වගේ තව මොනවා ගැනද දැනගන්න ඕන කියලා කියන්න පුළුවන් නම්, පහලින් comment එකක් දාන්න. මම පුළුවන් ඉක්මනින්ම උත්තර දෙන්නම්.

එහෙනම්, ආයෙත් අලුත් topic එකකින් හම්බවෙමු! සැමට ජය!