You want intersection of the list while saving of first list type.
Get the common field values in the Set.
valuesToCheck=secondList.stream().map(SecondListObject::commonFiled).collect(Collectors.toList);
'''
Apply a stream on the first while filtering based on the matching common field value in the set built in the previous step.
firstList.stream().filter(x->valuesToCheck.contains(x.getCommonFiled)).collect(toList)
You got the gist.
Answer from Gajraj Tanwar on Stack OverflowYou want intersection of the list while saving of first list type.
Get the common field values in the Set.
valuesToCheck=secondList.stream().map(SecondListObject::commonFiled).collect(Collectors.toList);
'''
Apply a stream on the first while filtering based on the matching common field value in the set built in the previous step.
firstList.stream().filter(x->valuesToCheck.contains(x.getCommonFiled)).collect(toList)
You got the gist.
Here is one possible solution. (Not tested.)
Assuming that the lists are are random access and the same length, and that neither the list elements or the properties are null:
List<SomeType> list1 = ...
List<SomeType> list2 = ...
List<SomeType> res =
IntStream.range(0, list1.size())
.filter(i -> list1.get(i).getSomeProp().equals(list2.get(i).getSomeProp()))
.map(i -> list1.get(i))
.Collectors.toList();
(This will also work if the lists aren't random access, but it will be O(N^2).)
Supposing that the class is named Foo and that the field to change is String valueToReplace with getter/setter, you could use listOne.replaceAll() in this way :
list1.replaceAll(one -> list2.stream()
.filter(other -> other.getCode().equals(one.getCode())
.findAny()
.map(Foo::getValueToReplace)
.ifPresent( newValue -> one.setValueToReplace(newValue));
return one;
)
The idea is for each elements of list1 you replace the valueToReplace field of it by the value of the first match of list2. Otherwise you do nothing.
This code is not as efficient as it could but for small lists it is perfect.
For bigger lists, using a Map to store code/valueToReplace is very welcome.
// supposing that the code are unique in each list
Map<Integer, String> mapOfValueToReplaceByCode =
list2.stream()
.collect(toMap(Foo::getCode, Foo::getValueToReplace));
list1.replaceAll(one -> {
String newValue = mapOfValueToReplaceByCode.get(one.getCode());
if (newValue != null){
one.setValueToReplace(newValue);
}
return one;
)
Just keep the replace values in a map (with code as key) and then iterate over list1 to modify where necessary.
Map<String, String> replaceValues = list2.stream()
.collect(Collectors.toMap(x -> x.code, x -> x.tobereplace));
list1.stream
.filter(x -> replaceValues.containsKey(x.code))
.forEach(x -> x.tobereplace = replaceValues.get(x.code));
EDIT
As josejuan points out in the comments, the Collectors.toMap will throw an exception if list2 contains duplicate values. The OP doesn't really specify what to do in that case, but the solution is using a merge function in the Collectors.toMap.
This will use the first element it encounters with any given code:
Map<String, String> replaceValues = list2.stream()
.collect(Collectors.toMap(x -> x.code, x -> x.tobereplace, (x1, x2) -> x1));
The merge policy could be anything, like using the first element with a non-empty value e.g.
If the list was known to not have duplicates, please use a Set instead of a List. It will make things clearer for anyone reading the code, and help you avoid unnecessary checks.
It is not a good idea to have side effects like setting of properties in the middle of a Stream. A better solution is to just create a Map of your User instances:
Map<Long, User> usersById =
users.stream().collect(Collectors.toMap(User::getUserId, u -> u));
Now you can simply loop through your WorkflowStatus objects:
for (WorkflowStatus status : workflowStatuses) {
Long userId = status.getUserId();
Long statusId = status.getWorkflowStatusId();
usersById.get(userId).setWorkflowStatusId(statusId);
}
<User> filteredList = users.stream().filter(user -> workflowStatuses
.stream()
.allMatch(user.getUserId().equals(workflowStatus.getWorkflowStatusId)))
.map(how to set workflowStatusId in user? can i do something like user->user.setWorkflowStatusId((workflowStatus.getWorkflowStatusId)))
.collect(Collectors.toList());
On this generate the lamba exprestion loop on java 8
workflowStatus.foreach( data ) -> { user.setworkflowStatusId(data.getWorkflowStatusId);user.setuserId(data.getuserId);}
Or simple replace below
<User> filteredList = users.stream().filter(user -> workflowStatuses
.stream()
.allMatch(user.getUserId().equals(workflowStatus.getWorkflowStatusId)))
.map(workflowStatus.foreach( data ) -> { user.setworkflowStatusId(data.getWorkflowStatusId);user.setuserId(data.getuserId)})
.collect(Collectors.toList());
Let's run through each part of the code. First, createSharedListViaStream:
public static List<SchoolObj> createSharedListViaStream(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
// We create a stream of elements from the first list.
List<SchoolObj> listOneList = listOne.stream()
// We select any elements such that in the stream of elements from the second list
.filter(two -> listTwo.stream()
// there is an element that has the same name and school as this element,
.anyMatch(one -> one.getName().equals(two.getName())
&& two.getSchool().equals(one.getSchool())))
// and collect all matching elements from the first list into a new list.
.collect(Collectors.toList());
// We return the collected list.
return listOneList;
}
After running through the code, it does exactly what you want it to do. Now, let's run through createSharedListViaLoop:
public static List<SchoolObj> createSharedListViaLoop(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
// We build up a result by...
List<SchoolObj> result = new ArrayList<SchoolObj>();
// going through each element in the first list,
for (SchoolObj one : listOne)
{
// going through each element in the second list,
for (SchoolObj two : listTwo)
{
// and collecting the first list's element if it matches the second list's element.
if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
{
result.add(one);
}
}
}
// We return the collected list
return result;
}
So far, so good... right? In fact, your code in createSharedListViaStream is fundamentally correct; instead, it is your createSharedListViaLoop that may be causing discrepancies in output.
Think about the following set of inputs:
List1 = [SchoolObj("nameA","SchoolX"), SchoolObj("nameC","SchoolZ")]
List2 = [SchoolObj("nameA","SchoolX"), SchoolObj("nameA","SchoolX"), SchoolObj("nameB","SchoolY")]
Here, createSharedListViaStream will return the only element of the first list that appears in both lists: SchoolObj("nameA","SchoolX"). However, createSharedListViaLoop will return the following list: [SchoolObj("nameA","SchoolX"),SchoolObj("nameA","SchoolX")]. More precisely, createSharedListViaLoop will collect the correct object, but it will do so twice. I suspect this to be the reason for the output of createSharedListViaStream to be "incorrect" based on comparison to the output of createSharedListViaLoop.
The reason that createSharedListViaLoop does this duplication is based on the lack of termination of its inner for loop. Although we iterate over all elements of the first list to check if they are present in the second, finding a single match will suffice to add the element to the result. We can avoid redundant element addition by changing the inner loop to the following:
for (SchoolObj one : listOne)
{
for (SchoolObj two : listTwo)
{
if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
{
result.add(one);
break;
}
}
}
Additionally, if you don't want duplicate Objects in your list (by location in memory), you can use distinct like so:
List<SchoolObj> result = ...;
result = result.stream().distinct().collect(Collectors.toList());
As a final caution, the above will keep the results distinct in the following scenario:
List<SchoolObj> list = new ArrayList<>();
SchoolObj duplicate = new SchoolObj("nameC", "schoolD");
listOne.add(duplicate);
listOne.add(duplicate);
list.stream().distinct().forEach(System.out::println);
// prints:
// nameC schoolD
However, it will not work in the following scenario, unless you override the equals method for SchoolObj:
List<SchoolObj> list = new ArrayList<>();
listOne.add(new SchoolObj("nameC", "schoolD"));
listOne.add(new SchoolObj("nameC", "schoolD"));
list.stream().distinct().forEach(System.out::println);
// prints (unless Object::equals overridden)
// nameC schoolD
// nameC schoolD
You can filter in one list if contains in another list then collect.
List<SchoolObj> listCommon = listTwo.stream()
.filter(e -> listOne.contains(e))
.collect(Collectors.toList());
You need to override equals() method in SchoolObj class. contains() method you will uses the equals() method to evaluate if two objects are the same.
@Override
public boolean equals(Object o) {
if (!(o instanceof SchoolObj))
return false;
SchoolObj n = (SchoolObj) o;
return n.name.equals(name) && n.school.equals(school);
}
But better solution is to use Set for one list and filter in another list to collect if contains in Set. Set#contains takes O(1) which is faster.
Set<SchoolObj> setOne = new HashSet<>(listOne);
List<SchoolObj> listCommon = listTwo.stream()
.filter(e -> setOne.contains(e))
.collect(Collectors.toList());
You need to override hashCode() method also along with equals() in SchoolObj class for Set#contains.(assuming name and school can't be null)
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + name.hashCode();
result = prime * result + school.hashCode();
return result;
}
Here you will get details how to override equals and hashCode in a better way
It's not the most efficient solution but the most terse code would be:
boolean equalLists = listA.size() == listB.size() && listA.containsAll(listB);
Update:
@WesleyPorter is right. The solution above will not work if duplicate objects are in the collection.
For a complete solution you need to iterate over a collection so duplicate objects are handled correctly.
private static boolean cmp( List<?> l1, List<?> l2 ) {
// make a copy of the list so the original list is not changed, and remove() is supported
ArrayList<?> cp = new ArrayList<>( l1 );
for ( Object o : l2 ) {
if ( !cp.remove( o ) ) {
return false;
}
}
return cp.isEmpty();
}
Update 28-Oct-2014:
@RoeeGavriel is right. The return statement needs to be conditional. The code above is updated.
ArrayList already have support for this, with the equals method. Quoting the docs
... In other words, two lists are defined to be equal if they contain the same elements in the same order.
It does require you to properly implement equals in your MyData class.
Edit
You have updated the question stating that the lists could have different orders. In that case, sort your list first, and then apply equals.
If I understand correctly, this is the example scenario:
- listOne [datab] items:
[A, B, C, D] - listTwo [front] items:
[B, C, D, E, F]
and what you need to get as an effect is:
- added:
[E, F] - deleted:
[A]
First thing first, I would use some type adapter or extend the different types from one common class and override the equals method so you can match them by id and name
Secondly, this is very easy operations on sets (you could use set's but list are fine too). I recommend using a library: https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/CollectionUtils.html
And now basically:
- added is
listTwo - listOne - deleted is
listOne - listTwo
and using java code:
- added:
CollectionUtils.removeAll(listTwo, listOne) - deleted:
CollectionUtils.removeAll(listOne, listTwo)
Otherwise, all collections implementing Collection (Java Docs) also has removeAll method, which you can use.
I propose solution using java 8 streams:
ArrayList<ObjOne> list = new ArrayList<>(Arrays.asList(new ObjOne("1","1"),new ObjOne("3","3"),new ObjOne("2","2")));
ArrayList<ObjTwo> list2 = new ArrayList<>(Arrays.asList(new ObjTwo("1","1"),new ObjTwo("3","3"),new ObjTwo("4","4")));
List<ObjOne> removed = list.stream().filter(o1 -> list2.stream().noneMatch(o2 -> o2.getId().equals(o1.getId())))
.collect(Collectors.toList());
System.out.print("added ");
removed.forEach(System.out::println);
List<ObjTwo> added = list2.stream().filter(o1 -> list.stream().noneMatch(o2 -> o2.getId().equals(o1.getId())))
.collect(Collectors.toList());
System.out.print("removed ");
added.forEach(System.out::println);
This is basically your solution but implemented using streams, which will make your code shorter and easer to read