Modern Java

Lambda Expressions

ReadingPreview

আপনি একটি free preview lesson দেখছেন।

Lesson Overview

Modern Java-তে Lambda Expression খুব গুরুত্বপূর্ণ একটি feature।

Lambda ব্যবহার করে আমরা ছোট একটি behavior বা logic-কে conciseভাবে প্রকাশ করতে পারি এবং প্রয়োজন হলে সেই behavior-কে অন্য method-এর কাছে pass করতে পারি।

Example:

number -> number * 2

আরেকটি example:

course ->
        course.priceInPaisa()
        > 500_000

প্রথমবার Lambda syntax দেখলে কিছুটা অস্বাভাবিক মনে হতে পারে।

কিন্তু এর মূল idea খুব simple:

Lambda Expression
→ একটি behavior-এর concise implementation

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

  • কেন Lambda Expression প্রয়োজন
  • Lambda কী represent করে
  • Lambda syntax
  • Single parameter Lambda
  • Multiple parameter Lambda
  • No-parameter Lambda
  • Expression Lambda
  • Block Lambda
  • Return value
  • Type inference
  • Lambda এবং Comparator
  • Collection APIs-এর সাথে Lambda
  • Behavior parameter হিসেবে pass করা
  • Variable capture
  • effectively final
  • Lambda scope
  • Side effects
  • কখন Lambda code পরিষ্কার করে
  • কখন normal method বা loop better

Why Do We Need Lambda Expressions?

ধরুন আমাদের একটি Course model আছে:

record Course(
        String title,
        long priceInPaisa
) {
}

এখন আমরা Course-গুলো price অনুযায়ী sort করতে চাই।

Java-তে Comparator<Course> ব্যবহার করে sorting rule define করা যায়।

Lambda আসার আগে common approach ছিল anonymous class ব্যবহার করা।

Comparator<Course> byPrice =
        new Comparator<Course>() {

            @Override
            public int compare(
                    Course first,
                    Course second
            ) {
                return Long.compare(
                        first.priceInPaisa(),
                        second.priceInPaisa()
                );
            }
        };

তারপর:

courses.sort(
        byPrice
);

Code completely valid।

কিন্তু actual business logic কতটুকু?

মূল logic শুধু:

Long.compare(
        first.priceInPaisa(),
        second.priceInPaisa()
);

বাকি অংশ mainly ceremony:

new Comparator<Course>()
anonymous class
@Override
compare() declaration

ছোট একটি behavior প্রকাশ করতে অনেক boilerplate লিখতে হচ্ছে।

Lambda এই boilerplate কমায়।


Same Comparator with Lambda

একই behavior Lambda দিয়ে:

Comparator<Course> byPrice =
        (
                first,
                second
        ) -> Long.compare(
                first.priceInPaisa(),
                second.priceInPaisa()
        );

এখানে code-এর focus সরাসরি চলে এসেছে:

দুইটি Course কীভাবে compare হবে?

এই behavior-এর উপর।


What Is a Lambda Expression?

Lambda Expression হলো এমন একটি concise syntax যার মাধ্যমে compatible Functional Interface-এর behavior implement করা যায়।

সহজভাবে:

Input
↓
Behavior
↓
Result

Example:

number -> number * 2

এখানে:

Input:
number

Behavior:
number * 2

Result:
doubled number

Lambda Is Not a Standalone Function

Java-তে Lambda Python বা JavaScript-এর standalone function-এর মতো independently exist করে না।

Lambda-এর একটি:

target type

লাগে।

Example:

Comparator<Integer> comparator =
        (
                first,
                second
        ) -> Integer.compare(
                first,
                second
        );

এখানে target type:

Comparator<Integer>

Java Comparator দেখে বুঝতে পারে Lambda-টি কোন behavior implement করছে।


Functional Interface Preview

ধরুন আমরা লিখলাম:

@FunctionalInterface
interface Calculator {

    int calculate(
            int first,
            int second
    );
}

এখানে abstract method একটি:

calculate(...)

তাই Lambda ব্যবহার করে এর implementation দিতে পারি:

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

এখন:

int result =
        addition.calculate(
                10,
                20
        );

Result:

30

Functional Interface আমরা পরের lesson-এ বিস্তারিত শিখব।

এই lesson-এর জন্য শুধু মনে রাখুন:

Lambda
→ Functional Interface-এর behavior implement করে

Basic Lambda Syntax

Lambda-এর basic structure:

(parameters) -> expression

Example:

(
        first,
        second
) -> first + second

আর multiple statements থাকলে:

(parameters) -> {
    statements
}

Example:

(
        first,
        second
) -> {
    int result =
            first + second;

    return result;
}

The Arrow Operator

Lambda syntax-এর:

->

অংশটি parameters এবং behavior আলাদা করে।

Conceptually:

input
->
what to do with the input

Example:

name -> name.length()

পড়তে পারেন:

একটি name নাও
তার length return করো

Single Parameter Lambda

একটি parameter থাকলে:

name -> name.length()

লেখা যায়।

এটিও valid:

(name) -> name.length()

একটি inferred parameter-এর ক্ষেত্রে parentheses optional।


Another Single Parameter Example

number -> number > 0

Meaning:

একটি number নাও
number positive কি না return করো

Multiple Parameters

দুই বা তার বেশি parameters থাকলে parentheses লাগবে।

Example:

(
        first,
        second
) -> first + second

Incorrect:

first, second -> first + second

No Parameters

কোনো parameter না থাকলে empty parentheses ব্যবহার করতে হয়।

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

আরেকটি example:

() -> new ArrayList<>()

Expression Lambda

একটি Lambda যদি শুধু একটি expression return করে, তাহলে braces এবং return প্রয়োজন হয় না।

Example:

number -> number * 2

এখানে result automatically:

number * 2

Another Expression Lambda

course ->
        course.priceInPaisa()
        > 0

এটি একটি boolean expression।

Result হবে:

true

অথবা:

false

Block Lambda

একাধিক statement প্রয়োজন হলে braces ব্যবহার করতে হয়।

number -> {
    int doubled =
            number * 2;

    System.out.println(
            doubled
    );

    return doubled;
}

এটিকে বলা যায়:

Block Lambda

Return from a Block Lambda

যদি block Lambda-এর target interface result expect করে, তাহলে explicit return প্রয়োজন।

Correct:

(
        first,
        second
) -> {
    int result =
            first + second;

    return result;
}

Incorrect:

(
        first,
        second
) -> {
    int result =
            first + second;
}

যদি return value expected হয়, দ্বিতীয় version compile করবে না।


Expression Lambda vs Block Lambda

Simple behavior হলে:

number -> number * 2

prefer করা যায়।

Unnecessarily:

number -> {
    return number * 2;
}

লিখলে code বড় হয়, কিন্তু clarity বাড়ে না।


Void Lambda

সব Lambda value return করে না।

Example:

message ->
        System.out.println(
                message
        )

এখানে behavior হলো:

message print করা

Meaningful return value নেই।


Multiple Statement Void Lambda

message -> {
    System.out.println(
            "Message received"
    );

    System.out.println(
            message
    );
}

এখানে return দরকার নেই।


Type Inference

Lambda parameters-এর type অনেক সময় explicitly লিখতে হয় না।

Example:

Comparator<String> byLength =
        (
                first,
                second
        ) -> Integer.compare(
                first.length(),
                second.length()
        );

আমরা লিখিনি:

String first
String second

তবুও Java জানে এগুলো String

কেন?

Target type:

Comparator<String>

Explicit Parameter Types

চাইলে type explicitly লেখা যায়:

Comparator<String> byLength =
        (
                String first,
                String second
        ) -> Integer.compare(
                first.length(),
                second.length()
        );

কিন্তু type obvious হলে inferred version সাধারণত cleaner।


Do Not Mix Parameter Styles

এভাবে লেখা যাবে না:

(
        String first,
        second
) -> ...

Either:

(
        String first,
        String second
) -> ...

or:

(
        first,
        second
) -> ...

Lambda with Comparator

Comparator Lambda-এর খুব common use case।

Suppose:

List<Integer> numbers =
        new ArrayList<>(
                List.of(
                        30,
                        10,
                        20
                )
        );

Ascending sort:

numbers.sort(
        (
                first,
                second
        ) -> Integer.compare(
                first,
                second
        )
);

Result:

10
20
30

Descending Comparator

numbers.sort(
        (
                first,
                second
        ) -> Integer.compare(
                second,
                first
        )
);

Result:

30
20
10

Lambda with Custom Objects

Suppose:

record Course(
        String title,
        long priceInPaisa
) {
}

Sort by price:

courses.sort(
        (
                first,
                second
        ) -> Long.compare(
                first.priceInPaisa(),
                second.priceInPaisa()
        )
);

Standard Comparator API Can Be Better

উপরের Lambda valid।

কিন্তু Java already provides:

Comparator.comparingLong(
        Course::priceInPaisa
)

So production code-এ:

courses.sort(
        Comparator.comparingLong(
                Course::priceInPaisa
        )
);

আরও expressive হতে পারে।

Important principle:

Lambda জানি বলে সবকিছু Lambda দিয়ে লিখতে হবে না।

Clearer standard API থাকলে সেটি ব্যবহার করুন।


Lambda with removeIf()

Suppose:

List<Integer> scores =
        new ArrayList<>(
                List.of(
                        40,
                        85,
                        30,
                        95,
                        70
                )
        );

আমরা 50-এর কম scores remove করতে চাই।

scores.removeIf(
        score -> score < 50
);

Result:

85
95
70

Reading the Lambda

এই Lambda:

score -> score < 50

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

একটি score নাও।

score যদি 50-এর কম হয়,
true return করো।

removeIf() true পাওয়া elements remove করবে।


Lambda with forEach()

Suppose:

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

Print:

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

Equivalent Traditional Loop

একই কাজ:

for (
        String name
        : names
) {
    System.out.println(
            name
    );
}

দুইটিই valid।


Lambda Does Not Automatically Mean Better Code

Simple operation-এর ক্ষেত্রে:

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

clean হতে পারে।

কিন্তু complex control flow হলে traditional loop clearer হতে পারে।

Example requirements:

condition check
continue
break
multiple state changes

এগুলো normal loop-এ অনেক সময় সহজে বোঝা যায়।


Behavior as a Parameter

Lambda-এর সবচেয়ে powerful conceptগুলোর একটি:

method-এর কাছে শুধু data নয়,
behavior-ও pass করা যায়।

Normally আমরা লিখি:

process(
        course
);

এখানে course data।

কিন্তু Lambda-এর মাধ্যমে আমরা বলতে পারি:

data নিয়ে কী করা হবে

Custom Behavior Example

Define:

@FunctionalInterface
interface NumberOperation {

    int apply(
            int first,
            int second
    );
}

Then:

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

আরেকটি:

NumberOperation multiplication =
        (
                first,
                second
        ) -> first * second;

Same Interface, Different Behavior

Use:

int sum =
        addition.apply(
                10,
                5
        );

Result:

15

And:

int product =
        multiplication.apply(
                10,
                5
        );

Result:

50

একই interface।

Behavior আলাদা।


Passing the Behavior Directly

আমরা method বানাতে পারি:

static int calculate(
        int first,
        int second,
        NumberOperation operation
) {
    return operation.apply(
            first,
            second
    );
}

Call:

int result =
        calculate(
                10,
                5,
                (
                        first,
                        second
                ) -> first + second
        );

Another Call

int result =
        calculate(
                10,
                5,
                (
                        first,
                        second
                ) -> first * second
        );

calculate() method একই।

Caller decide করছে:

কোন behavior ব্যবহার হবে।

Why Is This Useful?

Without behavior parameterization, আমরা হয়তো লিখতাম:

calculateAddition(...)
calculateMultiplication(...)
calculateMaximum(...)
calculateMinimum(...)

কিছু situations-এ একটি common operation-এর variation behavior হিসেবে pass করা cleaner design দিতে পারে।


But Do Not Over-Abstract

Lambda support আছে বলে এমন method বানানোর দরকার নেই:

method
+ 5 behavior parameters
+ 4 callbacks
+ unclear responsibility

এতে code flexible হলেও difficult to understand হতে পারে।

Use behavior parameterization when variation genuinely meaningful।


Lambda and Object-Oriented Programming

Lambda OOP replace করে না।

Modern Java-তে আমরা একসঙ্গে ব্যবহার করি:

Classes
Objects
Interfaces
Composition
Lambdas
Functional APIs

Lambda mainly small behavior express করা সহজ করে।


Variable Capture

Lambda নিজের বাইরের scope-এর কিছু local variable ব্যবহার করতে পারে।

Example:

int minimumScore =
        80;

scores.removeIf(
        score ->
                score
                < minimumScore
);

এখানে Lambda use করছে:

minimumScore

যেটি Lambda-এর বাইরে declared।

এটিকে বলা হয়:

Variable Capture

Captured Variable

Example:

int threshold =
        100;

Predicate<Integer> rule =
        number ->
                number > threshold;

এখানে Lambda capture করছে:

threshold

Final or Effectively Final

Lambda local variable capture করতে পারবে যদি variable:

final

অথবা:

effectively final

হয়।


Explicit final

final int minimumScore =
        80;

scores.removeIf(
        score ->
                score
                < minimumScore
);

Valid।


Effectively Final

এটিও valid:

int minimumScore =
        80;

scores.removeIf(
        score ->
                score
                < minimumScore
);

আমরা final keyword লিখিনি।

কিন্তু minimumScore পরে reassign করিনি।

তাই variableটি:

effectively final

What Does Effectively Final Mean?

সহজভাবে:

Variable একবার value পেয়েছে
এবং পরে নতুন value assign করা হয়নি।

Example:

int limit =
        10;

যদি আর কোথাও না লিখি:

limit =
        somethingElse;

তাহলে limit effectively final।


Invalid Capture

int minimumScore =
        80;

minimumScore =
        90;

scores.removeIf(
        score ->
                score
                < minimumScore
);

এটি compile করবে না।

কারণ:

minimumScore

reassigned হয়েছে।

এটি effectively final নয়।


Another Invalid Example

int count =
        0;

names.forEach(
        name ->
                count++
);

এটিও compile করবে না।

কারণ Lambda captured local variable:

count

modify করছে।


Why Does Java Restrict Captured Locals?

এই restriction code reasoning সহজ রাখে।

Local variables method execution-এর সাথে tightly connected।

যদি Lambdas freely local variables mutate করতে পারত, তাহলে:

variable lifetime
concurrency
visibility
shared mutable state

reason করা আরও complicated হতো।

Java তাই captured local variable-এর value stable রাখে।


Mutating an Object Is Different

Consider:

List<String> result =
        new ArrayList<>();

names.forEach(
        name ->
                result.add(
                        name
                )
);

এটি compile করে।

কেন?

কারণ local variable:

result

নতুন object-এ reassign হয়নি।

Reference একই আছে।

কিন্তু referenced ArrayList object mutate হয়েছে।


Reference Reassignment vs Object Mutation

এইটি:

result.add(
        value
);

object mutate করে।

এইটি:

result =
        new ArrayList<>();

local variable reference reassign করে।

Lambda capture-এর জন্য এই দুইটি একই বিষয় নয়।


Does Compiling Mean Good Design?

না।

এই code valid:

List<Course> result =
        new ArrayList<>();

courses.forEach(
        course -> {
            if (
                    course.priceInPaisa()
                    > 0
            ) {
                result.add(
                        course
                );
            }
        }
);

কিন্তু traditional loop হয়তো clearer:

for (
        Course course
        : courses
) {
    if (
            course.priceInPaisa()
            > 0
    ) {
        result.add(
                course
        );
    }
}

Later আমরা Stream API শিখব, যেখানে transformation-এর আরও natural functional style দেখব।


Instance Fields and Lambda

Instance field captured local variable নয়।

Example:

class Counter {

    private int count;

    void process(
            List<String> names
    ) {
        names.forEach(
                name ->
                        count++
        );
    }
}

এটি compile করতে পারে।

কিন্তু shared mutable state এবং concurrency থাকলে এটি thread-safety issue তৈরি করতে পারে।

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


Lambda Scope

Lambda parameter Lambda body-এর ভিতরে available।

Example:

course ->
        course.title()

এখানে:

course

শুধু Lambda-এর parameter।


Parameter Naming

Simple example:

x -> x * 2

ঠিক আছে।

কিন্তু business code-এ meaningful names prefer করুন।

Better:

course ->
        course.priceInPaisa()
        > 0

than:

c ->
        c.priceInPaisa()
        > 0

যদিও short Lambdas-এ concise naming acceptable হতে পারে।


Lambda and this

Lambda নিজের আলাদা this introduce করে না।

Instance method-এর ভিতরে Lambda ব্যবহার করলে:

this

সাধারণত enclosing object-কে refer করে।

এটি anonymous class-এর behavior থেকে different।


Lambda vs Anonymous Class

Anonymous class:

Comparator<Course> byPrice =
        new Comparator<Course>() {

            @Override
            public int compare(
                    Course first,
                    Course second
            ) {
                return Long.compare(
                        first.priceInPaisa(),
                        second.priceInPaisa()
                );
            }
        };

Lambda:

Comparator<Course> byPrice =
        (
                first,
                second
        ) -> Long.compare(
                first.priceInPaisa(),
                second.priceInPaisa()
        );

ছোট functional behavior-এর জন্য Lambda অনেক concise।


But Lambda Is Not Just Short Anonymous-Class Syntax

দুইটির মধ্যে differences আছে:

this semantics
scope behavior
runtime representation

তাই Lambda-কে শুধু:

anonymous class-এর shortcut

হিসেবে ভাবা পুরোপুরি accurate নয়।

Better mental model:

Functional Interface-এর behavior implementation

Target Typing

Lambda-এর type context থেকে আসে।

Suppose:

Function<Integer, Integer> doubler =
        number ->
                number * 2;

Target type বলছে:

Input:
Integer

Output:
Integer

এই context ছাড়া:

number -> number * 2

এর exact Java type জানা সম্ভব নয়।


Same Shape, Different Meaning

এই Lambda:

value -> value

different contexts-এ হতে পারে:

String → String

Integer → Integer

Course → Course

Target interface determine করবে actual type।


Lambda with Validation

Lambda body-তে normal Java logic ব্যবহার করা যায়।

Example:

value -> {
    if (
            value < 0
    ) {
        throw new IllegalArgumentException(
                "Value cannot be negative."
        );
    }

    return value * 2;
}

Lambda ব্যবহার করছি বলে normal exception rules disappear করে না।


Keep Lambdas Small

Good Lambda:

course ->
        course.priceInPaisa()
        > 500_000

এক নজরেই intent বোঝা যায়।


Large Lambda

এমন code consider করুন:

course -> {
    validateCourse(
            course
    );

    calculatePrice(
            course
    );

    updateMetrics(
            course
    );

    sendNotifications(
            course
    );

    writeAuditLog(
            course
    );
}

Technically possible।

কিন্তু behavior বড় হয়ে যাচ্ছে।

এখন named method clearer হতে পারে:

courses.forEach(
        course ->
                processCourse(
                        course
                )
);

Extract Meaningful Methods

Instead of:

courses.removeIf(
        course ->
                course.priceInPaisa()
                == 0
                && course.title()
                        .length()
                < 5
                && ...
);

যদি এটি একটি meaningful business rule হয়:

courses.removeIf(
        course ->
                shouldRemoveCourse(
                        course
                )
);

আরও readable হতে পারে।


Business Logic Deserves Names

Suppose একটি Course promotion-এর জন্য eligible কি না determine করতে অনেক rules লাগে।

Long inline Lambda না লিখে:

boolean isEligibleForPromotion(
        Course course
)

method create করা better হতে পারে।

Then:

course ->
        isEligibleForPromotion(
                course
        )

নিজেই meaningful।


Side Effects

Lambda সবসময় pure হতে হবে না।

Example:

name ->
        System.out.println(
                name
        )

print করা একটি:

side effect

আরও side effects:

Collection mutate করা
Field update করা
File লেখা
Network request করা
Logging করা

Pure Lambda

Example:

number ->
        number * 2

এটি input নেয় এবং result return করে।

External state change করে না।

এ ধরনের behavior generally সহজে reason করা যায়।


Side-Effecting Lambda

course -> {
    auditLog.add(
            course.code()
    );

    return course.priceInPaisa()
            > 0;
}

এখানে Lambda দুইটি কাজ করছে:

audit log change করছে
এবং
boolean decision return করছে

এ ধরনের mixed responsibility carefully use করতে হবে।


Lambdas Do Not Replace Loops

Suppose আমরা প্রথম invalid Course খুঁজতে চাই এবং পাওয়ার সাথে সাথে loop stop করতে চাই।

Traditional loop:

for (
        Course course
        : courses
) {
    if (
            !isValid(
                    course
            )
    ) {
        System.out.println(
                "Invalid course: "
                + course.title()
        );

        break;
    }
}

এটি খুব clear।

একই logic জোর করে:

forEach(...)

দিয়ে করা code-কে unnecessarily complicated করতে পারে।


forEach() and Control Flow

Normal loop-এর ভিতরে আমরা ব্যবহার করতে পারি:

break
continue

forEach() Lambda-এর ভিতরে এগুলো একইভাবে ব্যবহার করা যায় না।

তাই:

Lambda syntax shorter

মানেই:

Lambda always better

না।


Example — Sorting by String Length

List<String> titles =
        new ArrayList<>(
                List.of(
                        "Java",
                        "System Design",
                        "Backend"
                )
        );

titles.sort(
        (
                first,
                second
        ) -> Integer.compare(
                first.length(),
                second.length()
        )
);

Result:

Java
Backend
System Design

Add a Secondary Rule

Suppose equal length হলে alphabetical order চাই।

titles.sort(
        (
                first,
                second
        ) -> {
            int byLength =
                    Integer.compare(
                            first.length(),
                            second.length()
                    );

            if (
                    byLength != 0
            ) {
                return byLength;
            }

            return first.compareTo(
                    second
            );
        }
);

এটি valid।


Comparator API Is Clearer Here

Same idea:

titles.sort(
        Comparator.comparingInt(
                String::length
        ).thenComparing(
                Comparator.naturalOrder()
        )
);

এখানে standard API intent আরও clearly communicate করছে।


Custom Functional Interface Example

@FunctionalInterface
interface CourseRule {

    boolean test(
            Course course
    );
}

Then:

CourseRule paidCourse =
        course ->
                course.priceInPaisa()
                > 0;

আরেকটি:

CourseRule expensiveCourse =
        course ->
                course.priceInPaisa()
                >= 500_000;

Applying the Behavior

static boolean matches(
        Course course,
        CourseRule rule
) {
    return rule.test(
            course
    );
}

Call:

boolean result =
        matches(
                course,
                c ->
                        c.priceInPaisa()
                        > 0
        );

Standard Functional Interfaces

Java already provides common functional interfaces।

Examples:

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

তাই সব behavior-এর জন্য custom interface বানাতে হয় না।

পরের lesson-এ এগুলো বিস্তারিত শিখব।


Practical Example — Course Sorting

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<Course> courses =
                new ArrayList<>(
                        List.of(
                                new Course(
                                        "Java Foundation",
                                        300_000
                                ),
                                new Course(
                                        "System Design",
                                        800_000
                                ),
                                new Course(
                                        "Backend Development",
                                        500_000
                                )
                        )
                );

        courses.sort(
                (
                        first,
                        second
                ) -> Long.compare(
                        first.priceInPaisa(),
                        second.priceInPaisa()
                )
        );

        courses.forEach(
                course ->
                        System.out.println(
                                course.title()
                                + " - "
                                + course.priceInPaisa()
                        )
        );
    }

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

Output:

Java Foundation - 300000
Backend Development - 500000
System Design - 800000

Practical Example — Passing Behavior

public class Main {

    public static void main(String[] args) {
        int addition =
                calculate(
                        10,
                        5,
                        (
                                first,
                                second
                        ) -> first + second
                );

        int multiplication =
                calculate(
                        10,
                        5,
                        (
                                first,
                                second
                        ) -> first * second
                );

        System.out.println(
                addition
        );

        System.out.println(
                multiplication
        );
    }

    static int calculate(
            int first,
            int second,
            NumberOperation operation
    ) {
        return operation.apply(
                first,
                second
        );
    }

    @FunctionalInterface
    interface NumberOperation {

        int apply(
                int first,
                int second
        );
    }
}

Output:

15
50

Practice 1 — Lambda Parameter

Given:

number -> number * 2

Parameter কোনটি?

Answer

number

Practice 2 — Behavior

name -> name.length()

এই Lambda কী করে?

Answer

একটি name নেয় এবং তার length return করে।


Practice 3 — Two Parameters

দুইটি integer যোগ করার Lambda লিখুন।

Solution

(
        first,
        second
) -> first + second

Practice 4 — No Parameters

Started print করার Lambda লিখুন।

Solution

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

Practice 5 — Block Lambda

এই Lambda:

number -> number * 2

block form-এ লিখুন।

Solution

number -> {
    return number * 2;
}

Practice 6 — Comparator

Ascending integer comparator Lambda:

Solution

(
        first,
        second
) -> Integer.compare(
        first,
        second
)

Practice 7 — Descending Comparator

Solution

(
        first,
        second
) -> Integer.compare(
        second,
        first
)

Practice 8 — removeIf()

Length 4-এর কম String remove করুন।

Solution

values.removeIf(
        value ->
                value.length()
                < 4
);

Practice 9 — Variable Capture

Valid?

int minimum =
        50;

scores.removeIf(
        score ->
                score
                < minimum
);

Answer

Yes।

minimum effectively final।


Practice 10 — Invalid Capture

Valid?

int minimum =
        50;

minimum =
        60;

scores.removeIf(
        score ->
                score
                < minimum
);

Answer

No।

minimum reassigned হয়েছে, তাই effectively final নয়।


Practice 11 — Object Mutation

এটি compile করবে?

List<Integer> result =
        new ArrayList<>();

values.forEach(
        value ->
                result.add(
                        value
                )
);

Answer

Yes।

result reference reassign করা হয়নি।

Referenced ArrayList mutate হয়েছে।


Practice 12 — Large Lambda

একটি Lambda-এর ভিতরে আছে:

Validation
Logging
Multiple branches
Error handling
Database-like operations

এটি inline রাখা উচিত?

Answer

সাধারণত না।

Meaningful named method বা abstraction extract করা clearer।


Practice 13 — Target Type

এই code-এ Java কীভাবে জানে first এবং second হলো Course?

Comparator<Course> comparator =
        (
                first,
                second
        ) -> ...

Answer

Target type:

Comparator<Course>

Lambda parameter types determine করতে compiler-কে context দেয়।


Practice 14 — Loop or Lambda?

Requirement:

First invalid element পেলে processing stop করতে হবে।

কোনটি সাধারণত clearer?

Answer

Traditional for loop।

কারণ:

break

naturalভাবে ব্যবহার করা যায়।


True or False

  1. Lambda Expression behavior represent করতে পারে।
  2. Lambda কোনো target type ছাড়াই standalone Java function।
  3. একটি parameter হলে parentheses অনেক সময় omit করা যায়।
  4. Multiple parameters হলে parentheses লাগে।
  5. Expression Lambda explicit return ছাড়াই result দিতে পারে।
  6. Block Lambda result দিলে explicit return লাগতে পারে।
  7. Java target type থেকে Lambda parameter type infer করতে পারে।
  8. Captured local variable freely reassign করা যায়।
  9. Captured local variable final বা effectively final হতে হয়।
  10. Effectively-final reference-এর object mutate করা সম্ভব।
  11. সব traditional loop Lambda দিয়ে replace করা উচিত।
  12. বড় business logic inline Lambda-তে রাখাই best।
  13. Comparator Lambda-এর common use case।
  14. Lambda OOP replace করে।
  15. Clearer standard API থাকলে Lambda manually লেখা বাধ্যতামূলক নয়।

Answers

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

Knowledge Check

Question 1

Lambda Expression কী?

Question 2

Lambda কেন useful?

Question 3

-> কী separate করে?

Question 4

Expression Lambda এবং Block Lambda-এর difference কী?

Question 5

Java কীভাবে Lambda parameter-এর type infer করে?

Question 6

Lambda-এর target type কেন প্রয়োজন?

Question 7

Variable capture কী?

Question 8

effectively final বলতে কী বোঝায়?

Question 9

Captured local variable কেন reassign করা যায় না?

Question 10

কেন একটি captured ArrayList mutate করা যায় কিন্তু local reference reassign করা যায় না?

Question 11

কখন Lambda-এর বদলে normal method better?

Question 12

কখন traditional loop Lambda-based forEach()-এর চেয়ে better?


Knowledge Check Answers

Answer 1

Lambda Expression হলো compatible Functional Interface-এর একটি behavior conciseভাবে implement করার syntax।

Example:

number ->
        number * 2

Answer 2

Lambda ছোট behavior-এর জন্য anonymous class-এর boilerplate কমায় এবং behavior-কে method-এর কাছে parameter হিসেবে pass করা সহজ করে।

Answer 3

->

Lambda parameters এবং Lambda body বা behavior আলাদা করে।

Answer 4

Expression Lambda একটি expression দিয়ে result দিতে পারে:

number ->
        number * 2

Block Lambda braces-এর মধ্যে multiple statements রাখতে পারে:

number -> {
    int result =
            number * 2;

    return result;
}

Answer 5

Expected Functional Interface বা target type compiler-কে parameter এবং return type সম্পর্কে information দেয়।

Answer 6

Lambda নিজে standalone named type নয়। কোন functional behavior implement হচ্ছে তা target interface determine করে।

Answer 7

Lambda যখন নিজের বাইরের local variable ব্যবহার করে, তাকে variable capture বলা হয়।

Example:

int limit =
        10;

value ->
        value > limit

Answer 8

একটি local variable initial value পাওয়ার পরে যদি আর reassign না হয়, তাহলে explicit final না থাকলেও সেটি effectively final।

Answer 9

Java captured local variables-এর value stable রাখে, যাতে scope, state এবং concurrency reasoning manageable থাকে।

Answer 10

কারণ local reference একই object-কে point করছে, তাই reference effectively final থাকতে পারে। Object-এর internal state পরিবর্তন করা reference reassign করার সমান নয়।

Answer 11

যখন behavior:

দীর্ঘ
multiple branches আছে
important business rule represent করে
error handling বেশি
অনেক side effect আছে

তখন named method clearer হতে পারে।

Answer 12

যখন:

break
continue
early exit
complex mutable state

প্রয়োজন হয়, traditional loop সাধারণত clearer।


Lambda Syntax Cheat Sheet

No Parameters

() ->
        doSomething()

One Parameter

value ->
        process(
                value
        )

Multiple Parameters

(
        first,
        second
) ->
        first + second

Expression Returning a Value

value ->
        value * 2

Block Returning a Value

value -> {
    int result =
            value * 2;

    return result;
}

Void Behavior

value ->
        System.out.println(
                value
        )

Practical Lambda Checklist

Lambda লেখার আগে নিজেকে জিজ্ঞেস করুন:

কোন Functional Interface expected?

Lambda কী input নিচ্ছে?

কী result দিচ্ছে?

একটি expression যথেষ্ট কি?

কোন external variable capture করছি?

Captured local variable effectively final কি?

Lambda unnecessary side effect করছে কি?

Lambda body খুব বড় হয়ে যাচ্ছে কি?

Named method intent আরও ভালোভাবে explain করবে কি?

Java standard library-তে clearer helper already আছে কি?

Core Mental Model

Lambda-কে শুধু:

short syntax

হিসেবে ভাববেন না।

Better mental model:

একটি method কিছু data পেয়েছে।

এখন caller সেই method-কে
কীভাবে কাজ করতে হবে
সেই behavior-টিও দিতে পারে।

Example questions:

এই Course-গুলো কোন rule অনুযায়ী sort হবে?

কোন Course remove হবে?

প্রতিটি item নিয়ে কী করা হবে?

দুইটি number কীভাবে combine হবে?

Lambda সেই:

behavior

provide করে।


Lesson Summary

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

আমরা শিখেছি:

  • Lambda ছোট behavior conciseভাবে express করে
  • Lambda একটি compatible Functional Interface-এর behavior implement করে
  • Lambda standalone Java function নয়
  • Lambda-এর একটি target type প্রয়োজন
  • -> parameters এবং behavior আলাদা করে
  • একটি parameter-এর parentheses optional হতে পারে
  • Multiple parameters-এর জন্য parentheses লাগে
  • No-parameter Lambda () ব্যবহার করে
  • Expression Lambda conciseভাবে result return করতে পারে
  • Block Lambda multiple statements support করে
  • Java target type থেকে parameter type infer করতে পারে
  • Lambda Comparator-এর সাথে খুব commonly used হয়
  • removeIf() এবং forEach() Lambda behavior নিতে পারে
  • Behavior method parameter হিসেবে pass করা যায়
  • Lambda OOP-এর complement, replacement নয়
  • Lambda বাইরের local variable capture করতে পারে
  • Captured local variable final বা effectively final হতে হয়
  • Object mutation এবং local reference reassignment একই বিষয় নয়
  • Side effects Lambda-এর ভিতরে possible, কিন্তু intentionally ব্যবহার করা উচিত
  • Lambda ছোট এবং focused রাখা ভালো
  • Complex business logic named method-এ রাখা clearer হতে পারে
  • Traditional loops এখনো অনেক situation-এ better
  • Standard Java APIs clearer হলে unnecessary manual Lambda এড়িয়ে যাওয়া উচিত

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

আগে আমরা method-এর কাছে data pass করতাম।

Modern Java-তে functional interfaces-এর মাধ্যমে
behavior-ও pass করতে পারি।

আর Lambda Expression সেই behavior লেখাকে concise এবং practical করে।


Next Lesson

পরবর্তী lesson:

Functional Interfaces and Method References

আমরা শিখব:

  • Functional Interface কী
  • @FunctionalInterface
  • Predicate<T>
  • Function<T, R>
  • Consumer<T>
  • Supplier<T>
  • UnaryOperator<T>
  • BinaryOperator<T>
  • Behavior composition
  • Method Reference
  • ClassName::staticMethod
  • object::instanceMethod
  • ClassName::instanceMethod
  • Constructor references
  • Lambda এবং Method Reference-এর মধ্যে practical choice