Writing in the front
As mentioned earlier, an important criterion for determining whether a language supports functional programming is whether it treats functions as “first-class citizens.”
Functions are “first class citizens,” meaning they have the same status as other data types — they can be assigned to a variable, used as an argument to another function, or returned as a value from another function.
Java 8 gives functions the ability to be “first-class citizens” through functional interfaces.
This article takes a closer look at functional interfaces in Java 8.
Sample code for this article is available at gitee, gitee.com/cnmemset/ja…
Complete column access, can pay attention to the public number [member said].
Functional interface
What is a function interface? All interfaces with only one abstract method are functional interfaces.
As per the specification, it is strongly recommended that you annotate @functionalinterface when defining a FunctionalInterface so that it can be determined at compile time whether the interface complies with the FunctionalInterface specification. Of course, you can leave @functionalInterface unannotated, which does not affect the definition and use of functional interfaces.
The following is a typical functional interface:
// The annotation @functionalInterface is strongly recommended
@FunctionalInterface
public interface Consumer {
// The only abstract method
void accept(T t);
// There can be multiple non-abstract methods (default methods)
default Consumer andThen(Consumer after) {
Objects.requireNonNull(after);
return(T t) -> { accept(t); after.accept(t); }; }}Copy the code
A functional interface is essentially an interface, so we can implement a functional interface through a concrete class (including anonymous classes). But unlike normal interfaces, the implementation of a functional interface can also be a lambda expression or even a method reference.
Let’s walk through some of the typical functional interfaces built into the JDK.
Functional interfaces built into Java 8
The new built-in functional interfaces in Java 8 are defined in the java.util.function package and include:
1. Functions
Function
In the code world, the most common type of functional interface is to take a parameter value and return a response value. The JDK provides a standard generic Function interface:
@FunctionalInterface
public interface Function<T.R> {
/** * Given a parameter T of type T, return a response value of type R. * *@paramT function argument *@returnResult */
R apply(T t);
}
Copy the code
A classic use of Function is the computeIfAbsent Function for a Map.
public V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction);
Copy the code
The computeIfAbsent function checks whether the key exists in the map. If the key does not exist, the mappingFunction calculates a value, writes the key/value pair to the map, and returns the calculated value. If the key already exists, the value corresponding to the key in the map is returned.
In a hypothetical application scenario, we want to build a HashMap where key is a word and value is the letter length of the word. Example code is as follows:
public static void testFunctionWithLambda(a) {
// Build a HashMap where key is a word and value is the letter length of the word
Map wordMap = new HashMap<>();
Integer wordLen = wordMap.computeIfAbsent("hello", s -> s.length());
System.out.println(wordLen);
System.out.println(wordMap);
}
Copy the code
The above example would print:
5
{hello=5}
Copy the code
Notice the code snippet “s -> s.length()”, which is a typical lambda expression with the same meaning as a function:
public static int getStringLength(String s) {
return s.length();
}
Copy the code
A more detailed introduction to lambda expressions can be found in a subsequent article series.
As mentioned earlier, functional interfaces can also be implemented through a method reference. Example code is as follows:
public static void testFunctionWithMethodReference(a) {
Map wordMap = new HashMap<>();
Integer wordLen = wordMap.computeIfAbsent("hello", String::length);
System.out.println(wordLen);
System.out.println(wordMap);
}
Copy the code
Noting that methods reference “String:: Length,” Java 8 allows us to convert an instance method into an implementation of a functional interface. It has the same meaning as the lambda expression “s -> s.length()”.
A more detailed introduction to method references can be found in a subsequent article series.
BiFunction
Function is limited to one argument, but two arguments are quite common, so BiFunction takes two parameter values and then returns a response value.
@FunctionalInterface
public interface BiFunction<T.U.R> {
/** * Given a parameter T of type T and a parameter U of type U, return a response of type R. * *@paramT the first argument *@paramU The second argument *@returnResult */
R apply(T t, U u); . }Copy the code
A classic use of Function is the replaceAll Function of Map.
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
Copy the code
The replaceAll function of a Map iterates through all entries in the Map, calculates a new value using the BiFunction parameter function, and then replaces the old value with the new value.
In a hypothetical application scenario, we use a HashMap to record some words and their length, and then the product manager has a new requirement to uniformly record the length of some specified words to zero. Example code is as follows:
public static void testBiFunctionWithLambda(a) {
Map wordMap = new HashMap<>();
wordMap.put("hello".5);
wordMap.put("world".5);
wordMap.put("on".2);
wordMap.put("at".2);
// K and v in the lambda expression are the key and original value of the Entry in the Map respectively.
// The return value of the lambda expression is a new value value.
wordMap.replaceAll((k, v) -> {
if ("on".equals(k) || "at".equals(k)) {
// Corresponds to the word on and at, and the length of the word is 0
return 0;
} else {
// For other words, the length of the word remains unchanged
returnv; }}); System.out.println(wordMap); }Copy the code
The output of the above code is:
{world=5, at=0, hello=5, on=0}
Copy the code
2. Supplier
In addition to Function and BiFunction, a common functional interface is to return a response value without any arguments. This is Supplier:
@FunctionalInterface
public interface Supplier<T> {
/** * get an instance of an object of type T. * *@returnObject instance */
T get(a);
}
Copy the code
A typical application scenario for Supplier is to quickly implement factory-class production methods, including delayed or asynchronous production methods. Example code is as follows:
public class SupplierExample {
public static void main(String[] args) {
testSupplierWithLambda();
}
public static void testSupplierWithLambda(a) {
final Random random = new Random();
// Generate a random integer
lazyPrint(() -> {
return random.nextInt(100);
});
// Delay 3 seconds, generate a random integer
lazyPrint(() -> {
try {
System.out.println("waiting for 3s...");
Thread.sleep(3*1000);
} catch (InterruptedException e) {
// do nothing
}
return random.nextInt(100);
});
}
public static void lazyPrint(Supplier lazyValue) { System.out.println(lazyValue.get()); }}Copy the code
The output looks like this:
26
waiting for 3s...
27
Copy the code
3. Consumers
If a Supplier is a producer, its opposite is a Consumer.
Consumer
In contrast to Supplier, a Consumer receives an argument and does not return any value.
@FunctionalInterface
public interface Consumer<T> {
/** * Performs operations on a given single parameter. * *@paramT Enter the parameter */
void accept(T t); . }Copy the code
Sample code:
public static void testConsumer(a) {
List list = Arrays.asList("Guangdong"."Zhejiang"."Jiangsu");
// Consume each element in the list
list.forEach(s -> System.out.println(s));
}
Copy the code
The output of the above code is:
Guangdong
Zhejiang
Jiangsu
Copy the code
BiConsumer
And BiConsumer, which has the same semantics as Consumer, except that BiConsumer accepts two parameters.
@FunctionalInterface
public interface BiConsumer<T.U> {
/** * Perform operations on the given two parameters. * *@paramT the first argument *@paramU The second argument */
void accept(T t, U u); . }Copy the code
Sample code:
public static void testBiConsumer(a) {
Map cityMap = new HashMap<>();
cityMap.put("Guangdong"."Guangzhou");
cityMap.put("Zhejiang"."Hangzhou");
cityMap.put("Jiangsu"."Nanjing");
// Consume each (key, value) key-value pair in the map
cityMap.forEach((key, value) -> {
System.out.println(String.format("The capital of %s is %s", key, value));
});
}
Copy the code
The output of the above code is:
The capital of Guangdong is Guangzhou and Zhejiang is Hangzhou. The capital of Jiangsu is NanjingCopy the code
4. Predicate
Predicate means that it takes a parameter value and then returns a Boolean value based on a given Predicate condition. It is essentially a special Function, a Function that specifies a return value of type Boolean.
@FunctionalInterface
public interface Predicate<T> {
/** * A Boolean result is computed based on the given argument. * *@paramT Enter the parameter *@returnReturns true if the argument meets the assertion condition, false */ otherwise
boolean test(T t); . }Copy the code
The scenarios for Predicate are often used as some kind of filtering condition. Example code:
public static void testPredicate(a) {
List provinces = new ArrayList<>(Arrays.asList("Guangdong"."Jiangsu"."Guangxi"."Jiangxi"."Shandong"));
boolean removed = provinces.removeIf(s -> {
return s.startsWith("G");
});
System.out.println(removed);
System.out.println(provinces);
}
Copy the code
The code above filters out the provinces starting with the letter G and outputs:
true
[Jiangsu, Jiangxi, Shandong]
Copy the code
5. Operators
The Operator functional interface is a special type of Function that requires the return value type to be the same as the parameter type.
Like Function/BiFunction, Operators also supports one or two arguments.
UnaryOperator
UnaryOperator supports one argument. UnaryOperator<T> is equivalent to Function<T, T> :
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T.T> {... }Copy the code
UnaryOperator converts province pinyin to uppercase and lowercase letters:
public static void testUnaryOperator(a) {
List provinces = Arrays.asList("Guangdong"."Jiangsu"."Guangxi"."Jiangxi"."Shandong");
// Convert the province letters to uppercase letters
// Use lambda expressions to implement UnaryOperator
provinces.replaceAll(s -> s.toUpperCase());
System.out.println(provinces);
// Convert the letters of the province to lowercase letters.
// Implement UnaryOperator using method references
provinces.replaceAll(String::toLowerCase);
System.out.println(provinces);
}
Copy the code
The output of the above code is:
[GUANGDONG, JIANGSU, GUANGXI, JIANGXI, SHANDONG]
[guangdong, jiangsu, guangxi, jiangxi, shandong]
Copy the code
BinaryOperator
BinaryOperator supports two parameters. BinaryOperator<T> is the same as BiFunction<T, T, T>.
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T.T.T> {... }Copy the code
Example code for BinaryOperator — evaluates the sum of all integers ina List:
public static void testBinaryOperator(a) {
List values = Arrays.asList(1.3.5.7.11);
// Sum using reduce: 0+1+3+5+7+11 = 27
int sum = values.stream()
.reduce(0, (a, b) -> a + b);
System.out.println(sum);
}
Copy the code
The output of the above code is:
27
Copy the code
6. Legacy functional interfaces from Java 7 and previous versions
The definition of a functional interface was mentioned earlier: all interfaces that have only one abstract method are functional interfaces.
According to this definition, some “older” interfaces defined in Java 7 or earlier are also functional interfaces, including:
Runnable, Callable, Comparator, etc.
Of course, these legacy functional interfaces are annotated with @functionalInterface in Java 8.
Composite functional interfaces
As we mentioned in our first article, functional programming is a programming paradigm in which the entire program consists of function calls and function combinations.
Function composing, “composing,” means to combine a series of simple functions into a composite function.
Functional interfaces in Java 8 also provide the ability to compose functions. Notice that almost every built-in functional interface has a nonabstract method, andThen. The function of the andThen method is to combine multiple functional interfaces and execute them one by one in serial order to form a new functional interface.
Take the consumer.andthen method, which returns a new Consumer instance. The new Consumer instance will execute the current ACCPET method and then the After AccPET method. The source code snippet is as follows:
@FunctionalInterface
public interface Consumer<T> {...default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
// Execute the accept method of the current Consumer first, and then the accept method of after
// In particular, accept(t) cannot be written before the return statement, otherwise accept(t) will be executed earlier
return(T t) -> { accept(t); after.accept(t); }; }... }Copy the code
Example code is as follows:
public static void testConsumerAndThen(a) {
Consumer printUpperCase = s -> System.out.println(s.toUpperCase());
Consumer printLowerCase = s -> System.out.println(s.toLowerCase());
// Combine to get a new Consumer: print uppercase first, then lowercase
Consumer prints = printUpperCase.andThen(printLowerCase);
List list = Arrays.asList("Guangdong"."Zhejiang"."Jiangsu");
list.forEach(prints);
}
Copy the code
The output of the above code is:
GUANGDONG
guangdong
ZHEJIANG
zhejiang
JIANGSU
jiangsu
Copy the code
The function. andThen method is more complex. It returns a new Function instance, in which the current apply method is executed with a parameter T of type T, resulting in a return value R of type R, andThen R is used as an input parameter. Continue with the apply method after, which returns a value of type V:
@FunctionalInterface
public interface Function<T.R> {
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
// execute the apply method with T of type T and return R of type R;
// apply after to return a value of type V;
// In particular, apply(t) cannot be written before a return statement, otherwise apply(t) will be executed earlier.
return(T t) -> after.apply(apply(t)); }}Copy the code
Code examples:
public static void testFunctionAndThen(a) {
// wordLen counts the length of words
Function wordLen = s -> s.length(); S -> {return s.length(); }
// effectiveWord Indicates a valid word only when the length of the word is greater than or equal to 4
Function effectiveWordLen = len -> len >= 4;
// Function and Function combine to get a new Function,
// If the Integer type is removed, the Integer type is returned.
Function effectiveWord = wordLen.andThen(effectiveWordLen);
Map wordMap = new HashMap<>();
wordMap.computeIfAbsent("hello", effectiveWord);
wordMap.computeIfAbsent("world", effectiveWord);
wordMap.computeIfAbsent("on", effectiveWord);
wordMap.computeIfAbsent("at", effectiveWord);
System.out.println(wordMap);
}
Copy the code
The output of the above code is:
{at=false, world=true, hello=true, on=false}
Copy the code
conclusion
Java 8 gives functions the ability to be “first-class citizens” through functional interfaces.
A functional interface allows functions, like any other data type, to be assigned to a variable, as an argument to another function, or as a return value to another function.
The implementation of a functional interface can be a class (including anonymous classes), but is more likely to be a lambda expression or a method reference.