I have a list:
List<UserItem> userList = new ArrayList<>();
Where I add the following:
User father = new User();
father.setName("Peter");
UserItem parent = new UserItem(father, null);
userList.add(parent);
I then create another user:
User daughter = new User();
daughter.setName("Emma");
UserItem child = new UserItem(daughter, <OBJECT IN LIST WHERE NAME IS "PETER">);
userList.add(child);
However, I need to change the text wrapped in <> above to the parent object I added before (the father), specified by the name ("Peter" in this case).
How can I find an object in a List by a specific attribute? In my case, how can I find the object in the List that has the name "Peter"?
Please note that I add hundreds, sometimes thousands, of different users like this to the list. Each "parent" has a unique name.
The obvious solution would be iterating on the list and when the condition is met, return the object:
for (User user : userList) {
if ("peter".equals(user.getName()) {
return user;
}
}
And you can use filter (Java 8):
List<User> l = list.stream()
.filter(s -> "peter".equals(s.getUser()))
.collect(Collectors.toList());
to get a list with all "peter" users.
As suggested in comments, I think using Map is a better option here.
Answer to your question is here: https://stackoverflow.com/a/1385698/2068880
Stream peters = userList.stream().filter(p -> p.user.name.equals("Peter"))
However, as ruakh suggested, it's more reasonable to use Map<String, UserItem> to make it faster. Otherwise, it will iterate all the objects in the list to find users with name "Peter".
Other way with parallelStream with findAny
Optional<UserItem> optional = userList.parallelStream().findAny(p -> p.user.getName().equalsIgnoreCase("Peter"));
UserItem user = optional.isPresent() ? optional.get() : null;
Related
I can use the below snippet to retrieve the name if there is 1 entry in the list by retrieving element 0 in the list, however, each NameResponse can have several names (e.g. a first name, a middle name and a surname). How can I retrieve x names associated with one customer? There could be 20 names for argument's sake. I would like to implement using a stream since I am using Java 8, but I am unsure how to implement this. Any suggestions?
private List<String> getNames(Customer customer) {
List<NameResponse> nameResponses = new ArrayList<>();
NameResponse nameResponse = new NameResponse();
nameResponse.setName("Test Name");
nameResponses.add(nameResponse);
customer.setNames(nameResponses);
return List.of(customer.getNames().get(0).getName());
}
Customer class:
private List<NameResponse> names;
NameResponse class:
private String name;
Something like below assuming you have the appropriate getters:
return customer.getNames()
.stream()
.map(NameResponse::getName)
.collect(Collectors.toList());
You could do that using the map operator on the stream and then collect to output a list:
return customer.getNames().stream()
.map(nameResponse -> nameResponse.getName())
.collect(Collectors.toList()));
I need a help on iterating and comparing 2 different list using java 8. If one of the attribute matches then i need to set the values of one list into another. An employee can have multiple projects.
EmployeeDto{ --List
Name
Id
Number
ProjectList }
I have a List projectList
ProjectDto
{
Id,
ProjectName....}
I want to compare employeeList and projectList. Check if Id matches from both list then set
employeeDto.setProjectList(projectList)
List<ProjectDto> newList = new Arraylist<>();
for(EmployeeDto emp: employeeList) {
for(ProjectDto project: ProjectList){
if(project.getId().equals(emp.getId())){
newList.add(project);
}
emp.setProjectList(newList));
}
Thanks in advance
employeeList.forEach(employee -> {
employee.setProjectList(projectList.stream()
.filter(project -> Objects.equals(employee.getId(), project.getId()))
.collect(Collectors.toList()));
});
That is one possible way.
There is no need to do O(n^2) work. You can group the projects by emp ID first.
Map<String, List<ProjectDto>> grouped = projects.stream().collect(groupingBy(ProjectDto::getId, toList()));
Then simply iterate through your employees and set the respective projectList for each one.
employees.forEach(emp -> emp.setProjectList(grouped.getOrDefault(emp.getId(), new ArrayList<>())));
I am trying to learn how to use the lambda functions for sleeker code but struggling to make this work.
I have two lists. The "old" list is always shorter or the same length as the "updated list".
I want to take the objects from the "updated list" and overwrite the "stale objects" in the shorter "old list".
The lists have a unique field for each object.
For example, it is a bit like updating books in a library with new editions. The UUID (title+author) remains the same but the new object replaces the old on the shelf with a new book/object.
I know I could do it the "long way" and make a HashMap<MyUniqueFieldInMyObject, MyObject> and then take the new List<MyUpdatedObjects> and do the same.
I.e. Have HashMap<UniqueField, MyOldObject> and HashMap<UniqueField, MyUpdatedObject>, then iterate over the old objects with a pseudo "if updated objects have an entry with the same key, overwrite the value with the updated value"...
But...
Is there a "nicer" shorted way to do this with functional lambda statements?
I was thinking along the lines of:
List<MyObject> updatedList;
List<MyObject> oldList;
updatedList.forEach(MyObject -> {
String id = MyObject.getId();
if (oldList.stream().anyMatcher(MyObject ->
MyObject.getId().matches(id)) {
//Do the replacement here? If so...how?
}
}
Which is where I am lost!
Thanks for any guidance.
If you want to update the list in place rather than making a new list, you can use List.replaceAll:
oldList.replaceAll(old ->
updateListe.stream()
.filter(updated -> updated.getId().equals(old.getId())
.findFirst()
.orElse(old)
);
The main problem with this solution is that its complexity is O(size-of-old*size-of-updated). The approach you described as "long way" can protect you from having to iterate over the entire updated list for every entry in the old list:
// note that this will throw if there are multiple entries with the same id
Map<String, MyObject> updatedMap = updatedList.stream()
.collect(toMap(MyObject::getId, x->x));
oldList.replaceAll(old -> updatedMap.getOrDefault(old.getId(), old));
I recommend you to iterate over the oldList - the one you want to update. For each of the object iterated match the equivalent one by its id and replace it using Stream::map. If an object is not found, replace it with self (doesn't change the object) using Optional::orElse.
List<MyObject> newList = oldList
.stream() // Change values with map()
.map(old -> updatedList.stream() // Iterate each to find...
.filter(updated -> old.getId() == updated.getId()) // ...by the same id
.findFirst() // Get new one to replace
.orElse(old)) // Else keep the old one
.collect(Collectors.toList()); // Back to List
List<Foo> updatedList = List.of(new Foo(1L, "new name", "new desc."));
List<Foo> oldList = List.of(new Foo(1L, "old name", "old desc."));
List<Foo> collect = Stream.concat(updatedList.stream(), oldList.stream())
.collect(collectingAndThen(toMap(Foo::getId, identity(), Foo::merge),
map -> new ArrayList(map.values())));
System.out.println(collect);
This will print out:
[Foo{id=1, name='new name', details='old desc.'}]
In Foo::merge you can define which fields need update:
class Foo {
private Long id;
private String name;
private String details;
/*All args constructor*/
/*getters*/
public static Foo merge(Foo newFoo, Foo oldFoo) {
return new Foo(oldFoo.id, newFoo.name, oldFoo.details);
}
}
I think it's best to add the objects to be updated into a new list to avoid changing a list you are streaming on and then you can simply replace the old with the new list
private List<MyObject> update(List<MyObject> updatedList, List<MyObject> oldList) {
List<MyObject> newList = new ArrayList<>();
updatedList.forEach(object -> {
if (oldList.stream().anyMatch(old -> old.getUniqueId().equals(object.getUniqueId()))) {
newList.add(object);
}
}
return newList;
}
I have a generic Object, let's say it's user:
#Data
class User {
public String userName;
public int age;
}
I create a bunch of them:
User user = new User("Freddy", 22);
User user = new User("Freddy", 18);
User user = new User("Hans", 21);
User user = new User("Michael", 5);
User user = new User("Freddy", 29);
User user = new User("Hans", 33);
User user = new User("Michael", 20);
User user = new User("Freddy", 15);
These are stored in let's say an ArrayList(assume they are all there)
List<User> userList = New ArrayList<>();
Now I wish to make three lists:
List<User> freddyList;
List<User> hansList;
List<User> michaelList;
I want to loop through userList and as you can predict, add the user to freddyList if userName equals "Freddy", and so forth.
How do I solve this with forEach?
usersList.foreach(
if (usersList.getUsername().equals("Freddy")){
freddyList.add(usersList.Object) // This doesn't work
}
)
Lambda expressions should be short and clean. With forEach you will end up using multi expression lambda that would look ugly.
My suggestion is to use use Java stream groupingBy:
Map< String, List<User> > map = userList.stream().collect( Collectors.groupingBy( u -> u.getUserName() ) );
List<User> freddyList = map.get("Freddy");
List<User> hansList = map.get("Hans");
List<User> michaelList = map.get("Michael");
Adding to the comment, your code should look something like this:
usersList.forEach( user ->
if (user.getUsername().equals("Freddy")){
freddyList.add(user);
}
)
You need to consume the user object from forEach (look here) and add it to the respective list as shown below:
List<User> userList = new ArrayList<>();
//Add your users here
//Declare your freddyList, hansList, etc..
userList.forEach(user -> {
if (user.getUsername().equals("Freddy")){
freddyList.add(user);
} else if(user.getUsername().equals("Hans")) {
hansList.add(user);
} else if(user.getUsername().equals("Michael")) {
michaelList.add(user);
}
});
But, I suggest you can directly get the list of users by grouping by userName using Java streams (Collectors.groupingBy) as shown below in a single line:
Map<String, List<User>> groupByUser =
userList.stream().collect(Collectors.groupingBy(User::getUserName));
You seems to misunderstand some things :
usersList.getUsername() does not exists, you cannot get a username from a list
usersList.Object does not belong to anything too
You'd better use filter() :
List<User> freddyList = usersList.stream().filter(u -> u.getUserName().equals("Freddy"))
.collect(Collectors.toList());
List<User> hansList = usersList.stream().filter(u -> u.getUserName().equals("Hans"))
.collect(Collectors.toList());
List<User> michaelList = usersList.stream().filter(u -> u.getUserName().equals("Michael"))
.collect(Collectors.toList());
These will iterate over the list which contains all the guys, keep the ones that have the good username, and collect them into the good list
You can do a for each user if the value of the current user is equal to either one of these do that bit of code.
Example:
for(User user : userList){
if (user.getUsername().equals("Freddy"))
{
freddyList.add(user);
}
else if(user.getUsername().equals("Hans"))
{
hansList.add(user);
}
else
{
michaelList.add(user);
}
You can change it to a switch if you like, use another method, but that is the best logic you need to get it working.
Also make sure your object class User has the getUsername() method defined so you can use it in your code.
I Have two sets of Roles. One from a database, another from UI. I also wrote comparing method of two Roles based on the giving name of the Role.
Problem: I'd like to extract role (keep one from database) if the compare value returns true using lambdas expression.
The matchingRoleNames work well, but I'm looking the best lambdas practices to do that without for loop.
This is my set result which worked fine:
Set<Role> rolesFromUI = user.getRolesFromUI();
Stream <Role> roleStreamFromDB = roleService.getAllRolesStreamFromDatabase();
Set<Role> matchingRoleNames = roleStreamFromDB.filter(role->
{
for(Role roleTmp:rolesFromUI){
if(roleTmp.compareTo(role)==0){
return true;
}
}
return false;
})
public int compareTo(Role role){
return this.roleName.compareTo(role.getRoleName());
}
Thank you
instead of using the for each loop inside the filter intermediate operation of the first stream, you can just stream over the other set and see if any of the elements in that match any element of the first stream.
i.e:
Set<Role> matchingRoleNames = roleStreamFromDB.filter(e -> rolesFromUI.stream().anyMatch( x -> x.compareTo(e) == 0))
.collect(Collectors.toSet());
As you are saying that you want to compare roles by name, you can do what you want by first collecting the names of the UI roles to a Set and then, when filtering the DB roles, just check if the name of the current role is in the set:
// Collect UI role names
Set<String> uiRoleNames = user.getRolesFromUI().stream()
.map(Role::getRoleName)
.collect(Collectors.toSet());
// Filter DB roles
Set<Role> filteredRoles = roleService.getAllRolesStreamFromDatabase()
.filter(role -> uiRoleNames.contains(role.getRoleName()))
.collect(Collectors.toSet());
This is both simple and efficient, because the Set returned by Collectors.toSet() is expected to be efficient for lookups (this means that its contains method will be O(1) for the average case).
If you want to be 100% sure that the returned set will be O(1) for contains, you might collect the UI role names as follows:
Set<String> uiRoleNames = user.getRolesFromUI().stream()
.map(Role::getRoleName)
.collect(Collectors.toCollection(HashSet::new));