Java Stream: Get The Latest Version Of Custom Records

I have a list of User objects defined as follows:

public class User { private String userId; // Unique identifier private String name; private String surname; private String otherPersonalInfo; private int versionNumber; } public User(String userId, String name, String surname, String otherPersonalInfo, int version) { super(); this.name = name; this.surname = surname; this.otherPersonalInfo = otherPersonalInfo; this.version = version; } } 

List example:

 List<User> users = Arrays.asList( new User("JOHNSMITH", "John", "Smith", "Some info", 1), new User("JOHNSMITH", "John", "Smith", "Updated info", 2), new User("JOHNSMITH", "John", "Smith", "Latest info", 3), new User("BOBDOE", "Bob", "Doe", "Personal info", 1), new User("BOBDOE", "Bob", "Doe", "Latest info", 2) ); 

I need a way to filter this list, so that I only get the latest version for each user, i.e.

 {"JOHNSMITH", "John", "Smith", "Latest info", 3}, {"BOBDOE", "Bob", "Doe", "Latest info", 2} 

What is the best way to achieve this using the Java8 Stream API?

+3
java java-8 java-stream
source share
6 answers
 HashMap<String, User> map = users.stream().collect(Collectors.toMap(User::getUserId, e -> e, (left, right) -> {return left.getVersion() > right.getVersion() ? left : right;}, HashMap::new)); System.out.println(map.values()); 

Above code print:

 [User [userId=BOBDOE, name=Bob, surname=Doe, otherPersonalInfo=Latest info, version=2], User [userId=JOHNSMITH, name=John, surname=Smith, otherPersonalInfo=Latest info, version=3]] 

Explanation: toMap takes 4 arguments:

  • keyMapper mapping function to create keys
  • valueMapper mapping function to create values
  • mergeFunction is a merge function used to resolve conflicts between values ​​associated with the same key, as indicated in Map.merge (Object, Object, BiFunction)
  • mapSupplier is a function that returns a new, empty map into which the results will be added.

  • First argument: user :: getUserId () to get the key.
  • The second arg is a function that returns a User object as is.
  • The third arg is a function that resolves the conflict by comparing and storing User with the latest version.
  • The fourth arg is the "new" HashMap method.
+1
source share

Helping this answer a bit:

  Collection<User> latestVersions = users.stream() .collect(Collectors.groupingBy(User::getUserId, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparing(User::getVersionNumber)), Optional::get))) .values(); 

I guess ordinary getters. Result:

 [John Smith Latest info 3, Bob Doe Latest info 2] 
+6
source share

I sorted first by version so that the first entry is the first in the list. Subsequently, I filtered out a separate key to ensure that only one object corresponding to this key is part of the result. For filtering, I need a predicate that stores the state for filtering by things already seen.

The predicate is as follows:

  private static <T> Predicate<T> distinctByKey( Function<? super T, ?> key ) { Map<Object, Boolean> seen = new ConcurrentHashMap<>(); return t -> seen.putIfAbsent( key.apply( t ), Boolean.TRUE ) == null; } 

And then I can use the following thread:

 users.stream().sorted( ( u1, u2 ) -> u2.versionNumber - u1.versionNumber ) .filter( distinctByKey( u -> u.name + u.surname ) ) .collect( Collectors.toList() ); 

There are other nice solutions for creating a separate key database, which can be found in Java 8 Distinct by property .

+1
source share

It will be painful, but it can be done with some aggregation within the Java 8 Streams platform:

 // create a Map from user name to users, sorted by version Map<String, NavigableSet<User>> grouped = users.stream() .collect( Collectors.groupingBy( u -> u.name + "," + u.surname, HashMap::new, Collectors.toCollection( () -> new TreeSet<>( Comparator.comparing( User::getVersionNumber))))); // retrieve the latest versions from the Map List<User> latestVersions = grouped.entrySet() .stream() .map(e -> e.getValue().last()) .collect(Collectors.toList()); 

Given how thorough this is, I would probably agree to an imperative decision.

  • Save Map<String, User>
  • For each User check if the Map already contains a custom string representation.
  • If this is not the case, or the user associated with it has a lower version number, save the user on the map.
0
source share

In java 8, you can create a comparator in the form of a lambda expression.

a call to users.stream().sorted passing in the comparator.

Example:

  Comparator<User > byVersionNumber = (u1, u2) -> Integer.compare( u1.getversionNumber(), u2.getversionNumber()); users.stream().sorted(byVersionNumber) .forEach(u -> System.out.println(u)); 

Check the syntax for its rough

0
source share
  List<User> users = Arrays.asList( new User("JOHNSMITH", "John", "Smith", "Some info", 1), new User("JOHNSMITH", "John", "Smith", "Updated info", 2), new User("JOHNSMITH", "John", "Smith", "Latest info", 3), new User("BOBDOE", "Bob", "Doe", "Personal info", 1), new User("BOBDOE", "Bob", "Doe", "Latest info", 2) ).stream() .collect(Collectors.collectingAndThen( Collectors.toMap( User::getUserId, //The user unique property Function.identity(), //Function<User, User> BinaryOperator.maxBy(Comparator.comparing(User::getVersionNumber)) ), map -> (List)map.values() )); 
0
source share

All Articles