Writing in the front
With the introduction of functional interfaces and lambda expressions in Java 8, the Java Collections Framework (JCF) added interfaces to accommodate functional programming.
The goal of this article is to take you through the common interfaces added to the Collections framework in Java 8 and make our code cleaner and more advanced.
Sample code for this article is available from Gitee: gitee.com/cnmemset/ja…
Obtain all column, can search concern public number [member said].
Collections framework in Java 8
First, I’ll give you an overview of the Java Collections framework.
The Java Collections framework is primarily derived from two root interfaces: the Collecton interface, which is used to store a single element; The other is the Map interface, which is mainly used to store key-value pairs.
The interface inheritance structure of the Java Collections framework is shown below:
Java 8 adds some methods to the Collection, List, and Map interfaces. The following table lists some commonly added functions:
The interface name | A new method added to Java8 |
---|---|
Collection | forEach(), removeIf(), stream(), parallelStream() |
List | replaceAll(), sort() |
Map | forEach(), replaceAll(), compute(), computeIfAbsent(), computeIfPresent(), merge() |
Each of these functions takes a parameter of a functional interface type. There is a special term for this type of function: high Order function.
Higher-order functions (definition) :
A function is called a higher-order function if one or more of its arguments are of function type, or if its return value is of function type.
As you can see, most of these new methods have default implementation, which greatly reduces the burden of specific subclasses. The default methods in interfaces are new to Java 8, and an important role is “interface Evolution.” For example, suppose we had a class that implemented the Collection interface prior to Java 8:
public class Container implements Collection {... }Copy the code
After upgrading to Java 8, the previous class Container will fail to compile because of the new method forEach in the Collection, assuming there is no default method.
Higher-order functions in Collection
1. forEach
Strictly speaking, the forEach method is defined in the interface Iterable, and its method signature and default implementation are:
default void forEach(Consumer action) {
Objects.requireNonNull(action);
for (T t : this) { action.accept(t); }}Copy the code
It iterates through each element in the collection and then performs the action specified by the action parameter on each element in turn. The action argument is a functional interface of type Consumer, which we can implement through lambda expressions or method references. Therefore, in Java 8 or later, instead of iterating over a Collection with a for statement, use the forEach method.
Example code is as follows:
public static void collectionForEach(a) {
Collection list = Arrays.asList("Guangdong"."Zhejiang"."Jiangsu");
/ / for statement
for (String s : list) {
System.out.println(s);
}
// forEach + lambda expression
list.forEach(s -> System.out.println(s));
// forEach method + method reference
list.forEach(System.out::println);
}
Copy the code
2. removeIf
The removeIf method signature is:
default boolean removeIf(Predicate filter) {... }Copy the code
RemoveIf iterates through each element in the collection, then performs the specified filtering on each element in turn. The filter argument is a functional interface of type Predicate.
Example code is as follows, assuming we want to filter out provinces starting with the letter G:
public static void collectionRemoveIf(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 output of the above code is:
true
[Jiangsu, Jiangxi, Shandong]
Copy the code
3. replaceAll
The replaceAll method signature is:
default void replaceAll(UnaryOperator operator) {... }Copy the code
The replaceAll method performs the operator specified calculation on each element in the collection and replaces the original element with the result. The operator argument is a functional interface of type UnaryOperator, whose argument and return value types are the same.
The example code is as follows: convert all provinces’ pinyin to uppercase letters
public static void listReplaceAll(a) {
List provinces = Arrays.asList("Guangdong"."Jiangsu"."Guangxi"."Jiangxi"."Shandong");
provinces.replaceAll(s -> s.toUpperCase());
System.out.println(provinces);
}
Copy the code
The output of the above code is:
[GUANGDONG, JIANGSU, GUANGXI, JIANGXI, SHANDONG]
Copy the code
4. sort
Sort’s method signature is as follows:
default void sort(Comparator c) {... }Copy the code
The sort method sorts the elements in a List according to the collation specified by comparator C. The parameter c is of type Comparator, which is also a functional interface.
Sample code:
public static void listSort(a) {
List list = Arrays.asList("Guangdong"."Zhejiang"."Jiangsu"."Xizang"."Fujian"."Hunan"."Guangxi");
// Sort provinces by length first, or alphabetically if they are the same length
list.sort((first, second) -> {
int lenDiff = first.length() - second.length();
return lenDiff == 0 ? first.compareTo(second) : lenDiff;
});
list.forEach(s -> System.out.println(s));
}
Copy the code
The output of the above code is:
Hunan
Fujian
Xizang
Guangxi
Jiangsu
Zhejiang
Guangdong
Copy the code
5. The stream and parallelStream
Stream is an important part of functional programming in Java, as we will expand on in a later article.
Higher-order functions in Map
1. forEach
Like Collection, Map also has a forEach method, which has the following method signature and default implementation:
default void forEach(BiConsumer action) {... }Copy the code
As you can see, the Map’s forEach method iterates through all key-value pairs in the Map and performs the action specified by the action parameter. The action parameter is of type BiConsumer and requires two parameters, representing the key and value of the key-value pair.
Sample code:
public static void mapForEach(a) {
Map cityMap = new HashMap<>();
cityMap.put("Guangdong"."Guangzhou");
cityMap.put("Zhejiang"."Hangzhou");
cityMap.put("Jiangsu"."Nanjing");
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
2. replaceAll
Map’s replaceAll method signature and default implementation are as follows:
default void replaceAll(BiFunction function) {... }Copy the code
Like replaceAll for a Collection, Map’s replaceAll method performs the calculation specified by operator on each key/value pair in the Map and replaces the original value with the result. Note that the parameter function is a BiFunction, which means that it needs to provide a function implementation that has two arguments, one for the key type (K) and one for the value type (V), and that it needs to return a value of type (V).
Sample code:
public static void mapReplaceAll(a) {
Map cityMap = new HashMap<>();
cityMap.put("Guangdong"."Guangzhou");
cityMap.put("Zhejiang"."Hangzhou");
cityMap.put("Jiangsu"."Nanjing");
// Change the provincial capital pinyin to uppercase
cityMap.replaceAll((key, value) -> {
return value.toUpperCase();
});
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
3. compute
Compute method signature:
default V compute(K key, BiFunction remappingFunction) {... }Copy the code
The compute method associates the evaluation of the remappingFunction parameter to the key parameter, but if the evaluation is null, the key mapping is removed from the Map.
Example code is as follows:
public static void mapCompute(a) {
Map cityMap = new HashMap<>();
cityMap.put("Guangdong"."null");
cityMap.put("Zhejiang"."Hangzhou");
cityMap.put("Jiangsu"."null");
// A slightly more complex statement that first calls forEach to iterate over the cityMap keys and then evaluates the new values from the old key-value pairs
Set keys = new HashSet<>(cityMap.keySet());
keys.forEach(key -> {
cityMap.compute(key, (k, v) -> {
// If Guangdong, go back to Guangzhou
if ("Guangdong".equals(k)) {
return "Guangzhou";
}
// If the value is the string "null" in the old key-value pair, null is returned.
// This means cityMap will remove the corresponding key
if ("null".equals(v)) {
return null;
}
// Otherwise, return the original value
return v;
});
});
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 The capital of Zhejiang is HangzhouCopy the code
Notice that Jiangsu has been removed from cityMap.
4. computeIfPresent
The method signature and default implementation of computeIfPresent is:
default V computeIfPresent(K key, BiFunction remappingFunction) { Objects.requireNonNull(remappingFunction); V oldValue; if ((oldValue = get(key)) ! = null) { V newValue = remappingFunction.apply(key, oldValue); if (newValue ! = null) { put(key, newValue); return newValue; } else { remove(key); return null; } } else { return null; }}Copy the code
ComputeIfPresent has the same method signature and functions as Compute, but unlike Compute, it only has a key in the Map and the corresponding value is non-null. The evaluation function specified by the remappingFunction argument is called (lazy evaluation of function programming features: the function is executed only after firing or some condition is met). If the calculation result is NULL, the mapping of the key is deleted. Otherwise, the original mapping of the key is replaced with the result.
5. computeIfAbsent
The method signature and default implementation for computeIfAbsent is:
default V computeIfAbsent(K key, Function mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if((newValue = mappingFunction.apply(key)) ! =null) {
put(key, newValue);
returnnewValue; }}return v;
}
Copy the code
ComputeIfAbsent (null) computeIfPresent (null) computeIfPresent (null) computeIfAbsent (null) computeIfPresent (null) computeIfAbsent (null) computeIfPresent (null) computeIfAbsent (null) computeIfAbsent (null) computeIfPresent (null) computeIfAbsent (null) computeIfAbsent (null) computeIfPresent (null) The function is executed only after firing or some condition is met, and the computed result is associated with the key only if it is not null. If the calculation result is NULL, the Map is not modified and no new mapping is added.
ComputeIfAbsent always returns the value corresponding to the key in the Map after the operation, which may be a previously existing value (if any) or a new value that has been calculated.
ComputeIfAbsent is particularly useful for initializing a Map. In a practice scenario where employees in the company already have a “name -> age” Map and we need to build a new Map of “age -> name list” based on it, using computeIfAbsent makes the code concise and efficient:
public static void mapComputeIfAbsent(a) {
Map staffMap = new HashMap<>();
staffMap.put("Lilei".24);
staffMap.put("Hanmeimei".22);
staffMap.put("Liming".24);
staffMap.put("Jim".22);
staffMap.put("David".24);
Map> staffInvertMap = new HashMap<>();
staffMap.forEach((key, value) -> {
// Build a new Map with the age key
// Take the age of 22:
// If staffInvertMap does not have a previous mapping for age 22,
// create a mapping of "age -> ArrayList" and return the new ArrayList.
// If staffInvertMap already has a 22 year mapping, the existing ArrayList is returned.
List nameList = staffInvertMap.computeIfAbsent(value, age -> {
// For the same age, this code is executed only once
return new ArrayList<>();
});
nameList.add(key);
});
System.out.println(staffInvertMap);
}
Copy the code
The output of the above code is:
{22=[Hanmeimei, Jim], 24=[Lilei, David, Liming]}
Copy the code
An aside:
The authors particularly like the computeIfPresent and computeIfAbsent functions, which, in addition to simplifying our code, have another feature: In ConcurrentHashMap, the set of operations in computeIfPresent and computeIfAbsent are atomic. The meaning of atomicity, welcome to pay attention to the public number [member said], discuss together.
6. merge
The merge method signature and default implementation is:
default V merge(K key, V value, BiFunction remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null)? value : remappingFunction.apply(oldValue, value);if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
Copy the code
The merge method merges the old and new values of keys. If no mapping exists or the mapping value is null, the value parameter is associated with the key. Otherwise, oldValue and parameter value are used as two parameters of remappingFunction, and a new value is calculated. If the new value is not null, the new value is associated with the key. If the new value is null, the mapping of the key is deleted.
A typical scenario is to make a comment for a user. If a comment has been made before, the new comment information is added to the end of the comment. Example code is as follows:
public static void mapMerge(a) {
Map staffMap = new HashMap<>();
staffMap.put("Lilei"."Gender male");
// equivalent to staffmap. merge("Lilei", "age 24", String::concat)
// oldValue = male; oldValue = age 24
staffMap.merge("Lilei"."Age 24", (oldValue, value) -> oldValue.concat(value));
staffMap.merge("Hanmeimei"."The age of 22", String::concat);
System.out.println(staffMap);
}
Copy the code
The output of the above code is:
{Lilei= male age 24, Hanmeimei= age 22}Copy the code
conclusion
Java 8 introduces functional programming and therefore brings many new features to the Collections framework in Java, making it easier to do functional programming.
As a good Java programmer, we should consciously apply these new methods to our actual coding to make our code cleaner and clearer.