7

User Detail model:

private String userName;
private int userSalary;

I've a ArrayList of user information

List<UserDetail> userDetails = new ArrayList<>();

UserDetail user1 = new UserDetail("Robert", 100);
UserDetail user2 = new UserDetail("John", 100);
UserDetail user3 = new UserDetail("Robert", 55);

userdetails.add(user1);
userdetails.add(user2);
userdetails.add(user3);

I'm trying to iterate through the array and find out if there are any duplicate entries based on userName, from the above list I've two records with same user name "Robert", in this case I want to add up the userSalary and remove one record from the List.

Expected new ArrayList:

userName userSalary

Robert 155
John 100

Is this possible ??

4
  • 2
    You add three times the same user1 to the list. Is it ok? Commented Mar 3, 2020 at 16:11
  • You could use a map with key as userName and value as userSalary to help you aggregate the salaries for the same user. Commented Mar 3, 2020 at 16:14
  • @Uata updated my post, that should be user1, user2 and user3. Commented Mar 3, 2020 at 16:17
  • 1
    Does this answer your question? Java - Merge objects of list given a condition Commented Mar 3, 2020 at 16:17

5 Answers 5

2
 userDetails.stream()
            .collect(Collectors.toMap(
                        UserDetail::getName,
                        Function.identity(),
                        (left, right) -> {
                            left.setSalary(left.getSalary() + right.getSalary());
                            return left;
                        }
                    ))
            .values();

This will give you a Collection<UserDetail>. You can copy that into an ArrayList if needed, obviously.

Sign up to request clarification or add additional context in comments.

Comments

1

Because your goal is to group UserDetail objects that share the same userName, I recommend storing the result in a Map instead of an ArrayList.

This is possible by streaming your List and collecting it to a Map using Collectors#groupingBy in conjunction with Collectors#summingInt:

List<UserDetail> userDetails = new ArrayList<>();

UserDetail user1 = new UserDetail("Robert", 100);
UserDetail user2 = new UserDetail("John", 100);
UserDetail user3 = new UserDetail("Robert", 55);

userDetails.add(user1);
userDetails.add(user2);
userDetails.add(user3);

Map<String, Integer> groupedUserDetails = userDetails.stream()
    .collect(Collectors.groupingBy(UserDetail::getUserName,
        Collectors.summingInt(UserDetail::getUserSalary)));

System.out.println(groupedUserDetails);

This above snippet may output the following:

{Robert=155, John=100}

If you want to convert this Map<String, Integer> into a List<UserDetail>, then you can use the following:

List<UserDetail> newUserDetails = groupedUserDetails.entrySet()
    .stream()
    .map(entry -> new UserDetail(entry.getKey(), entry.getValue())
    .collect(Collectors.toList());

1 Comment

that is Map<String, Integer> though, not a List/Collection<UserDetail> as the OP requested.
1

Here is a solution without streams(may be easier to understand):

Iterator<UserDetail> it=userDetails.iterator();
Map<String,UserDetail> found=new HashMap<>();
while(it.hasNext()){
    UserDetail next=it.next();
    if(found.containsKey(next.getUserName())){
        found.get(next.getUserName()).setUserSalery(found.get(next.getUserName()).getUserSalery()+next.getUserSalery();
        it.remove();
    }
    else{
        found.put(next.getUserName(),next);
    }
}

This iterates through all elements.

If it has already found a matching element, it adds its own salery to it and removes itself out of the list.

If not, it marks itself to be found if other elemts are found with the same name later.

This assumes that UserDetail has standard getter/setter methods for userName and userSalery.

Note that a for-each loop cannot be used because you cannot modify the content of the List in there (it would throw a ConcurrentModificationException).

From the comments(from @Holger:

You can use a single UserDetail previous = found.putIfAbsent(next.getName());, followed by if(previous != null) { previous.setSalery(previous.getSalery()+next.getSalery()); it.remove(); } instead of looking up the map three times in a row.

That code would be:

Iterator<UserDetail> it=userDetails.iterator();
Map<String,UserDetail> found=new HashMap<>();
while(it.hasNext()){
    UserDetail next=it.next();
    UserDetail previous = found.putIfAbsent(next.getUserName());
    if(previous != null) {
        previous.setUserSalery(previous.getUserSalery()+next.getUserSalery());
        it.remove();
    }
}

This essentially does the same thing.

It adds the current element to the List if it does not exist and if not, it just adds up the salery.

2 Comments

You can use a single UserDetail previous = found.putIfAbsent(next.getName());, followed by if(previous != null) { previous.setSalery(previous.getSalery()+next.getSalery()); it.remove(); } instead of looking up the map three times in a row.
@Holger I've added it.
0

I can suggest an alternate approach. HashMap

  • Use username as key
  • override equals & hashcode
  • Before adding an element to the hashMap, check if any object already present
  • If object found, get salary from it, update the current object, and then add to map

public class UserDetail {
    private String userName;
    private int userSalary;

    @Override
    public boolean equals(Object obj) {
        return this.userName.equals(((UserDetail)obj).userName);
    }

    @Override
    public int hashCode() {
        return userName.length();
    }
}

Comments

0

Here Map is used to store UserDetail by userName. If user is in map salary will be add up and update in the map. Other wise user will be put in the map. Finally the map values will be converted to a UserDetail list

Map<String, UserDetail> mergedMap = new HashMap<>(); // username is used as the key 
userDetails.forEach(userDetail -> {
   String userName = userDetail.getUserName();
   UserDetail userInMap = mergedMap.get(userName);
   if (userInMap != null) { // if user is in map salary will be added
      userInMap.setUserSalary(userInMap.getUserSalary() + userDetail.getUserSalary());
   } else { //otherwise user will put in map
      mergedMap.put(userDetail.getUserName(), userDetail);
   }
});

List<UserDetail> usersWithMergedSalaries = new ArrayList<>(mergedMap.values()); //convert map values to a list

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.