-
Notifications
You must be signed in to change notification settings - Fork 48
Functional Interfaces 2
An interface with single (one and only) abstract method. @FunctionalInterface is generally used to indicate that the interface is a functional interface. Functional interface is the basis for Java to support functional programming.
JAVA is object-oriented, but for the convenience of writing, it needs to "pass" a method to a method, However, it is not possible to "pass" a method. So, functional interface just "pass" the object of the implementation class of the interface with single (one and only) abstract method. In this way, a similar transfer method is achieved. In fact, Lambda is an object.
Functional Interface | Method | Parameter Type | Return Type | Description |
---|---|---|---|---|
Consumer<T> |
void accept(T t) |
T |
void | Accepts 1 parameter, no return |
Function<T, R> |
R apply(T t) |
T |
R |
Accept 1 parameter, return 1 result |
Predicate<T> |
boolean test(T t) |
T |
boolean | Accepts 1 parameter, returns 1 boolean result |
Supplier<T> |
T get() |
None | T |
No parameter, return 1 result |
UnaryOperator<T> extends Function<T, T> |
R apply(T t) |
T |
T |
Accept 1 parameter, return 1 result, parameters and result have the same type |
BiFunction<T, U, R> |
R apply(T t, U u) |
T, U |
R |
Accepts 2 parameters, returns 1 result |
BinaryOperator<T> extends BiFunction<T ,T, T> |
R apply(T t1, T t2) |
T, T |
T |
Accepts 2 parameters, returns 1 result, parameters and result have the same type |
/* Consumer source code */
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> {
// Call the accept method of current Consumer object
accept(t);
// Call the accept method of next Consumer object
after.accept(t);
};
}
}
To define a Consumer object, the traditional way is as follows:
Consumer c = new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
};
Consumer<Integer> c2 = new Consumer<Integer>() {
@Override
public void accept(Integer x) {
x = x + 1;
System.out.println(x);
}
};
In Java 8, for functional programming interface, it can be defined as follows:
Consumer c = (Object o) -> {
System.out.println(o);
};
Consumer<Integer> c2 = (Integer x) -> {
x = x + 1;
System.out.println(x);
};
Further simplify:
Consumer c = (o) -> {
System.out.println(o);
};
Consumer<Integer> c2 = (x) -> {
x = x + 1;
System.out.println(x);
};
Further simplify:
Consumer c = o -> {
System.out.println(o);
};
Consumer<Integer> c2 = x -> {
x = x + 1;
System.out.println(x);
};
As explained above, functional interface has single (one and only) abstract method. So the compiler will automatically implement this abstract method. If the interface has multiple abstract methods, the compiler does not know which method should be implemented. Therefore, the body of the function after = can be regarded as the implementation of the accept method.
- Input: The part before -> surrounded by (). Consumer has only one input parameter, and the number of input parameters is determined by different functional interfaces. For example, functional interface with two parameters are written as: (a, b), no parameter is written as: ().
- Function body: The part after -> is surrounded by {}. It can be a piece of code.
- Output: Functional programming can have return value or no return value.
When there is only one statement in the function body, {} can be removed for further simplification:
Consumer c = o -> System.out.println(o);
Consumer<Integer> c2 = x -> System.out.println(x + 1);
However, this is not the simplest, here is just calling the println static method in System.out class to directly print the parameter, so it can be further simplified as follow:
/**
* Call the static method println in System.out to print the parameter
* 1. System.out is class
* 2. println is a method in System.out class
* The type of parameter and return matches, which can be simplified:
*/
Consumer c = System.out::println;
// The type of parameter and return matches, but the return is x + 1,
// so the simplification cannot be continued
Through the last piece of code, we can simply understand functional programming. The Consumer interface can be directly regarded as a function. This function receives an parameter and processes it. In essence, it is still an object, but we have omitted the object definition process in the old way, and directly use a piece of code to assign values to the functional interface object. And most importantly, because this functional object is still an object, it can be used as a parameter or return value of other methods.
Definition: Whether to call another Consumer after calling the current Consumer.
private static void consumerAndThen() {
Consumer f1 = System.out::println;
Consumer f2 = n -> System.out.println(n + "-f2");
Consumer f3 = n -> System.out.println(n + "-f3");
// Execute: f1.accept("test1"); -> f2.accept("test1" + "-f2"); -> f3.accept("test1" + "-f3");
f1.andThen(f2).andThen(f3).accept("test1");
// Execute: f1.accept("test2"); -> f1.accept("test2"); -> f1.accept("test2"); -> f1.accept("test2");
f1.andThen(f1).andThen(f1).andThen(f1).accept("test2");
}
/* Output */
test1
test1-f2
test1-f3
test2
test2
test2
test2
private static void consumerExample1(Consumer<String> one, Consumer<String> two) {
one.andThen(two).accept("Hello");
}
public static void main(String[] args) {
consumerExample1(
s -> System.out.println(s.toUpperCase()),
s -> System.out.println(s.toLowerCase()));
}
/* Output */
HELLO
hello
private static void formatPrint(Consumer<String> one, Consumer<String> two, String[] array) {
for (String info : array) {
one.andThen(two).accept(info);
}
}
public static void main(String[] args) {
String[] array = {"Liam,Male", "Emma,Female", "Noah,Male"};
formatPrint(
s -> System.out.print("Name: " + s.split(",")[0]),
s -> System.out.println(", Gender: " + s.split(",")[1]),
array);
}
/* Output */
Name: Liam, Gender: Male
Name: Emma, Gender: Female
Name: Noah, Gender: Male
/* Function source code */
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
To define a Function object, the traditional way is as follows:
Function f = new Function() {
@Override
public Object apply(Object o) {
return o;
}
};
// The first is parameter type, the second is return type
Function<Integer, Boolean> f2 = new Function<Integer, Boolean>() {
@Override
public Boolean apply(Integer i) {
return i >= 0;
}
};
In Java 8, for functional programming interface, it can be defined as follows:
Function f = (Object o) -> {
return o;
};
Function<Integer, Boolean> f2 = (Integer x) -> {
return x >= 0;
};
Further simplify:
Function f = (o) -> {
return o;
};
Function<Integer, Boolean> f2 = (x) -> {
return x >= 0;
};
Further simplify:
Function f = o -> {
return o;
};
Function<Integer, Boolean> f2 = x -> {
return x >= 0;
};
When there is only one statement in the function body, {} can be removed for further simplification:
Function f = o -> o;
Function<Integer, Boolean> f2 = x -> x >= 0;
public static void functionExample1() {
Function<Integer, Integer> f = s -> s + 1;
Function<Integer, Integer> g = s -> s * 2;
/**
* Indicates that execute g before f, and use the return of g as the parameter of f
* fog(x) = f(g(x))
* fog(1) = f(g(1)) = f(2) = 3
* It is equivalent to the following code:
* Integer a = g.apply(1);
* System.out.println(f.apply(a));
*/
System.out.println(f.compose(g).apply(1));
/**
* Indicates that after executing f, the return value is used as the parameter to execute g
* gof(x) = g(f(x))
* gof(1) = g(f(1)) = g(2) = 4
* It is equivalent to the following code:
* Integer a = f.apply(1);
* System.out.println(g.apply(a));
*/
System.out.println(f.andThen(g).apply(1));
/**
* The identity method returns a Function without any processing, that is, the parameter is equal to the return
*/
System.out.println(Function.identity().apply("a"));
}
/* Output */
3
4
a
/* Predicate source code */
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}
}
To define a Predicate object, the traditional way is as follows:
Predicate p = new Predicate() {
@Override
public boolean test(Object o) {
return o.equals("abc");
}
};
Predicate<Integer> p2 = new Predicate<Integer>() {
@Override
public boolean test(Integer i) {
return i >= 0;
}
};
In Java 8, for functional programming interface, it can be defined as follows:
Predicate p = (Object o) -> {
return o.equals("abc");
};
Predicate<Integer> p2 = (Integer x) -> {
return x >= 0;
};
Further simplify:
Predicate p = (o) -> {
return o.equals("abc");
};
Predicate<Integer> p2 = (x) -> {
return x >= 0;
};
Further simplify:
Predicate p = o -> {
return o.equals("abc");
};
Predicate<Integer> p2 = x -> {
return x >= 0;
};
When there is only one statement in the function body, {} can be removed for further simplification:
Predicate f = o -> o.equals("abc");
// equals to
Predicate f = o -> "abc".equals(o);
Predicate<Integer> p2 = x -> x >= 0;
However, this is not the simplest, here is just calling the equals static method in String class, so it can be further simplified as follow:
/* Call the static method equals in String */
* 1. String is class
* 2. equals is a method in String class
* The type of parameter and return matches, which can be simplified:
*/
Predicate f = "abc"::equals;
// The type of parameter and return matches, but the return is x >= 0,
// so the simplification cannot be continued
/**
* Find all eligible elements
*/
public static void predicateTest(List<Integer> list, Predicate<Integer> predicate) {
for (Integer n : list) {
if (predicate.test(n)) {
System.out.print(n + " ");
}
}
}
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
System.out.println("\nAll even numbers: ");
predicateTest(list, n -> n % 2 == 0);
System.out.println("\nAll numbers greater than 3: ");
predicateTest(list, n -> n > 3);
}
/* Output */
All even numbers:
2 4 6 8
All numbers greater than 3:
4 5 6 7 8 9
/**
* A string must contain both uppercase "H" and uppercase "W"
*/
private static void predicateAnd(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.and(two).test("Helloworld");
System.out.println("Does the string meet the requirements: " + isValid);
}
public static void main(String[] args) {
predicateAnd(s -> s.contains("H"), s -> s.contains("W"));
}
/* Output */
Does the string meet the requirements: false
/**
* A string must contain uppercase "H" or uppercase "W"
*/
private static void predicateOr(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.or(two).test("Helloworld");
System.out.println("Does the string meet the requirements: " + isValid);
}
public static void main(String[] args) {
predicateOr(s -> s.contains("H"), s -> s.contains("W"));
}
/* Output */
Does the string meet the requirements: true
/**
* Invert the original Predicate
*/
public static void predicateNegate(List<Integer> list, Predicate<Integer> predicate) {
for (Integer n : list) {
if (predicate.negate().test(n)) {
System.out.print(n + " ");
}
}
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
System.out.println("\nAll odd numbers: ");
predicateTest2(list, n -> n % 2 == 0);
System.out.println("\nAll numbers less than or equal to 3: ");
predicateTest2(list, n -> n > 3);
}
/* Output */
All odd numbers:
1 3 5 7 9
All numbers less than or equal to 3:
1 2 3
private static void predicateExample1() {
Predicate<String> p = o -> o.equals("test");
Predicate<String> g = o -> o.startsWith("t");
/**
* negate: Invert the original Predicate
* When calling p.test("test") is True, then calling p.negate().test("test") will be False
*/
System.out.println(p.negate().test("test"));
/**
* and: For the same input value, if multiple Predicates all return True,
* then the result returns True, otherwise returns False
*/
System.out.println(p.and(g).test("test"));
/**
* or: For the same input value, as long as one of multiple Predicates returns True,
* then the result returns True, otherwise it returns False
*/
System.out.println(p.or(g).test("ta"));
}
/* Output */
false
true
true
private static List<String> filter(String[] array, Predicate<String> one,
Predicate<String> two) {
List<String> list = new ArrayList<>();
for (String info : array) {
if (one.and(two).test(info)) {
list.add(info);
}
}
return list;
}
public static void main(String[] args) {
String[] array = {"James,female", "Amelia,female", "Henry,male", "Logan,female"};
List<String> list = filter(array,
s -> "female".equals(s.split(",")[1]),
s -> s.split(",")[0].length() == 5);
System.out.println(list);
}
/* Output */
[James,female, Logan,female]
/* Supplier source code */
@FunctionalInterface
public interface Supplier<T> {
T get();
}
To define a Supplier object, the traditional way is as follows:
Supplier s = new Supplier() {
@Override
public Object get() {
return "abc".toLowerCase();
}
};
Supplier<String> s2 = new Supplier<String>() {
@Override
public String get() {
return "abc".toString();
}
};
In Java 8, for functional programming interface, it can be defined as follows:
Supplier s = () -> {
return "abc".toLowerCase();
};
Supplier<String> s2 = () -> {
return "abc".toString();
};
When there is only one statement in the function body, {} can be removed for further simplification:
Supplier s = () -> "abc".toLowerCase();
Supplier<String> s2 = () -> "abc"::toString;
However, this is not the simplest, here is just calling the toLowerCase static method in String class, so it can be further simplified as follow:
/* Call the static method toLowerCase in String */
* 1. String is class
* 2. toLowerCase is a method in String class
* String.toLowerCase and String.toString don't have parameters, which can be simplified:
*/
Supplier s = "abc"::toLowerCase;
Supplier<String> s2 = "abc"::toString;
private static String supplierExample1(Supplier<String> supplier) {
return function.get();
}
public static void main(String[] args) {
String msgA = "Hello ";
String msgB = "World";
System.out.println(getString(() -> msgA + msgB));
}
/* Output */
Hello World
public static int getMax(Supplier<Integer> supplier) {
return supplier.get();
}
public static void main(String[] args) {
int arr[] = {21, 23, 24, 52, 34, 213};
int maxNum = getMax(() -> {
// Find the maximum value
int max = arr[0];
for (int i : arr) {
if (i > max) {
max = i;
}
}
return max;
});
System.out.println(maxNum);
}
/* Output */
213
/* UnaryOperator source code */
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
To define a UnaryOperator object, the traditional way is as follows:
UnaryOperator u = new UnaryOperator() {
@Override
public Object apply(Object o) {
return o;
}
};
UnaryOperator<Integer> u2 = new UnaryOperator<Integer>() {
@Override
public Integer apply(Integer i) {
return i * i;
}
};
In Java 8, for functional programming interface, it can be defined as follows:
UnaryOperator u = (Object o) -> {
return o;
};
UnaryOperator<Integer> u2 = (Integer i) -> {
return i * i;
};
When there is only one statement in the function body, {} can be removed for further simplification:
UnaryOperator u = (Object o) -> o;
UnaryOperator<Integer> u2 = (Integer i) -> i * i;
public static int unaryOperatorExample1(UnaryOperator<Integer> unaryOperator, int i) {
return unaryOperator.apply(i);
}
public static void main(String[] args) {
System.out.println(unaryOperatorExample1(x -> x * x, 10));
}
/* Output */
100
/* BiFunction source code */
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
To define a BiFunction object, the traditional way is as follows:
BiFunction b = new BiFunction() {
@Override
public Object apply(Object o1, Object o2) {
return o1.equals(o2) ? o1 : o2;
}
};
// The first two are parameter type, the third is return type
BiFunction<Integer, Integer, Integer> b2 = new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer i1, Integer i2) {
return i1 + i2;
}
};
In Java 8, for functional programming interface, it can be defined as follows:
BiFunction b = (Object o1, Object o2) -> {
return o1.equals(o2) ? o1 : o2;
};
BiFunction<Integer, Integer, Integer> b2 = (Integer i1, Integer i2) -> {
return i1 + i2;
};
Further simplify:
BiFunction b = (o1, o2) -> {
return o1.equals(o2) ? o1 : o2;
};
BiFunction<Integer, Integer, Integer> b2 = (i1, i2) -> {
return i1 + i2;
};
When there is only one statement in the function body, {} can be removed for further simplification:
BiFunction b = (o1, o2) -> o1.equals(o2) ? o1 : o2;
BiFunction<Integer, Integer, Integer> b2 = (i1, i2) -> i1 + i2;
Further simplify:
// The type of parameter and return mismatches,
// so the simplification cannot be continued
/**
* Call the static method sum in Integer to calculate the sum
* 1. Integer is class
* 2. sum is a method in Integer class
* The type of parameter and return matches, which can be simplified:
*/
BiFunction<Integer, Integer, Integer> b2 = Integer::sum;
Definition: Take the result returned by BiFunction as the parameter of Function and get a new result.
// (1 + 2) * 10 = 30
BiFunction<Integer, Integer, Integer> b = Integer::sum;
int result = b.andThen(i -> i * 10).apply(1, 2);
System.out.println(result);
/* Output */
30
public static void biFunctionExample(int a, int b, BiFunction<Integer, Integer, Integer> biFunction) {
Consumer<Integer> c = System.out::println;
c.accept(biFunction.apply(a, b));
}
public static void main(String[] args) {
biFunctionExample(1, 2, Integer::sum);
biFunctionExample(1, 2, (i1, i2) -> i1 - i2);
biFunctionExample(1, 2, (i1, i2) -> i1 * i2);
biFunctionExample(1, 2, (i1, i2) -> i1 / i2);
}
/* Output */
3
-1
2
0
public static void biFunctionExample2(int a, int b,
BiFunction<Integer, Integer, Integer> biFunction,
Function<Integer, Integer> function) {
Consumer<Integer> c = System.out::println;
c.accept(biFunction.andThen(function).apply(a, b));
}
public static void main(String[] args) {
biFunctionExample2(1, 2, Integer::sum, i -> i * 10);
biFunctionExample2(1, 2, (i1, i2) -> i1 - i2, i -> i * 10);
biFunctionExample2(1, 2, (i1, i2) -> i1 * i2, i -> i * 10);
biFunctionExample2(1, 2, (i1, i2) -> i1 / i2, i -> i * 10);
}
/* Output */
30
-10
20
0
/* BinaryOperator source code */
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
To define a BinaryOperator object, the traditional way is as follows:
BinaryOperator b = new BinaryOperator() {
@Override
public Object apply(Object o1, Object o2) {
return o1.equals(o2) ? o1 : o2;
}
};
BinaryOperator<Integer> b2 = new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer i1, Integer i2) {
return i1 + i2;
}
};
In Java 8, for functional programming interface, it can be defined as follows:
BinaryOperator b = (Object o1, Object o2) -> {
return o1.equals(o2) ? o1 : o2;
};
BinaryOperator<Integer> b2 = (Integer i1, Integer i2) -> {
return i1 + i2;
};
Further simplify:
BinaryOperator b = (o1, o2) -> {
return o1.equals(o2) ? o1 : o2;
};
BinaryOperator<Integer> b2 = (i1, i2) -> {
return i1 + i2;
};
When there is only one statement in the function body, {} can be removed for further simplification:
BinaryOperator b = (o1, o2) -> o1.equals(o2) ? o1 : o2;
BinaryOperator<Integer> b2 = (i1, i2) -> i1 + i2;
Further simplify:
// The type of parameter and return mismatches,
// so the simplification cannot be continued
/**
* Call the static method sum in Integer to calculate the sum
* 1. Integer is class
* 2. sum is a method in Integer class
* The type of parameter and return matches, which can be simplified:
*/
BinaryOperator<Integer> b2 = Integer::sum;
Definition: Return the smaller of the two elements according to the specified Comparator.
BinaryOperator<Integer> b = BinaryOperator.minBy(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
public static void main(String[] args) {
System.out.println(b.apply(2, 3));
}
/* Output */
2
Definition: Return the larger of the two elements according to the specified Comparator.
BinaryOperator<Integer> b = BinaryOperator.minBy(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
public static void main(String[] args) {
System.out.println(b.apply(2, 3));
}
/* Output */
3
public static void binaryOperatorExample(int a, int b, BinaryOperator<Integer> binaryOperator) {
Consumer<Integer> c = System.out::println;
c.accept(binaryOperator.apply(a, b));
}
public static void main(String[] args) {
binaryOperatorExample(1, 2, Integer::sum);
binaryOperatorExample(1, 2, (i1, i2) -> i1 - i2);
binaryOperatorExample(1, 2, (i1, i2) -> i1 * i2);
binaryOperatorExample(1, 2, (i1, i2) -> i1 / i2);
}
/* Output */
3
-1
2
0
Peer Learning
Codecrunch Contributions
Piazza Contributions
Wiki Contributions
Guides
Setting Up Checkstyle
Setting Up Java
Setting Up MacVim
Setting Up Sunfire
Setting Up Unix For Mac
Setting Up Unix For Windows
Setting Up Vim
Setting up SSH Config
CS2030 Contents
Lecture 1 SummaryCompile-run vs Run-time Summary
Quick Guide To Abstraction
Generics and Variance of Types
Comparable vs Comparator
Summary of completable future
CS2030S Notes
ELI5 Optional.map vs Optional.flatMap
PECS Example Code
Java Collection Framework (Iterator)
Generic
Generic Type Parameter and Generic Wildcard
Calculator
Lambda-Expression
Single Abstract Method (SAM)
Method Reference
Functional Interfaces 2
Simple Usage of Sandbox
Associative-but-not-commutative
Higher Order function
Functional Programming
Calculator With Functor
Eager Evaluation VS Lazy Evaluation
Simple Usage of Lazy Evaluation
Lazy Evaluation for LazyList
Lazy Evaluation for BinaryTree
Stream
Parallel Stream
Optional
Simple Usage of Stream
Asynchronous Programming
Notes on CompletableFuture
Notes on CompletableFuture 2
Simple Usage of CompletableFuture
Mind Map
Exception Handling
Links
CS2030 Java Style Guide
CS2030 Javadoc Specification
JDK 11 Download Link
JDK 11 API Docs
Codecrunch
Piazza Forum