Java 8 feature book (latest version)

Posted by lkalik on Wed, 19 Jan 2022 10:10:53 +0100

1, Preface

Java 8 is a milestone version, which is full of praise with the following new features.

  • Lambda expression brings a new style and ability to code construction;
  • Steam API enriches collection operations and expands the ability of collection;
  • The new date and time API comes out after a thousand calls;

With the in-depth understanding of the new features of Java 8, you will be fascinated by the beauty of Lambda expressions (including method references) and streaming operations, and not by the beauty of framework design.

2, Method reference

Lambda expression is an anonymous function, which can be understood as a piece of code that can be passed with parameters (code is passed like data). The use of lambda expressions requires the support of functional interfaces.

Method reference is a simplified way to write a special Lambda expression. When only one method is called in the Lambda body and this method meets the functional interface specification, the:: method reference syntax can be used.

From the perspective of syntax expressiveness, method reference > Lambda expression > anonymous inner class. Method reference is a high-level Lambda expression. The language expression is more concise and is strongly recommended.

The method reference expression does not need to display the parameters declaring the called method, and is automatically injected according to the context. Method reference can improve the elegance of Lambda expression language and make the code more concise. The following takes Comparator sorting as an example to show how to build elegant code with method reference.

(1) Method reference and sorting

1. Common data type

Common data types are relatively easy to understand.

// Forward sort (method reference)
Stream.of(11, 15, 11, 12).sorted(Integer::compareTo).forEach(System.out::println);
// Forward sort
Stream.of(11, 15, 11, 12).sorted(Comparator.naturalOrder()).forEach(System.out::println);
// Reverse sort
Stream.of(11, 15, 11, 12).sorted(Comparator.reverseOrder()).forEach(System.out::println);
2. Object data type

(1) Data intact

Data integrity has two meanings: one is that the object itself is not empty; Second, the attribute value of the object to be compared is not empty, and the sorting operation is carried out on this premise.

// Sort sets by age (in positive order)
Collections.sort(userList, Comparator.comparingInt(XUser::getAge));
// Sort sets by age (in reverse order)
Collections.sort(userList, Comparator.comparingInt(XUser::getAge).reversed());

This example is expanded with Integer type. Similarly, numeric types such as Double type and Long type are handled in the same way. among Comparator Is an important class in the sorting process.

(2) Missing data

Missing data means that the object itself is empty or the attribute of the object to be compared is empty. If it is not processed, a null pointer exception will appear in the above sorting.

The most common processing method is to filter out null pointer data through the filter method in stream operation, and then sort according to the above strategy.

userList.stream().filter(e->e.getAge()!=null).collect(Collectors.toList());
3. String processing

When a few developers build entity classes, String types blossom everywhere. In the scenario of operation or sorting, the defects of String are gradually exposed. The following describes the sorting of String numeric types, that is, to complete the desired operation without modifying the data type.

Entity class

public class SUser {
    private Integer userId;
    private String UserName;
    // It should be Double type, but the wrong use is String type
    private String score;
}

Positive and reverse sorting

// Sort sets by age (in positive order)
Collections.sort(userList, Comparator.comparingDouble(e -> new Double(e.getScore())));

When converting and sorting data types, it is not smooth to use the built-in API of JDK. It is recommended to use the sorting tool class in the commons-collection4 package. To learn more, please step by step ComparatorUtils.

// Sort sets by age (in reverse order)
Collections.sort(userList, ComparatorUtils.reversedComparator(Comparator.comparingDouble(e -> new Double(e.getScore()))));

Summary: by taking sorting as an example, comparing the Comparator interface, Lambda expression and method reference, the code readability is gradually improved.

(2) Sorter

The built-in sorter can complete the sorting requirements of most scenarios. When the sorting requirements are more refined, it is a better choice to introduce a third-party framework in time.

1. Single column sorting

Single column sorting includes positive and negative order.

// positive sequence
Comparator<Person> comparator = Comparator.comparing(XUser::getUserName);
// Reverse order
Comparator<Person> comparator = Comparator.comparing(XUser::getUserName).reversed();
2. Multi column sorting

Multi column sorting refers to how to sort the next step when the elements to be compared have equal values.

// By default, multiple columns are sorted in positive order
Comparator<XUser> comparator = Comparator.comparing(XUser::getUserName)
    .thenComparing(XUser::getScore);
// Custom positive and negative order
Comparator<XUser> comparator = Comparator.comparing(XUser::getUserName,Comparator.reverseOrder())
    .thenComparing(XUser::getScore,Comparator.reverseOrder());

3, Steam API

Flow operations include the following three parts: create flow, intermediate flow, close flow, filter, de duplication, map and sort the intermediate operations belonging to the flow, and collect the intermediate operations belonging to the termination operation. Stream It is the basic key class of flow operation.

(1) Create stream

(1) Create a stream from a collection

// Create a stream from a collection
List<String> lists = new ArrayList<>();
lists.stream();

(2) Create a stream from an array

// Create a stream from an array
String[] strings = new String[5];
Stream.of(strings);

Most applications are to create a stream through a collection, then go through intermediate operations, and finally terminate back to the collection.

(2) Intermediate operation

1. filter

Filtering refers to filtering the subsets that meet the conditions from the (set) flow, which is implemented through the Lambda expression production interface.

// Implement element filtering through assertion interface
stream.filter(x->x.getSalary()>10);

Non empty filtering

Non empty filtering includes two levels: one is whether the current object is empty or non empty; Second, whether an attribute of the current object is empty or non empty.

Filter non empty objects, syntax stream Filter (objects:: nonnull) makes non null assertions.

// Non null assertion
java.util.function.Predicate<Boolean> nonNull = Objects::nonNull;

see Objects Class for more details.

2. distinct

De duplication refers to removing duplicate elements from the (set) stream, and judging whether they are duplicate elements through hashcode and equals functions. The de duplication operation implements an operation similar to HashSet. For the de duplication of object element flow, you need to rewrite hashcode and equals methods.

If the Lombok plug-in is used for generic objects in the stream, the hashcode and equals methods are overridden by default with the @ Data annotation. If the fields are the same and the properties are the same, the objects are equal. More information can be viewed Lombok user manual

stream.distinct();
3. map

Take out a column of elements in the stream, and then cooperate with the collection to form a new collection.

stream.map(x->x.getEmpId());

filter and map operations are usually used together to extract the data of a row and a column in the stream. It is recommended to locate the data in the way of first column and then column.

Optional<MainExportModel> model = data.stream().filter(e -> e.getResId().equals(resId)).findFirst();
if (model.isPresent()) {
    String itemName = model.get().getItemName();
    String itemType = model.get().getItemType();
    return new MainExportVo(itemId, itemName);
}
4. sorted

The sorting in the traditional Collectors class supports the sorting of some of the List implementation classes. Using stream sorting can cover all the List implementation classes.

// Sort by default dictionary order
stream.sorted();
// Sort by salary
stream.sorted((x,y)->Integer.compare(x.getSalary(),y.getSalary()));

(1) Functional interface sorting

Based on the functional method in Comparator class, the sorting of object flow can be realized more gracefully.

// Forward sort (default)
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getPeriod));
// Reverse sort
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getPeriod).reversed());

(2) Sort by LocalDate and LocalDateTime

Compared with the interface, the new date interface has a more user experience, so it is more and more applied. Sorting based on date is a common operation.

// Prepare test data
Stream<DateModel> stream = Stream.of(new DateModel(LocalDate.of(2020, 1, 1)), new DateModel(LocalDate.of(2019, 1, 1)), new DateModel(LocalDate.of(2021, 1, 1)));

Positive and reverse sorting

// Forward sort (default)
stream.sorted(Comparator.comparing(DateModel::getLocalDate)).forEach(System.out::println);
// Reverse sort
stream.sorted(Comparator.comparing(DateModel::getLocalDate).reversed()).forEach(System.out::println);
5. reduce

The elements in the convection are calculated according to a certain strategy. The underlying logic of terminating operations is implemented by reduce.

(3) Terminate operation

collect stores the intermediate (calculation) results in the stream into a collection for further use. In order to facilitate the understanding of the collection operation and facilitate readers to master the collection operation, the collection is divided into ordinary collection and advanced collection.

1. General collection

(1) Collect as List

The default returned type is ArrayList, which can be accessed through collectors The toCollection (LinkedList:: new) display indicates that other data structures are used as return value containers.

List<String> collect = stream.collect(Collectors.toList());

For the collection of streams created by collections, please note that only the contents in the stream field are modified without returning a new type. The following operations directly modify the original collection without processing the return value.

// Modify the original set directly
userVos.stream().map(e -> e.setDeptName(hashMap.get(e.getDeptId()))).collect(Collectors.toList());

(2) Collect as Set

The default return type is HashSet, which can be accessed through collectors The toCollection (TreeSet:: new) display indicates that other data structures are used as return value containers.

Set<String> collect = stream.collect(Collectors.toSet());
2. Advanced collection

(1) Collect as Map

The default return type is HashMap, which can be accessed through collectors The toCollection (LinkedHashMap:: new) display indicates that other data structures are used as return value containers.

The application scenario collected as Map is more powerful. This scenario is described in detail below. It is hoped that the matching relationship between ID and NAME can be established in the returned results. The most common scenario is to batch query NAME in the database through ID, and then replace the ID in the original dataset with NAME after returning.

ID to NAME mapping

@Data
public class ItemEntity {
    private Integer itemId;
    private String itemName;
}

Prepare the collection data, which is usually the data queried from the database

// Simulate querying batch data from the database
List<ItemEntity> entityList = Stream.of(new ItemEntity(1,"A"), new ItemEntity(2,"B"), new ItemEntity(3,"C")).collect(Collectors.toList());

Convert the collection data into a Map with ID and NAME

// Convert the collection data into a Map with ID and NAME
Map<Integer, String> hashMap = entityList.stream().collect(Collectors.toMap(ItemEntity::getItemId, ItemEntity::getItemName));

ID and Object class mapping

@Data
public class ItemEntity {
    private Integer itemId;
    private String itemName;
    private Boolean status;
}

Convert the collection data into a Map of ID and entity class

// Convert the collection data into a Map of ID and entity class
Map<Integer, ItemEntity> hashMap = entityList.stream().collect(Collectors.toMap(ItemEntity::getItemId, e -> e));

The toMap parameter in the Collectors class is a functional interface parameter, which can customize the return value.

public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper) {
    return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

(2) Group collection

The packet collection operation of the stream simulates the group by operation at the database level in the memory level. The following demonstrates the packet operation of the stream. Collectors Class provides various levels of grouping operation support.

The grouping ability of the stream corresponds to the aggregation function in the database. At present, most aggregation functions that can operate in the database can find the corresponding ability in the stream.

// By default, List is used as the bearer container after grouping
Map<Integer, List<XUser>> hashMap = xUsers.stream().collect(Collectors.groupingBy(XUser::getDeptId));

// Display indicates that List is used as the host container after grouping
Map<Integer, List<XUser>> hashMap = xUsers.stream().collect(Collectors.groupingBy(XUser::getDeptId, Collectors.toList()));

Group after mapping

Map<Integer, List<String>> hashMap = xUsers.stream().collect(Collectors.groupingBy(XUser::getDeptId,Collectors.mapping(XUser::getUserName,Collectors.toList())));

4, Stream expansion

(1) Set and object inter rotation

Wrapping objects into collections and disassembling collections into objects are common operations.

1. Object to collection

Returns a collection instance of the default type

/**
 * Convert a single object to a collection
 *
 * @param t   Object instance
 * @param <T> object type
 * @param <C> Collection type
 * @return Collection instance containing object
 */
public static <T, C extends Collection<T>> Collection<T> toCollection(T t) {
    return toCollection(t, ArrayList::new);
}

User defined collection instance type returned

/**
 * Convert a single object to a collection
 *
 * @param t        Object instance
 * @param supplier Assembly factory
 * @param <T>      object type
 * @param <C>      Collection type
 * @return Collection instance containing object
 */
public static <T, C extends Collection<T>> Collection<T> toCollection(T t, Supplier<C> supplier) {
    return Stream.of(t).collect(Collectors.toCollection(supplier));
}
2. Set to object

Use the default sort rule. Note that this does not refer to natural order sorting.

/**
 * Gets the first element in the collection
 *
 * @param collection Collection instance
 * @param <E>        Element type in collection
 * @return generic types 
 */
public static <E> E toObject(Collection<E> collection) {
    // Handling collection null pointer exception
    Collection<E> coll = Optional.ofNullable(collection).orElseGet(ArrayList::new);
    // Here you can sort the stream and take out the first element
    return coll.stream().findFirst().orElse(null);
}

The above methods skillfully solve the exception problems in two aspects: first, the collection instance references the null pointer exception; Second, the set subscript is out of bounds.

(2) Others

1. Parallel computing

Based on the parallel flow in streaming computing, it can significantly improve the computing efficiency under big data and make full use of the number of CPU cores.

// Data accumulation through parallel flow
LongStream.rangeClosed(1,9999999999999999L).parallel().reduce(0,Long::sum);
2. Sequence array

Generates an array or collection of the specified sequence.

// Method 1: generate array
int[] ints = IntStream.rangeClosed(1, 100).toArray();
// Method 2: generate set
List<Integer> list = Arrays.stream(ints).boxed().collect(Collectors.toList());

5, Others

(1) New date time API

1,LocalDateTime
// Get current date (including time)
LocalDateTime localDateTime = LocalDateTime.now();
// Get current date
LocalDate localDate = localDateTime.toLocalDate();
// Get current time
LocalTime localTime = localDateTime.toLocalTime();

Date formatting

// Month MM needs to be capitalized, hour letter needs to be capitalized (lowercase represents hexadecimal)
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
// Get current time (string)
String dateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("dateTime = " + dateTime);
2,Duration
Duration duration = Duration.between(Instant.now(), Instant.now());
System.out.println("duration = " + duration);
3. Get current timestamp

The 13 bit timestamp is obtained in the following way, and the unit is milliseconds.

// Mode 1
long now = Timestamp.valueOf(LocalDateTime.now()).getTime();
// Mode II
long now = Instant.now().toEpochMilli();

(2) Optional

stay Optional Before the class appeared, null exceptions tormented almost every developer. In order to build a robust application, they had to use cumbersome if logic to avoid null pointer exceptions. Unlock the Optional class to make your application more robust.

1. Judge before use

The ifPresent method provides the ability to determine whether it is empty before further use.

2. Chain value

Chain value refers to the value of nested objects at different levels. Only when the upper object is not empty can its attribute value be read, and then continue to call to get the final result value. Sometimes only the result state at the end of the chain is concerned. Even if the intermediate state is empty, a null value is returned directly. The following provides an implementation method without if judgment and compact code introduction:

Optional<Long> optional = Optional.ofNullable(tokenService.getLoginUser(ServletUtils.getRequest()))
    								.map(LoginUser::getUser).map(SysUser::getUserId);
// Returns if it exists, and returns NULL if it does not exist
Long userId = optional.orElse(null);

6, Application of flow

(1) List to tree

The traditional way to build a tree list requires repeated recursive calls to query the database, which is inefficient. For a tree with more nodes, the efficiency is lower. Here is a solution that only needs to call the database once and convert the list into a tree through flow.

/**
 * List to tree
 *
 * @param rootList     List all datasets
 * @param parentId Parent ID of the first level directory
 * @return Tree list
 */
public List<IndustryNode> getChildNode(List<Industry> rootList, String parentId) {
    List<IndustryNode> lists = rootList.stream()
        	.filter(e -> e.getParentId().equals(parentId))
            .map(IndustryNode::new).collect(toList());
    lists.forEach(e -> e.setChilds(getChildNode(rootList, e.getId())));
    return lists;
}

Original address

Topics: Java Lambda Back-end