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

/** Lambdas in depth: syntax forms, target typing, the four method-reference kinds, capture, identity, `this`. */
public class LambdaInDepth {
    interface Calc { int apply(int a, int b); }
    interface Shout { String go(String s); }
    final String tag = "outer";

    public static void main(String[] args) {
        System.out.println("== 1. syntax forms ==");
        Calc a = (int x, int y) -> x + y;             // explicit types
        Calc b = (x, y) -> x * y;                     // inferred types
        Calc c = (x, y) -> { int s = x - y; return s; };   // block body needs return
        Function<Integer, Integer> d = x -> x * x;    // single parameter, no parentheses
        Supplier<String> e = () -> "no args";
        System.out.println(a.apply(2, 3) + " " + b.apply(2, 3) + " " + c.apply(2, 3) + " " + d.apply(4) + " " + e.get());

        System.out.println("\n== 2. target typing: the same lambda text, two different types ==");
        Function<String, String> f1 = s -> s.toUpperCase();
        Shout f2 = s -> s.toUpperCase();
        System.out.println(f1.apply("abc") + " " + f2.go("abc") + "   <- same body; f1 and f2 are the same type: " + f1.getClass().isInstance(f2));

        System.out.println("\n== 3. the four method-reference kinds ==");
        Function<String, Integer> staticRef = Integer::parseInt;                 // static method
        String prefix = "ab";
        Predicate<String> boundRef = prefix::startsWith;                         // bound instance: receiver ("ab") is fixed now
        Function<String, Integer> unboundRef = String::length;                   // unbound instance: first arg is the receiver
        Supplier<List<String>> ctorRef = ArrayList::new;                         // constructor
        System.out.println(staticRef.apply("42") + " " + boundRef.test("a") + " " + unboundRef.apply("hello") + " " + ctorRef.get());

        System.out.println("\n== 4. capture: values are copied, so locals must be effectively final ==");
        int base = 10;
        Supplier<Integer> cap = () -> base + 1;
        System.out.println("captured base=10, result " + cap.get());
        int[] box = { 0 };
        Runnable inc = () -> box[0]++;
        inc.run(); inc.run();
        System.out.println("array element workaround: " + box[0] + "  (the reference is final, the contents are not)");

        System.out.println("\n== 5. identity: non-capturing lambdas are reused, capturing ones are not ==");
        System.out.println("non-capturing same instance : " + (make() == make()));
        System.out.println("capturing    same instance : " + (makeCapturing(1) == makeCapturing(1)));

        System.out.println("\n== 6. 'this' and class names ==");
        new LambdaInDepth().thisDemo();
    }
    static Supplier<String> make() { return () -> "x"; }
    static Supplier<Integer> makeCapturing(int n) { return () -> n; }

    void thisDemo() {
        Supplier<String> lam = () -> this.tag;
        Supplier<String> anon = new Supplier<>() { final String tag = "anon"; public String get() { return this.tag; } };
        System.out.println("lambda this.tag = " + lam.get() + ", anonymous this.tag = " + anon.get());
        System.out.println("lambda is a synthetic class: " + lam.getClass().isSynthetic() + ", anonymous: " + anon.getClass().isSynthetic());
    }
}
