Lambda Expressions & Functional Interfaces

Lambda ও Functional Interface — Java 8 থেকে function-ও value

Read: ~35 min Intermediate 5 practice problems Live code runner

1. Functions as Values

Before Java 8, behavior was wrapped in anonymous inner classes — five lines of ceremony for one line of logic. A lambda expression is a compact way to supply a function where a functional interface is expected. A functional interface is simply any interface with exactly one abstract method (SAM — Single Abstract Method).

Java 8-এর আগে behavior দিতে anonymous inner class লিখতে হতো — এক লাইন logic-এর জন্য পাঁচ লাইন ceremony। Lambda হলো ঐ functional interface-এ এক-লাইন function দেওয়ার সংক্ষিপ্ত রূপ। Functional interface মানে — যার মধ্যে ঠিক একটি abstract method আছে (SAM — Single Abstract Method)।
Main.java
import java.util.*;
import java.util.function.*;

class Main {
    public static void main(String[] args) {
        // Old style — anonymous inner class
        Runnable r1 = new Runnable() {
            public void run() { System.out.println("old school"); }
        };
        r1.run();

        // New style — lambda
        Runnable r2 = () -> System.out.println("lambda!");
        r2.run();
    }
}

2. Lambda Syntax

Every lambda is (parameters) -> body. One-parameter lambdas may skip parentheses; single-expression bodies skip braces and the return.

প্রতিটি lambda-র form: (parameters) -> body। এক parameter হলে বন্ধনী বাদ যায়; এক-expression body হলে braces ও return বাদ যায়।
Lambda Anatomy (x, y) -> x + y s -> s.length() (int n) -> { if (n < 0) return 0; return n; } Figure 27.1 — তিন রকম lambda: দুই argument, এক argument (বন্ধনী বাদ), এবং multi-statement body।
Main.java
import java.util.*;
import java.util.function.*;

class Main {
    public static void main(String[] args) {
        BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
        System.out.println("add = " + add.apply(3, 4));

        Function<String, Integer> len = s -> s.length();
        System.out.println("len = " + len.apply("Bangladesh"));

        Function<Integer, Integer> clamp = (Integer n) -> {
            if (n < 0) return 0;
            if (n > 100) return 100;
            return n;
        };
        System.out.println("clamp(-5) = " + clamp.apply(-5));
        System.out.println("clamp(150) = " + clamp.apply(150));
    }
}

3. The Four Core Functional Interfaces

java.util.function ships dozens of functional interfaces, but four cover most needs: Predicate, Function, Consumer, Supplier.

java.util.function-এ অনেকগুলো functional interface আছে, কিন্তু চারটিই ৮০% কাজ সামলায়: Predicate, Function, Consumer, Supplier।
InterfaceSignaturePurposeবাংলায়
Predicate<T>T → booleanFiltering/testing.হ্যাঁ/না প্রশ্ন।
Function<T,R>T → RTransform one value to another.রূপান্তর।
Consumer<T>T → voidSide-effect on a value.কোনো কিছু পরিবর্তন/print।
Supplier<T>() → TLazy value produce.চাহিদামতো value তৈরি।
BiFunction<A,B,R>(A,B) → RTwo-arg transform.দুই-arg transform।
UnaryOperator<T>T → TSame-type transform.Same type-এ transform।
Main.java
import java.util.*;
import java.util.function.*;

class Main {
    public static void main(String[] args) {
        Predicate<String> isLong = s -> s.length() > 5;
        Function<String, String> shout = s -> s.toUpperCase();
        Consumer<String> printer = System.out::println;
        Supplier<String> greet = () -> "Hello from lambda!";

        System.out.println("isLong('Arif') = " + isLong.test("Arif"));
        System.out.println("isLong('Bangladesh') = " + isLong.test("Bangladesh"));
        System.out.println("shout = " + shout.apply("java"));
        printer.accept(greet.get());
    }
}

4. Variable Capture — Effectively Final

A lambda can read local variables from its enclosing scope, but those variables must be effectively final — that is, assigned once and never reassigned. This rule prevents subtle bugs when the lambda runs later on another thread.

Lambda নিজের বাইরের scope থেকে local variable পড়তে পারে, কিন্তু সেগুলো effectively final হতে হবে — অর্থাৎ একবার assign-এর পর আর বদলানো যাবে না। এটি thread-safety সমস্যা আগেই ঠেকায়।
Main.java
import java.util.*;
import java.util.function.*;

class Main {
    public static void main(String[] args) {
        int threshold = 10;           // effectively final
        Predicate<Integer> big = n -> n > threshold;
        System.out.println(big.test(5));   // false
        System.out.println(big.test(42));  // true

        // threshold = 20;   // would break: lambda no longer allowed to capture

        // Capturing a field of an object works fine
        int[] counter = { 0 };        // array is effectively final — contents can change
        Runnable tick = () -> counter[0]++;
        tick.run(); tick.run(); tick.run();
        System.out.println("counter = " + counter[0]);
    }
}

5. Defining Your Own Functional Interface

Mark any single-abstract-method interface with @FunctionalInterface. The annotation is optional but catches accidental second-method additions at compile time.

নিজের এক-abstract-method interface তৈরি করে @FunctionalInterface দিয়ে mark করুন — এতে ভুল করে দ্বিতীয় abstract method যোগ করলে compile-time-এ ধরা পড়বে।
Main.java
class Main {

    @FunctionalInterface
    interface DistanceCalc {
        double between(double a, double b);
    }

    public static void main(String[] args) {
        DistanceCalc manhattan = (a, b) -> Math.abs(a - b);
        DistanceCalc squared   = (a, b) -> (a - b) * (a - b);

        System.out.println("manhattan = " + manhattan.between(3, 10));
        System.out.println("squared   = " + squared.between(3, 10));
    }
}

6. Vocabulary

TermMeaningবাংলায়
SAMSingle Abstract Method — the rule for a functional interface.ঠিক একটি abstract method — functional interface-এর শর্ত।
LambdaCompact syntax: (params) -> body.সংক্ষিপ্ত syntax (params) -> body।
Effectively finalAssigned once, never reassigned.একবার assign, আর কখনো নয়।
@FunctionalInterfaceMarker annotation enforcing SAM.SAM check করার annotation।
java.util.functionStandard library of common functional interfaces.Standard functional interface-এর package।

7. Practice Problems

  1. Write a Predicate<String> that returns true for strings starting with "A".
    "A" দিয়ে শুরু হওয়া string-এর জন্য true দেয় এমন Predicate<String> লিখুন।
    ✨ Show Answer
    Main.java
    import java.util.function.*;
    class Main {
        public static void main(String[] args) {
            Predicate<String> startsA = s -> s.startsWith("A");
            System.out.println(startsA.test("Arif"));
            System.out.println(startsA.test("Rina"));
        }
    }
  2. Implement a Function<Integer, Integer> that doubles its argument; test with 7.
    Function<Integer, Integer> লিখুন যা argument দ্বিগুণ করে; 7 দিয়ে পরীক্ষা করুন।
    ✨ Show Answer
    Main.java
    import java.util.function.*;
    class Main {
        public static void main(String[] args) {
            Function<Integer, Integer> dbl = n -> n * 2;
            System.out.println(dbl.apply(7));
        }
    }
  3. Sort a list of strings by length using a Comparator lambda.
    string-এর list-কে length অনুযায়ী Comparator lambda দিয়ে sort করুন।
    ✨ Show Answer
    Main.java
    import java.util.*;
    class Main {
        public static void main(String[] args) {
            List<String> xs = new ArrayList<>(List.of("hi", "hello", "yo", "welcome"));
            xs.sort((a, b) -> Integer.compare(a.length(), b.length()));
            System.out.println(xs);
        }
    }
  4. Explain "effectively final" in 2–3 sentences.
    "effectively final" মানে 2–3 বাক্যে ব্যাখ্যা করুন।
    ✨ Show Answer

    Answer: A local variable is "effectively final" if, even without the final keyword, it is assigned exactly once and never reassigned. Lambdas and inner classes can only capture such variables because the value must remain stable if the lambda runs later on another thread. Add final explicitly if you want the compiler to enforce it.

    একটি local variable "effectively final" যদি final keyword না থাকলেও সেটি শুধু একবার assign হয় এবং আর কখনো পরিবর্তিত হয় না। Lambda বা inner class এই ধরনের variable-ই capture করতে পারে — কারণ লাইফটাইম/thread bound-এর কারণে value স্থির থাকা দরকার। চাইলে final keyword লিখে compiler-কে enforce করতে বলুন।

  5. Use a BiFunction<Integer, Integer, Integer> to compute GCD of two numbers.
    BiFunction দিয়ে দুটি সংখ্যার GCD বের করুন।
    ✨ Show Answer
    Main.java
    import java.util.function.*;
    class Main {
        public static void main(String[] args) {
            BiFunction<Integer, Integer, Integer> gcd = (a, b) -> {
                while (b != 0) { int t = b; b = a % b; a = t; }
                return a;
            };
            System.out.println(gcd.apply(48, 18));
            System.out.println(gcd.apply(100, 75));
        }
    }

Summary — Module 27

A lambda is a compact way to supply a single-abstract-method (SAM) function. Java 8's java.util.function gives you Predicate, Function, Consumer, Supplier, and their arity/type variants. Captured local variables must be effectively final. Use @FunctionalInterface on your own SAM interfaces for compile-time safety.

Lambda হলো SAM-interface-এ এক-লাইন function দেওয়ার সংক্ষিপ্ত syntax। java.util.function-এ Predicate/Function/Consumer/Supplier ইত্যাদি রেডি। Captured variable effectively final হতে হবে। নিজের SAM interface-এ @FunctionalInterface দিন।

Next Module → Method Reference — lambda-র আরও সংক্ষিপ্ত রূপ।