Functional Programming
Functional Interfaces
Single Abstract Method
What makes an interface work with lambdas? It must have exactly one abstract
method - a functional interface. Java provides Function, Predicate, Consumer,
and more. You can create your own too.
Define a functional interface
Interface with exactly one abstract method.
public class Definition {
@FunctionalInterface
interface Formatter {
String format(String input);
default String formatOrEmpty(String input) {
if (input == null) return "";
return format(input);
}
static Formatter identity() {
return s -> s;
}
}
public static void main(String[] args) {
Formatter upper = s -> s.toUpperCase();
String sample = ;
System.out.println("upper: " + upper.format(sample));
System.out.println("default: '" + upper.formatOrEmpty(null) + "'");
Formatter id = Formatter.identity();
System.out.println("identity: " + id.format("same"));
}
}
public class Definition {
@FunctionalInterface
interface Formatter {
String format(String input);
default String formatOrEmpty(String input) {
if (input == null) return "";
return format(input);
}
static Formatter identity() {
return s -> s;
}
}
public static void main(String[] args) {
Formatter upper = s -> s.toUpperCase();
String sample = ;
System.out.println("upper: " + upper.format(sample));
System.out.println("default: '" + upper.formatOrEmpty(null) + "'");
Formatter id = Formatter.identity();
System.out.println("identity: " + id.format("same"));
}
}
public class Definition {
@FunctionalInterface
interface Formatter {
String format(String input);
default String formatOrEmpty(String input) {
if (input == null) return "";
return format(input);
}
static Formatter identity() {
return s -> s;
}
}
public static void main(String[] args) {
Formatter upper = s -> s.toUpperCase();
String sample = ;
System.out.println("upper: " + upper.format(sample));
System.out.println("default: '" + upper.formatOrEmpty(null) + "'");
Formatter id = Formatter.identity();
System.out.println("identity: " + id.format("same"));
}
}
@FunctionalInterface annotation ensures single abstract method.
Built-in functional interfaces
Java's standard functional interfaces.
import java.util.function.*;
public class Builtin {
public static void main(String[] args) {
Predicate<String> isLong = s -> s.length() >= 5;
System.out.println("isLong('cat') = " + isLong.test("cat"));
System.out.println("isLong('tiger') = " + isLong.test("tiger"));
Function<String, Integer> length = s -> s.length();
System.out.println("length('hello') = " + length.apply("hello"));
Consumer<String> printer = s -> System.out.println("print: " + s);
printer.accept("message");
long fixedMillis = 1700000000000L;
Supplier<Long> nowMillis = () -> fixedMillis;
System.out.println("nowMillis() = " + nowMillis.get());
UnaryOperator<String> trim = s -> s.trim();
System.out.println("trim(' hi ') = '" + trim.apply(" hi ") + "'");
BiFunction<Integer, Integer, Integer> max = (a, b) -> a > b ? a : b;
System.out.println("max(3, 7) = " + max.apply(3, 7));
}
}
Function<T,R>, Predicate<T>, Consumer<T>, Supplier<T> - learn these.
Function composition
Chain functions together.
import java.util.function.*;
public class Composition {
public static void main(String[] args) {
Function<String, String> trim = s -> s.trim();
Function<String, String> upper = s -> s.toUpperCase();
Function<String, Integer> length = s -> s.length();
String text = ;
Function<String, String> upperAfterTrim = upper.compose(trim);
System.out.println("upperAfterTrim('" + text + "') = " + upperAfterTrim.apply(text));
Function<String, Integer> lenAfterUpperTrim = upperAfterTrim.andThen(length);
System.out.println("lenAfterUpperTrim('" + text + "') = " + lenAfterUpperTrim.apply(text));
Predicate<String> nonEmpty = s -> !s.isEmpty();
Predicate<String> startsWithA = s -> s.startsWith("A");
Predicate<String> ok = nonEmpty.and(startsWithA);
System.out.println("ok('') = " + ok.test(""));
System.out.println("ok('Bob') = " + ok.test("Bob"));
System.out.println("ok('Alice') = " + ok.test("Alice"));
System.out.println("not startsWithA('Bob') = " + startsWithA.negate().test("Bob"));
Consumer<String> a = s -> System.out.print("[" + s + "]");
Consumer<String> b = s -> System.out.println(" (len=" + s.length() + ")");
Consumer<String> both = a.andThen(b);
both.accept("hello");
}
}
import java.util.function.*;
public class Composition {
public static void main(String[] args) {
Function<String, String> trim = s -> s.trim();
Function<String, String> upper = s -> s.toUpperCase();
Function<String, Integer> length = s -> s.length();
String text = ;
Function<String, String> upperAfterTrim = upper.compose(trim);
System.out.println("upperAfterTrim('" + text + "') = " + upperAfterTrim.apply(text));
Function<String, Integer> lenAfterUpperTrim = upperAfterTrim.andThen(length);
System.out.println("lenAfterUpperTrim('" + text + "') = " + lenAfterUpperTrim.apply(text));
Predicate<String> nonEmpty = s -> !s.isEmpty();
Predicate<String> startsWithA = s -> s.startsWith("A");
Predicate<String> ok = nonEmpty.and(startsWithA);
System.out.println("ok('') = " + ok.test(""));
System.out.println("ok('Bob') = " + ok.test("Bob"));
System.out.println("ok('Alice') = " + ok.test("Alice"));
System.out.println("not startsWithA('Bob') = " + startsWithA.negate().test("Bob"));
Consumer<String> a = s -> System.out.print("[" + s + "]");
Consumer<String> b = s -> System.out.println(" (len=" + s.length() + ")");
Consumer<String> both = a.andThen(b);
both.accept("hello");
}
}
import java.util.function.*;
public class Composition {
public static void main(String[] args) {
Function<String, String> trim = s -> s.trim();
Function<String, String> upper = s -> s.toUpperCase();
Function<String, Integer> length = s -> s.length();
String text = ;
Function<String, String> upperAfterTrim = upper.compose(trim);
System.out.println("upperAfterTrim('" + text + "') = " + upperAfterTrim.apply(text));
Function<String, Integer> lenAfterUpperTrim = upperAfterTrim.andThen(length);
System.out.println("lenAfterUpperTrim('" + text + "') = " + lenAfterUpperTrim.apply(text));
Predicate<String> nonEmpty = s -> !s.isEmpty();
Predicate<String> startsWithA = s -> s.startsWith("A");
Predicate<String> ok = nonEmpty.and(startsWithA);
System.out.println("ok('') = " + ok.test(""));
System.out.println("ok('Bob') = " + ok.test("Bob"));
System.out.println("ok('Alice') = " + ok.test("Alice"));
System.out.println("not startsWithA('Bob') = " + startsWithA.negate().test("Bob"));
Consumer<String> a = s -> System.out.print("[" + s + "]");
Consumer<String> b = s -> System.out.println(" (len=" + s.length() + ")");
Consumer<String> both = a.andThen(b);
both.accept("hello");
}
}
f.andThen(g) - apply f, then g. f.compose(g) - apply g, then f.
Custom generic functional interface
Create your own functional interfaces.
import java.util.*;
public class CustomGeneric {
@FunctionalInterface
interface Validator<T> {
boolean isValid(T value);
default Validator<T> and(Validator<T> other) {
return v -> this.isValid(v) && other.isValid(v);
}
default Validator<T> or(Validator<T> other) {
return v -> this.isValid(v) || other.isValid(v);
}
}
private static <T> List<T> filter(List<T> items, Validator<T> validator) {
List<T> out = new ArrayList<>();
for (T item : items) {
if (validator.isValid(item)) {
out.add(item);
}
}
return out;
}
public static void main(String[] args) {
Validator<String> nonEmpty = s -> s != null && !s.isEmpty();
Validator<String> hasAt = s -> s.contains("@");
Validator<String> emailLike = nonEmpty.and(hasAt);
List<String> inputs = Arrays.asList("", "alice", "alice@example.com", "@", "bob@x");
System.out.println("inputs: " + inputs);
System.out.println("emailLike: " + filter(inputs, emailLike));
Validator<String> empty = s -> s.isEmpty();
Validator<String> emptyOrEmail = empty.or(emailLike);
System.out.println("emptyOrEmail: " + filter(inputs, emptyOrEmail));
}
}
Define with generics for maximum reusability.
Pass behavior as parameter
Methods that accept functional interfaces.
import java.util.*;
import java.util.function.*;
public class PassBehavior {
private static <T, R> List<R> map(List<T> items, Function<T, R> fn) {
List<R> out = new ArrayList<>();
for (T item : items) {
out.add(fn.apply(item));
}
return out;
}
private static <T> List<T> filter(List<T> items, Predicate<T> pred) {
List<T> out = new ArrayList<>();
for (T item : items) {
if (pred.test(item)) {
out.add(item);
}
}
return out;
}
private static int reduceInts(List<Integer> items, int identity, IntBinaryOperator op) {
int acc = identity;
for (int v : items) {
acc = op.applyAsInt(acc, v);
}
return acc;
}
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
System.out.println("lengths: " + map(names, s -> s.length()));
int minLength = ;
System.out.println("long names: " + filter(names, s -> s.length() >= minLength));
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
int sum = reduceInts(nums, 0, (a, b) -> a + b);
int product = reduceInts(nums, 1, (a, b) -> a * b);
System.out.println("sum = " + sum);
System.out.println("product = " + product);
}
}
import java.util.*;
import java.util.function.*;
public class PassBehavior {
private static <T, R> List<R> map(List<T> items, Function<T, R> fn) {
List<R> out = new ArrayList<>();
for (T item : items) {
out.add(fn.apply(item));
}
return out;
}
private static <T> List<T> filter(List<T> items, Predicate<T> pred) {
List<T> out = new ArrayList<>();
for (T item : items) {
if (pred.test(item)) {
out.add(item);
}
}
return out;
}
private static int reduceInts(List<Integer> items, int identity, IntBinaryOperator op) {
int acc = identity;
for (int v : items) {
acc = op.applyAsInt(acc, v);
}
return acc;
}
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
System.out.println("lengths: " + map(names, s -> s.length()));
int minLength = ;
System.out.println("long names: " + filter(names, s -> s.length() >= minLength));
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
int sum = reduceInts(nums, 0, (a, b) -> a + b);
int product = reduceInts(nums, 1, (a, b) -> a * b);
System.out.println("sum = " + sum);
System.out.println("product = " + product);
}
}
import java.util.*;
import java.util.function.*;
public class PassBehavior {
private static <T, R> List<R> map(List<T> items, Function<T, R> fn) {
List<R> out = new ArrayList<>();
for (T item : items) {
out.add(fn.apply(item));
}
return out;
}
private static <T> List<T> filter(List<T> items, Predicate<T> pred) {
List<T> out = new ArrayList<>();
for (T item : items) {
if (pred.test(item)) {
out.add(item);
}
}
return out;
}
private static int reduceInts(List<Integer> items, int identity, IntBinaryOperator op) {
int acc = identity;
for (int v : items) {
acc = op.applyAsInt(acc, v);
}
return acc;
}
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
System.out.println("lengths: " + map(names, s -> s.length()));
int minLength = ;
System.out.println("long names: " + filter(names, s -> s.length() >= minLength));
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
int sum = reduceInts(nums, 0, (a, b) -> a + b);
int product = reduceInts(nums, 1, (a, b) -> a * b);
System.out.println("sum = " + sum);
System.out.println("product = " + product);
}
}
Accept Predicate<T> to let caller define filtering logic.
Exercise: Practical.java
Build a data processor using functional interfaces