Modern Java

Functional Interfaces and Method References

ReadingPreview

You are viewing a free preview lesson.

Lesson Overview

আগের lesson-এ আমরা Lambda Expression শিখেছি।

আমরা দেখেছি, ছোট একটি behavior-কে inlineভাবে লিখে কোনো method-এর কাছে pass করা যায়।

Example:

number -> number * 2

বা:

course ->
        course.priceInPaisa()
        > 500_000

কিন্তু একটি গুরুত্বপূর্ণ প্রশ্ন এখনো বাকি:

Java কীভাবে বুঝে একটি Lambda কী ধরনের behavior represent করছে?

উত্তর:

Functional Interface

Lambda Expression নিজে কোনো standalone function নয়।

একটি Lambda সবসময় কোনো compatible Functional Interface-এর implementation হিসেবে কাজ করে।

Modern Java-তে কিছু standard functional interfaces আছে যেগুলো আমরা খুব frequently ব্যবহার করি:

Predicate<T>
Function<T, R>
Consumer<T>
Supplier<T>
UnaryOperator<T>
BinaryOperator<T>

এই lesson-এ আমরা শিখব:

  • Functional Interface কী
  • @FunctionalInterface
  • Predicate<T>
  • Function<T, R>
  • Consumer<T>
  • Supplier<T>
  • UnaryOperator<T>
  • BinaryOperator<T>
  • Custom functional interfaces
  • Behavior composition
  • and(), or(), negate()
  • andThen() এবং compose()
  • Method Reference
  • Static method reference
  • Bound instance method reference
  • Unbound instance method reference
  • Constructor reference
  • Lambda বনাম Method Reference
  • কখন কোনটি clearer

What Is a Functional Interface?

Functional Interface হলো এমন একটি interface যেখানে একটি মাত্র abstract method থাকে।

Example:

@FunctionalInterface
interface Calculator {

    int calculate(
            int first,
            int second
    );
}

এখানে abstract method মাত্র একটি:

calculate(...)

তাই আমরা এর implementation Lambda দিয়ে দিতে পারি।

Calculator addition =
        (
                first,
                second
        ) -> first + second;

এখন:

addition.calculate(
        10,
        20
);

return করবে:

30

Why One Abstract Method?

Lambda মূলত একটি behavior-এর implementation দেয়।

যদি interface-এ দুইটি abstract method থাকত:

interface Something {

    void first();

    void second();
}

তাহলে এই Lambda:

() -> System.out.println(
        "Hello"
)

কোন method implement করছে?

first()?
second()?

Ambiguous হয়ে যেত।

একটি abstract method থাকলে mapping পরিষ্কার:

Lambda
→ সেই single abstract method

@FunctionalInterface

Java-তে আমরা functional interface-এর উপর লিখতে পারি:

@FunctionalInterface

Example:

@FunctionalInterface
interface CourseRule {

    boolean test(
            Course course
    );
}

এই annotation compiler-কে জানায়:

এই interface-টি functional interface হিসেবেই design করা হয়েছে।

Is @FunctionalInterface Required?

না।

এই interface-টিও Lambda target হতে পারে:

interface CourseRule {

    boolean test(
            Course course
    );
}

কারণ abstract method মাত্র একটি।

কিন্তু annotation ব্যবহার করা ভালো কারণ future-এ কেউ accidentally আরেকটি abstract method যোগ করলে compiler error দেবে।


Example

Valid:

@FunctionalInterface
interface CourseRule {

    boolean test(
            Course course
    );
}

Invalid:

@FunctionalInterface
interface CourseRule {

    boolean test(
            Course course
    );

    boolean anotherRule(
            Course course
    );
}

Compiler বুঝবে:

এটি আর functional interface নয়।

Default Methods Do Not Break Functional Interface

একটি functional interface-এর একটি abstract method থাকতে হবে।

কিন্তু এতে default method থাকতে পারে।

Example:

@FunctionalInterface
interface CourseRule {

    boolean test(
            Course course
    );

    default boolean not(
            Course course
    ) {
        return !test(
                course
        );
    }
}

এটি এখনো functional interface।

কারণ abstract method একটি:

test(...)

Static Methods Are Also Fine

@FunctionalInterface
interface CourseRule {

    boolean test(
            Course course
    );

    static CourseRule alwaysTrue() {
        return course -> true;
    }
}

এটিও valid।

static method abstract contract-এর অংশ নয়।


Standard Functional Interfaces

Java ইতিমধ্যে common behavior-এর জন্য reusable functional interfaces দিয়েছে।

এগুলো থাকে:

java.util.function

package-এ।

সবচেয়ে গুরুত্বপূর্ণগুলো:

Predicate<T>
Function<T, R>
Consumer<T>
Supplier<T>
UnaryOperator<T>
BinaryOperator<T>

প্রতিটির একটি specific purpose আছে।


Predicate<T>

Predicate<T> এমন behavior represent করে যা:

একটি value নেয়
এবং
boolean return করে

Mental model:

T
→ boolean

Abstract method:

boolean test(
        T value
);

Predicate Example

Predicate<Integer> adultAge =
        age -> age >= 18;

Use:

boolean result =
        adultAge.test(
                20
        );

Result:

true

Course Predicate

record Course(
        String title,
        long priceInPaisa
) {
}

Predicate:

Predicate<Course> paidCourse =
        course ->
                course.priceInPaisa()
                > 0;

Check:

paidCourse.test(
        course
);

Read Predicate Naturally

এই code:

course ->
        course.priceInPaisa()
        > 0

এভাবে পড়তে পারেন:

একটি Course দাও।

Course-এর price zero-এর বেশি হলে true return করো।

Where Predicate Is Commonly Used

Predicate<T> আমরা অনেক জায়গায় দেখি:

removeIf(...)
filter(...)

Example:

courses.removeIf(
        course ->
                course.priceInPaisa()
                == 0
);

removeIf() একটি Predicate নেয়।


Predicate Composition

Suppose আমাদের দুইটি rule আছে।

Predicate<Course> paid =
        course ->
                course.priceInPaisa()
                > 0;

আর:

Predicate<Course> expensive =
        course ->
                course.priceInPaisa()
                >= 500_000;

এগুলো combine করা যায়।


and()

Predicate<Course> paidAndExpensive =
        paid.and(
                expensive
        );

Now:

paidAndExpensive.test(
        course
);

true হবে only when:

paid == true
AND
expensive == true

or()

Predicate<Course> paidOrExpensive =
        paid.or(
                expensive
        );

true হবে যখন যেকোনো একটি condition true।


negate()

Predicate<Course> free =
        paid.negate();

Meaning:

not paid

Example

Predicate<Course> published =
        course ->
                course.status()
                == CourseStatus.PUBLISHED;

Then:

Predicate<Course> unpublished =
        published.negate();

এই composition business rules readable করতে পারে।


Do Not Over-Compose

এমন code:

ruleA
        .and(
                ruleB
        )
        .or(
                ruleC.negate()
        )
        .and(
                ruleD
        )

technically valid হলেও business rule বোঝা কঠিন হতে পারে।

যদি rule meaningful হয়, name দিন:

Predicate<Course> eligibleForPromotion =
        paid
                .and(
                        published
                );

Readability priority।


Function<T, R>

Function<T, R> একটি input নেয় এবং অন্য একটি result return করে।

Mental model:

T
→ R

Abstract method:

R apply(
        T value
);

Function Example

Function<String, Integer> length =
        text -> text.length();

Input:

String

Output:

Integer

Use:

int result =
        length.apply(
                "Java"
        );

Result:

4

Course to Title

Function<Course, String> courseTitle =
        course ->
                course.title();

Use:

String title =
        courseTitle.apply(
                course
        );

Course to Price

Function<Course, Long> coursePrice =
        course ->
                course.priceInPaisa();

Notice:

Course
→ Long

Transforming Data

Function-এর সবচেয়ে গুরুত্বপূর্ণ mental model:

Transform one value into another.

Examples:

Course → String
String → Integer
Learner → EmailAddress
Enrollment → EnrollmentSummary

Function.andThen()

Suppose:

Function<String, String> trim =
        value -> value.strip();

And:

Function<String, String> upper =
        value -> value.toUpperCase();

Combine:

Function<String, String> normalize =
        trim.andThen(
                upper
        );

Input:

"  java  "

Process:

trim first
→ "java"

upper second
→ "JAVA"

andThen() Order

first.andThen(
        second
)

means:

first
then
second

compose()

compose() order উল্টো।

Function<String, String> normalize =
        upper.compose(
                trim
        );

Means:

trim first
then upper

because:

upper compose trim

means upper-এর আগে trim apply হবে।


andThen() vs compose()

যদি:

A.andThen(
        B
)

then:

A → B

If:

A.compose(
        B
)

then:

B → A

Prefer Readable Composition

অনেক chained transformations readable হতে পারে:

Function<String, String> normalize =
        trim
                .andThen(
                        upper
                );

কিন্তু excessively abstract composition এড়িয়ে চলুন।

একটি named method অনেক সময় clearer:

normalizeCourseCode(...)

Consumer<T>

Consumer<T> একটি value নেয় কিন্তু meaningful return value দেয় না।

Mental model:

T
→ void

Abstract method:

void accept(
        T value
);

Consumer Example

Consumer<String> printer =
        value ->
                System.out.println(
                        value
                );

Use:

printer.accept(
        "Java"
);

Output:

Java

Course Consumer

Consumer<Course> printCourse =
        course ->
                System.out.println(
                        course.title()
                );

Where Consumer Is Common

forEach() Consumer-style behavior নেয়।

Example:

courses.forEach(
        course ->
                System.out.println(
                        course.title()
                )
);

Consumer.andThen()

Consumers combine করা যায়।

Consumer<String> print =
        value ->
                System.out.println(
                        value
                );

Another:

Consumer<String> printLength =
        value ->
                System.out.println(
                        value.length()
                );

Combine:

Consumer<String> both =
        print.andThen(
                printLength
        );

Then:

both.accept(
        "Java"
);

runs both behaviors।


Be Careful with Side Effects

Consumer naturally represents side-effecting behavior।

Examples:

Print
Store
Send
Log
Update

এগুলো useful, কিন্তু অনেক side effect chain করলে flow বোঝা কঠিন হতে পারে।


Supplier<T>

Supplier<T> কোনো input নেয় না কিন্তু একটি value return করে।

Mental model:

nothing
→ T

Abstract method:

T get();

Supplier Example

Supplier<String> greeting =
        () -> "Hello";

Use:

String value =
        greeting.get();

Generate an Object

Supplier<List<String>> listFactory =
        () -> new ArrayList<>();

Use:

List<String> values =
        listFactory.get();

Supplier and Lazy Creation

Supplier useful যখন value এখনই create করতে চাই না।

Example:

Supplier<String> expensiveMessage =
        () -> buildExpensiveMessage();

এখন buildExpensiveMessage() run হবে যখন:

expensiveMessage.get()

call করা হবে।


Supplier in Error Creation

Modern Java APIs-এ এমন pattern frequently দেখা যায়:

orElseThrow(
        () ->
                new IllegalStateException(
                        "Course not found."
                )
);

এখানে exception creation behavior একটি Supplier-এর মতো।

Optional lesson-এ আমরা এটি বিস্তারিত দেখব।


UnaryOperator<T>

UnaryOperator<T> হচ্ছে special ধরনের Function যেখানে input এবং output একই type।

Mental model:

T
→ T

Example:

UnaryOperator<Integer> doubleValue =
        number -> number * 2;

Input:

Integer

Output:

Integer

String Normalization

UnaryOperator<String> normalize =
        value ->
                value.strip()
                        .toUpperCase();

Input:

String

Output:

String

Why Not Just Function?

এটিও possible:

Function<String, String>

কিন্তু:

UnaryOperator<String>

আরও specificভাবে communicate করে:

same type in
same type out

BinaryOperator<T>

BinaryOperator<T> দুইটি same-type input নেয় এবং same type result দেয়।

Mental model:

(T, T)
→ T

Example:

BinaryOperator<Integer> addition =
        (
                first,
                second
        ) -> first + second;

Maximum

BinaryOperator<Integer> maximum =
        (
                first,
                second
        ) -> Math.max(
                first,
                second
        );

String Combination

BinaryOperator<String> combine =
        (
                first,
                second
        ) ->
                first
                + ", "
                + second;

Functional Interface Cheat Sheet

Predicate<T>
T → boolean

Function<T, R>
T → R

Consumer<T>
T → void

Supplier<T>
() → T

UnaryOperator<T>
T → T

BinaryOperator<T>
(T, T) → T

এগুলো memorization-এর চেয়ে mental shape দিয়ে মনে রাখুন।


Custom Functional Interface

Standard interfaces সব situation cover করবে না।

Suppose:

@FunctionalInterface
interface PriceCalculator {

    long calculate(
            Course course,
            long discountInPaisa
    );
}

Then:

PriceCalculator calculator =
        (
                course,
                discount
        ) ->
                course.priceInPaisa()
                - discount;

When to Create a Custom Functional Interface

Create one when:

Behavior has meaningful domain semantics

For example:

EnrollmentEligibility
PricePolicy
CourseValidator

But avoid unnecessary new interfaces when standard types already communicate intent well।


Example

Instead of:

@FunctionalInterface
interface CourseCondition {

    boolean check(
            Course course
    );
}

consider:

Predicate<Course>

because it already means:

Course → boolean

But Domain Naming Can Be Valuable

Suppose business concept itself গুরুত্বপূর্ণ:

@FunctionalInterface
interface EnrollmentPolicy {

    boolean canEnroll(
            Learner learner,
            Course course
    );
}

This can communicate domain intent much better than a generic:

BiPredicate<Learner, Course>

Generic interface convenience এবং domain meaning-এর মধ্যে balance করতে হবে।


Other Standard Functional Interfaces

Java-তে আরও variants আছে।

Examples:

BiPredicate<T, U>
BiFunction<T, U, R>
BiConsumer<T, U>

BiPredicate<T, U>

Two inputs, boolean output:

(T, U)
→ boolean

Example:

BiPredicate<Integer, Integer> greaterThan =
        (
                first,
                second
        ) -> first > second;

BiFunction<T, U, R>

Two inputs, one output:

(T, U)
→ R

Example:

BiFunction<String, String, String> fullName =
        (
                first,
                last
        ) ->
                first
                + " "
                + last;

Primitive Specializations

Generic types such as:

Function<Integer, Integer>

may involve boxing/unboxing।

Java provides primitive specializations such as:

IntPredicate
IntFunction<R>
IntConsumer
IntSupplier
IntUnaryOperator
IntBinaryOperator

Example:

IntPredicate positive =
        value -> value > 0;

Do Beginners Need All of Them?

না।

Start with:

Predicate
Function
Consumer
Supplier
UnaryOperator
BinaryOperator

Then recognize primitive versions যখন performance বা primitive APIs-এর context-এ আসবে।


Method References

এখন Lambda syntax-এর আরেকটি concise form দেখব:

Method Reference

Suppose:

names.forEach(
        name ->
                System.out.println(
                        name
                )
);

Lambda শুধু existing method call করছে:

System.out.println(...)

এটাকে shorterভাবে লেখা যায়:

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

What Does :: Mean?

Method reference syntax uses:

::

It means conceptually:

Use this existing method
as the required behavior.

Lambda vs Method Reference

Lambda:

name ->
        System.out.println(
                name
        )

Method reference:

System.out::println

দুটোর behavior compatible হলে method reference ব্যবহার করা যায়।


Method Reference Is Not Calling the Method Immediately

This:

System.out::println

method call করছে না।

এটি একটি reference to behavior।

Calling would be:

System.out.println(
        value
);

Method reference:

behavior itself

Main Method Reference Forms

Common forms:

ClassName::staticMethod

object::instanceMethod

ClassName::instanceMethod

ClassName::new

প্রতিটি আলাদা pattern represent করে।


Static Method Reference

Suppose:

static int parse(
        String value
) {
    return Integer.parseInt(
            value
    );
}

Lambda:

Function<String, Integer> parser =
        value ->
                Integer.parseInt(
                        value
                );

Method reference:

Function<String, Integer> parser =
        Integer::parseInt;

Another Static Example

Lambda:

BinaryOperator<Integer> maximum =
        (
                first,
                second
        ) -> Math.max(
                first,
                second
        );

Method reference:

BinaryOperator<Integer> maximum =
        Math::max;

Bound Instance Method Reference

Suppose আমাদের already একটি object আছে:

PrintStream output =
        System.out;

Then:

Consumer<String> printer =
        output::println;

The object:

output

আগেই fixed।

এটি called object-এর bound instance method reference।


Another Bound Example

String prefix =
        "JAVA";

Suppose compatible interface context exists:

Predicate<String> startsWithJava =
        prefix::startsWith;

Conceptually:

value ->
        prefix.startsWith(
                value
        )

ClassName::instanceMethod

এটি একটু বেশি confusing হতে পারে।

Example:

Function<String, String> upper =
        String::toUpperCase;

Conceptually:

value ->
        value.toUpperCase()

এখানে input object-ই method receiver হয়ে যায়।


String Length

Lambda:

Function<String, Integer> length =
        value ->
                value.length();

Method reference:

Function<String, Integer> length =
        String::length;

Comparator Example

Comparator.comparing(
        Course::title
);

এখানে:

Course::title

means conceptually:

course ->
        course.title()

এটি একটি instance method reference।


Constructor Reference

Suppose:

Supplier<ArrayList<String>> factory =
        () ->
                new ArrayList<>();

Constructor reference:

Supplier<ArrayList<String>> factory =
        ArrayList::new;

Constructor with Parameter

Suppose:

Function<String, CourseCode> factory =
        value ->
                new CourseCode(
                        value
                );

Can become:

Function<String, CourseCode> factory =
        CourseCode::new;

if constructor signature matches।


Method Reference Depends on Target Type

Just like Lambda, method reference needs target context।

This alone:

String::length

does not fully tell Java how you intend to use it।

But:

Function<String, Integer> length =
        String::length;

provides the necessary context।


When Method Reference Is Clearer

Good:

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

Compared with:

names.forEach(
        name ->
                System.out.println(
                        name
                )
);

Method reference removes unnecessary syntax।


Another Good Example

Lambda:

courses.sort(
        Comparator.comparing(
                course ->
                        course.title()
        )
);

Better:

courses.sort(
        Comparator.comparing(
                Course::title
        )
);

When Lambda Is Clearer

Do not force method references।

Suppose:

course ->
        course.priceInPaisa()
        > 500_000

There may not be an existing method that expresses:

is expensive

A Lambda is clearer।


Bad Forced Method Reference Thinking

If you start creating strange helper methods only to write:

CourseRules::isPriceGreaterThanFiveHundredThousand

instead of a simple:

course ->
        course.priceInPaisa()
        > 500_000

you may be making the code worse।

Use method references when an existing named behavior already fits naturally।


Named Business Rule

On the other hand, if the concept matters:

static boolean isEligibleForPromotion(
        Course course
) {
    ...
}

then:

courses.removeIf(
        CourseRules::isNotEligibleForPromotion
);

may be clearer than duplicating a complex Lambda।


Lambda vs Method Reference Rule

A useful rule:

If the Lambda only forwards its parameters
to one existing method,
consider a method reference.

Example:

value ->
        Integer.parseInt(
                value
        )

becomes:

Integer::parseInt

But Readability Wins

Compare:

something::doSomething

with a Lambda where argument mapping is clearer।

If method reference makes the reader stop and mentally decode parameter placement, Lambda may be better।


Practical Example — Predicate

import java.util.function.Predicate;

public class Main {

    public static void main(String[] args) {
        Predicate<Integer> positive =
                value -> value > 0;

        Predicate<Integer> even =
                value ->
                        value % 2
                        == 0;

        Predicate<Integer> positiveEven =
                positive.and(
                        even
                );

        System.out.println(
                positiveEven.test(
                        10
                )
        );

        System.out.println(
                positiveEven.test(
                        -10
                )
        );
    }
}

Output:

true
false

Practical Example — Function

import java.util.function.Function;

public class Main {

    public static void main(String[] args) {
        Function<String, String> normalize =
                value ->
                        value.strip()
                                .toUpperCase();

        System.out.println(
                normalize.apply(
                        "  java  "
                )
        );
    }
}

Output:

JAVA

Practical Example — Supplier

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

public class Main {

    public static void main(String[] args) {
        Supplier<List<String>> factory =
                ArrayList::new;

        List<String> courses =
                factory.get();

        courses.add(
                "Java"
        );

        System.out.println(
                courses
        );
    }
}

Practical Example — Consumer

import java.util.List;
import java.util.function.Consumer;

public class Main {

    public static void main(String[] args) {
        Consumer<String> printer =
                System.out::println;

        List<String> names =
                List.of(
                        "Sakib",
                        "Subu",
                        "Sumu"
                );

        names.forEach(
                printer
        );
    }
}

Practical Example — Course Rules

import java.util.function.Predicate;

public class Main {

    public static void main(String[] args) {
        Course java =
                new Course(
                        "Java Foundation",
                        300_000,
                        true
                );

        Predicate<Course> paid =
                course ->
                        course.priceInPaisa()
                        > 0;

        Predicate<Course> published =
                Course::published;

        Predicate<Course> purchasable =
                paid.and(
                        published
                );

        System.out.println(
                purchasable.test(
                        java
                )
        );
    }

    record Course(
            String title,
            long priceInPaisa,
            boolean published
    ) {
    }
}

Course::published

Record accessor:

published()

returns:

boolean

So:

Course::published

matches:

Predicate<Course>

because conceptually:

Course
→ boolean

Standard Functional Interface Selection

Suppose আপনি behavior দেখছেন:

Course → boolean

Choose:

Predicate<Course>

If:

Course → String

choose:

Function<Course, String>

If:

Course → void

choose:

Consumer<Course>

If:

() → Course

choose:

Supplier<Course>

Practice 1 — Identify Interface

Behavior:

String → boolean

Which interface?

Answer

Predicate<String>

Practice 2

Behavior:

Course → String

Answer

Function<Course, String>

Practice 3

Behavior:

Learner → void

Answer

Consumer<Learner>

Practice 4

Behavior:

() → Course

Answer

Supplier<Course>

Practice 5

Behavior:

String → String

and input/output একই type।

Answer

UnaryOperator<String>

Function<String, String>-ও কাজ করবে, কিন্তু UnaryOperator বেশি specific।


Practice 6

Behavior:

(Integer, Integer) → Integer

same input/output type।

Answer

BinaryOperator<Integer>

Practice 7 — Predicate Composition

Given:

Predicate<Integer> positive =
        value -> value > 0;

Predicate<Integer> even =
        value -> value % 2 == 0;

Create a rule for positive and even।

Solution

Predicate<Integer> positiveEven =
        positive.and(
                even
        );

Practice 8 — Negation

Create rule:

not positive

Solution

Predicate<Integer> notPositive =
        positive.negate();

Practice 9 — Method Reference

Convert:

value ->
        System.out.println(
                value
        )

Solution

System.out::println

Practice 10

Convert:

value ->
        value.length()

with String input।

Solution

String::length

Practice 11

Convert:

value ->
        Integer.parseInt(
                value
        )

Solution

Integer::parseInt

Practice 12

Convert:

() ->
        new ArrayList<>()

Solution

ArrayList::new

Practice 13 — Choose Lambda or Method Reference

Which is clearer?

course ->
        course.priceInPaisa()
        > 500_000

or forcing an unrelated method reference?

Answer

The Lambda।

একটি existing meaningful method না থাকলে simple Lambda clearer।


Practice 14 — Functional Interface Rule

Can this be a Lambda target?

interface Rule {

    boolean test(
            String value
    );

    default String description() {
        return "Rule";
    }
}

Answer

Yes।

Abstract method একটি মাত্র:

test(...)

default method functional-interface status নষ্ট করে না।


Practice 15

Can this be a Lambda target?

interface Rule {

    boolean first(
            String value
    );

    boolean second(
            String value
    );
}

Answer

No।

এখানে দুইটি abstract method আছে।


True or False

  1. Functional Interface-এর একটি abstract method থাকে।
  2. @FunctionalInterface annotation বাধ্যতামূলক।
  3. Functional Interface-এ default method থাকতে পারে।
  4. Predicate<T> সাধারণত T → boolean behavior represent করে।
  5. Function<T, R> value transformation-এর জন্য useful।
  6. Consumer<T> meaningful result return করে।
  7. Supplier<T> কোনো input ছাড়াই value দিতে পারে।
  8. UnaryOperator<T>-এ input এবং output একই type।
  9. BinaryOperator<T> দুইটি same-type input থেকে same-type output দেয়।
  10. Predicate composition-এর জন্য and(), or(), negate() আছে।
  11. Method Reference :: syntax ব্যবহার করে।
  12. Method Reference method-কে immediately execute করে।
  13. System.out::println একটি bound instance method reference।
  14. String::length conceptually value -> value.length() represent করতে পারে।
  15. Method Reference সবসময় Lambda-এর চেয়ে better।

Answers

1. True
2. False
3. True
4. True
5. True
6. False
7. True
8. True
9. True
10. True
11. True
12. False
13. True
14. True
15. False

Knowledge Check

Question 1

Functional Interface কী?

Question 2

@FunctionalInterface কেন useful?

Question 3

Predicate<T> কী ধরনের behavior represent করে?

Question 4

Function<T, R> এবং Consumer<T>-এর মূল difference কী?

Question 5

Supplier<T> কখন useful?

Question 6

UnaryOperator<T> এবং Function<T, T>-এর মধ্যে conceptual difference কী?

Question 7

BinaryOperator<T> কী represent করে?

Question 8

Predicate.and() কী করে?

Question 9

Function.andThen() কী করে?

Question 10

Method Reference কী?

Question 11

ClassName::staticMethod এবং object::instanceMethod-এর difference কী?

Question 12

Method Reference-এর বদলে Lambda কখন clearer হতে পারে?


Knowledge Check Answers

Answer 1

Functional Interface হলো এমন interface যার একটি মাত্র abstract method থাকে এবং যার behavior Lambda Expression দিয়ে implement করা যায়।

Answer 2

এটি compiler-কে জানায় যে interface-টি functional interface হিসেবেই intended। Accidentally দ্বিতীয় abstract method যোগ হলে compiler error দেয়।

Answer 3

এটি একটি value নেয় এবং boolean result দেয়:

T → boolean

Answer 4

Function<T, R> input transform করে একটি result return করে।

T → R

Consumer<T> input নেয় কিন্তু meaningful return value দেয় না।

T → void

Answer 5

যখন কোনো input ছাড়াই value create বা provide করতে চাই।

Example:

Object factory
Lazy value
Exception creation

Answer 6

দুইটিই technically same-type transformation represent করতে পারে।

কিন্তু UnaryOperator<T> বেশি specificভাবে বলে:

T → T

Answer 7

দুইটি same-type value নিয়ে same-type result দেয়:

(T, T) → T

Answer 8

দুইটি Predicate combine করে এমন Predicate তৈরি করে যা তখনই true যখন দুটিই true।

Answer 9

প্রথম Function-এর result দ্বিতীয় Function-এর input হিসেবে দেয়।

Conceptually:

A → B

Answer 10

Method Reference হলো existing method বা constructor-কে functional behavior হিসেবে refer করার concise syntax।

Example:

System.out::println

Answer 11

ClassName::staticMethod

একটি static method refer করে।

object::instanceMethod

আগে থেকেই থাকা একটি specific object-এর instance method refer করে।

Answer 12

যখন Lambda additional logic করে, arguments rearrange করে, condition apply করে, অথবা method reference পড়তে বেশি confusing হয়।


Practical Selection Guide

যদি behavior হয়:

T → boolean

use:

Predicate<T>

যদি:

T → R

use:

Function<T, R>

যদি:

T → void

use:

Consumer<T>

যদি:

() → T

use:

Supplier<T>

যদি:

T → T

use:

UnaryOperator<T>

যদি:

(T, T) → T

use:

BinaryOperator<T>

Method Reference Cheat Sheet

Static method:

Integer::parseInt

Conceptually:

value ->
        Integer.parseInt(
                value
        )

Instance method on existing object:

System.out::println

Conceptually:

value ->
        System.out.println(
                value
        )

Instance method on incoming object:

String::length

Conceptually:

value ->
        value.length()

Record accessor:

Course::title

Conceptually:

course ->
        course.title()

Constructor:

ArrayList::new

Conceptually:

() ->
        new ArrayList<>()

Core Mental Model

Lambda Expression এবং Functional Interface-এর relationship মনে রাখার সবচেয়ে সহজ উপায়:

Functional Interface
→ behavior-এর shape define করে

Lambda
→ সেই behavior-এর implementation দেয়

Example:

Predicate<Course>

defines:

Course → boolean

Lambda:

course ->
        course.priceInPaisa()
        > 0

gives the actual rule।

আর যদি Lambda শুধু existing method call করে:

course ->
        course.title()

তখন আমরা অনেক সময় লিখতে পারি:

Course::title

Lesson Summary

এই lesson-এ আমরা Modern Java-এর functional foundation-এর গুরুত্বপূর্ণ অংশ শিখেছি।

আমরা শিখেছি:

  • Functional Interface-এ একটি abstract method থাকে
  • Lambda সেই abstract behavior-এর implementation দেয়
  • @FunctionalInterface intent enforce করতে সাহায্য করে
  • default এবং static methods functional interface-এ থাকতে পারে
  • Predicate<T> condition represent করে
  • Function<T, R> transformation represent করে
  • Consumer<T> side-effecting consumption represent করে
  • Supplier<T> input ছাড়া value provide করে
  • UnaryOperator<T> same-type transformation represent করে
  • BinaryOperator<T> দুইটি same-type input combine করে
  • Predicate composition-এর জন্য and(), or(), negate() আছে
  • Function composition-এর জন্য andThen() এবং compose() আছে
  • Standard functional interfaces unnecessary custom interfaces কমাতে পারে
  • Domain-specific behavior-এর জন্য custom functional interface meaningful হতে পারে
  • Method Reference existing behavior-কে conciseভাবে refer করে
  • :: Method Reference syntax
  • Static method, instance method এবং constructor reference আলাদা pattern
  • Method Reference Lambda-এর replacement নয়
  • Lambda clearer হলে Lambda ব্যবহার করা উচিত
  • Method Reference clearer হলে unnecessary forwarding Lambda বাদ দেওয়া যায়

সবচেয়ে গুরুত্বপূর্ণ mapping:

Predicate
→ Is this true?

Function
→ What does this become?

Consumer
→ What should I do with this?

Supplier
→ Give me a value.

UnaryOperator
→ Transform this into the same type.

BinaryOperator
→ Combine these two values.

Next Lesson

পরবর্তী lesson:

Introduction to the Stream API

আমরা শিখব:

  • Stream কী
  • Collection এবং Stream-এর difference
  • Stream pipeline
  • Source
  • Intermediate operation
  • Terminal operation
  • stream()
  • filter()
  • map()
  • forEach()
  • toList()
  • Lazy evaluation
  • Stream একবার consume করা যায় কেন
  • Stream data store করে না কেন
  • Loop বনাম Stream
  • কখন Stream code clearer করে
  • কখন traditional loop better