Is ArrayList.forEach() a mutative operation? - java

I have a HashMap groups which maps to ArrayList objects for its values. I wanted to re-assign the values given they meet a particular condition (i.e. in this case, they are present in another list) and came across the forEach method. Here's how I implemented it. I could not find any documentation as to whether it is able to transform my data hence my question.
Here's the code:
groups.get(request.getGroup()).forEach(
member -> {
int updatedMemberIndex = request.getGroupMembers().indexOf(member);
if (updatedMemberIndex != -1)
member = request.getGroupMembers().get(updatedMemberIndex);
}
);
And here are the method signatures for the relevant methods in request object:
List<GroupMember> getGroupMembers()
Group getGroup()

No, you cannot mutate the collection in this way. member is a local variable, so reassigning it in the if doesn't do anything.
You could use member to change properties of the GroupMember object itself, but that's not what you want.
It looks like you're trying to perform a map operation. You probably want something like:
List<GroupMember> newList = groups.get(request.getGroup()).stream().map(
member -> {
int updatedMemberIndex = request.getGroupMembers().indexOf(member);
return updatedMemberIndex != -1
? request.getGroupMembers().get(updatedMemberIndex)
: member;
}
).collect(Collectors.toList());

Related

Add object X to collection only if collection does not contain object with one matching property

I just started messing around with Java streams and I wrote something like this:
List<Device> devicesToDelete = new ArrayList<>();
List<Device> oldDeviceList = getCurrentDevices();
for (Device deviceFromOldList : oldDeviceList)
{
// part to simplify
boolean deviceNotExistOnDeleteList =
devicesToDelete.stream().noneMatch(nd -> nd.id == deviceFromOldList.id);
if (deviceNotExistOnDeleteList) {
devicesToDelete.add(deviceFromOldList);
}
// part to simplify end
}
Can it be simplified even more?
I'm not using Set because my Device class .equals() implementation compares all fields in that class. And here I need to compare only id field.
Just use a Map
Map<Object, Device> devicesToDelete = new HashMap<>();
List<Device> oldDeviceList = getCurrentDevices();
for(Device deviceFromOldList: oldDeviceList) {
devicesToDelete.putIfAbsent(deviceFromOldList.id, deviceFromOldList);
}
// in case you need a Collection:
Collection<Device> allDevicesToDelete = devicesToDelete.values();
putIfAbsent will only store the mapping if the key is not already present. This will get you the performance of hashing while only considering the ID.
You may change the type argument Object in Map<Object,Device> to whatever type your ID has, though it doesn’t matter for the operation, if all you need at the end, is the Collection<Device>.
You can use a Stream, e.g.
Map<Object, Device> devicesToDelete = getCurrentDevices().stream()
.collect(Collectors.toMap(
deviceFromOldList -> deviceFromOldList.id, Function.identity(), (a,b) -> a));
though, it’s debatable whether this is a necessary change. The loop is not bad.

Iterate efficiently through 2 different List with same Type of Object(Java8)

I have two list containing an important number of object with each N elements:
List<Foo> objectsFromDB = {{MailId=100, Status=""}, {{MailId=200, Status=""}, {MailId=300, Status=""} ... {MailId=N , Status= N}}
List <Foo> feedBackStatusFromCsvFiles = {{MailId=100, Status= "OPENED"}, {{MailId=200, Status="CLICKED"}, {MailId=300, Status="HARDBOUNCED"} ... {MailId=N , Status= N}}
Little Insights:
objectFromDB retrieves row of my database by calling a Hibernate method.
feedBackStatusFromCsvFiles calls a CSVparser method and unmarshall to Java objects.
My entity class Foo has all setters and getters. So I know that the basic idea is to use a foreach like this:
for (Foo fooDB : objectsFromDB) {
for(Foo fooStatus: feedBackStatusFromCsvFiles){
if(fooDB.getMailId().equals(fooStatus.getMailId())){
fooDB.setStatus(fooStatus.getStatus());
}
}
}
As far as my modest knowledge of junior developer is, I think it is a very bad practice doing it like this? Should I implement a Comparator and use it for iterating on my list of objects? Should I also check for null cases?
Thanks to all of you for your answers!
Assuming Java 8 and considering the fact that feedbackStatus may contain more than one element with the same ID.
Transform the list into a Map using ID as key and having a list of elements.
Iterate the list and use the Map to find all messages.
The code would be:
final Map<String, List<Foo>> listMap =
objectsFromDB.stream().collect(
Collectors.groupingBy(item -> item.getMailId())
);
for (final Foo feedBackStatus : feedBackStatusFromCsvFiles) {
listMap.getOrDefault(feedBackStatus.getMailId(), Colleactions.emptyList()).forEach(item -> item.setStatus(feedBackStatus.getStatus()));
}
Use maps from collections to avoid the nested loops.
List<Foo> aList = new ArrayList<>();
List<Foo> bList = new ArrayList<>();
for(int i = 0;i<5;i++){
Foo foo = new Foo();
foo.setId((long) i);
foo.setValue("FooA"+String.valueOf(i));
aList.add(foo);
foo = new Foo();
foo.setId((long) i);
foo.setValue("FooB"+String.valueOf(i));
bList.add(foo);
}
final Map<Long,Foo> bMap = bList.stream().collect(Collectors.toMap(Foo::getId, Function.identity()));
aList.stream().forEach(it->{
Foo bFoo = bMap.get(it.getId());
if( bFoo != null){
it.setValue(bFoo.getValue());
}
});
The only other solution would be to have the DTO layer return a map of the MailId->Foo object, as you could then use the CVS list to stream, and simply look up the DB Foo object. Otherwise, the expense of sorting or iterating over both of the lists is not worth the trade-offs in performance time. The previous statement holds true until it definitively causes a memory constraint on the platform, until then let the garbage collector do its job, and you do yours as easy as possible.
Given that your lists may contain tens of thousands of elements, you should be concerned that you simple nested-loop approach will be too slow. It will certainly perform a lot more comparisons than it needs to do.
If memory is comparatively abundant, then the fastest suitable approach would probably be to form a Map from mailId to (list of) corresponding Foo from one of your lists, somewhat as #MichaelH suggested, and to use that to match mailIds. If mailId values are not certain to be unique in one or both lists, however, then you'll need something a bit different than Michael's specific approach. Even if mailIds are sure to be unique within both lists, it will be a bit more efficient to form only one map.
For the most general case, you might do something like this:
// The initial capacity is set (more than) large enough to avoid any rehashing
Map<Long, List<Foo>> dbMap = new HashMap<>(3 * objectFromDb.size() / 2);
// Populate the map
// This could be done more effciently if the objects were ordered by mailId,
// which perhaps the DB could be enlisted to ensure.
for (Foo foo : objectsFromDb) {
Long mailId = foo.getMailId();
List<Foo> foos = dbMap.get(mailId);
if (foos == null) {
foos = new ArrayList<>();
dbMap.put(mailId, foos);
}
foos.add(foo);
}
// Use the map
for (Foo fooStatus: feedBackStatusFromCsvFiles) {
List<Foo> dbFoos = dbMap.get(fooStatus.getMailId());
if (dbFoos != null) {
String status = fooStatus.getStatus();
// Iterate over only the Foos that we already know have matching Ids
for (Foo fooDB : dbFoos) {
fooDB.setStatus(status);
}
}
}
On the other hand, if you are space-constrained, so that creating the map is not viable, yet it is acceptable to reorder your two lists, then you should still get a performance improvement by sorting both lists first. Presumably you would use Collections.sort() with an appropriate Comparator for this purpose. Then you would obtain an Iterator over each list, and use them to iterate cooperatively over the two lists. I present no code, but it would be reminiscent of the merge step of a merge sort (but the two lists are not actually merged; you only copy status information from one to the other). But this makes sense only if the mailIds from feedBackStatusFromCsvFiles are all distinct, for otherwise the expected result of the whole task is not well determined.
your problem is merging Foo's last status into Database objects.so you can do it in two steps that will make it more clearly & readable.
filtering Foos that need to merge.
merging Foos with last status.
//because the status always the last,so you needn't use groupingBy methods to create a complex Map.
Map<String, String> lastStatus = feedBackStatusFromCsvFiles.stream()
.collect(toMap(Foo::getMailId, Foo::getStatus
, (previous, current) -> current));
//find out Foos in Database that need to merge
Predicate<Foo> fooThatNeedMerge = it -> lastStatus.containsKey(it.getMailId());
//merge Foo's last status from cvs.
Consumer<Foo> mergingFoo = it -> it.setStatus(lastStatus.get(it.getMailId()));
objectsFromDB.stream().filter(fooThatNeedMerge).forEach(mergingFoo);

How to compare a field between two Lists of objects?

Let's suppose I've an object that looks like this:
public class Supermarket {
public String supermarketId;
public String lastItemBoughtId;
// ...
}
and I have two lists of supermarkets, one "old", another "new" (i.e. one is local, the other is retrieved from the cloud).
List<Supermarket> local = getFromLocal();
List<Supermarket> cloud = getFromCloud();
I would like to find all the pairs of Supermarket objects (given supermarketId) that have lastItemBoughtId different from one another.
The first solution I have in mind is iterating the first List, then inside the first iteration iterating the second one, and each time that local.get(i).supermarketId.equals(cloud.get(j).supermarketId), checking if lastItemBoughtId of the i element is different from the id of the j element. If it's different, I add the whole Supermarket object on a new list.
To be clearer, something like this:
List<Supermarket> difference = new ArrayList<>();
for (Supermarket localSupermarket : local) {
for (Supermarket cloudSupermarket : cloud) {
if (localSupermarket.supermarketId.equals(cloudSupermarket.supermarketId) &&
!localSupermarket.lastItemBoughtId.equals(cloudSupermarket.lastItemBoughtId))
difference.add(cloudSupermarket);
}
}
Clearly this looks greatly inefficient. Is there a better way to handle such a situation?
One solution :
Construct a Map of the Local supermarkets using the supermarketId as the key by running through the list once
Loop through the cloud list and do you comparison, looking up the local supermarket from your map.
i.e. O(n) instead of O(n2)
Here's a two-line solution:
Map<String, Supermarket> map = getFromLocal().stream()
.collect(Collectors.toMap(s -> s.supermarketId, s -> s));
List<Supermarket> hasDiffLastItem = getFromCloud().stream()
.filter(s -> !map.get(s.supermarketId).lastItemBoughtId.equals(s.lastItemBoughtId))
.collect(Collectors.toList());
I would put one of the lists in a Map with as key the Supermarket ID and as value the supermarket instance then iterate over the other getting from the Map and comparing the lastItemBoughtId.

Conditional mapping to new objects with a Java Stream

I have a stream of objects (a List) and want to create new objects from that stream, to be inserted into a Set. However, two or more objects in the incoming List may hash to the same Key in the Set, in which case I want to append a String from the nth List object to the one already in the Set instead of creating a new one.
Something like this, but in functional form:
HashSet<ClassB> mySet = new HashSet<>();
for (ClassA instanceA : classAList) {
if (mySet.contains(ClassB.key(instanceA))) { //static method call to find the key
mySet.get(instanceA).appendFieldA(instanceA.getFieldA());
} else {
mySet.add(new ClassB(instanceA));
}
}
return mySet;
In functional form I though of creating something like this:
List classAList = new ArrayList<>();
classAList.stream()
.map(instanceA -> new ClassB(instanceA))
.collect(Collectors.toSet());
But then of course that ignores the hashmap and I don't get to combine fields my multiple instances of ClassA that would all resolve to the same ClassB. I'm not sure how to put that in there. Do I need ignore the map() call and create a custom collector instead to do this job? There seems to be more than one way to do this, but I'm new to Streams.
It’s hard to understand what you actually want as your code example does not work at all. The problem is that a Set does not work like a Map, you can’t ask it for the contained equivalent object. Besides that, you are using different objects for your contains(…) and get(…) call. Also, it’s not clear what the difference between ClassB.key(instanceA) and new ClassB(instanceA) is.
Let’s try to redefine it:
Suppose we have a key type Key and a method Key.key(instanceA) to define the group candidates. Then we have a ClassB which is the resulting type, created via new ClassB(instanceA) for a single (or primary ClassA instance), having an .appendFieldA(…) method to receive a value of another ClassA instance when merging two group members. Then, the original (pre Java 8) code will look as follows:
HashMap<Key, ClassB> myMap = new HashMap<>();
for(ClassA instanceA: classAList) {
Key key=Key.key(instanceA);
if(myMap.containsKey(key)) {
myMap.get(key).appendFieldA(instanceA.getFieldA());
} else {
myMap.put(key, new ClassB(instanceA));
}
}
Then, myMap.values() provides you a collection of the ClassB instances. If it has to be a Set, you may create it via
Set<ClassB> result=new HashSet<>(myMap.values());
Note that this also works, when Key and ClassB are identical as it seems to be in your code, but you may ask youself, whether you really need both, the instance created via .key(instanceA) and the one created via new ClassB(instanceA)…
This can be simplified via the Java 8 API as:
for(ClassA instanceA: classAList) {
myMap.compute(Key.key(instanceA), (k,b)-> {
if(b==null) b=new ClassB(instanceA);
else b.appendFieldA(instanceA.getFieldA());
return b;
});
}
or, if you want it look even more function-stylish:
classAList.forEach(instanceA ->
myMap.compute(Key.key(instanceA), (k,b)-> {
if(b==null) b=new ClassB(instanceA);
else b.appendFieldA(instanceA.getFieldA());
return b;
})
);
For a stream solution, there is the problem, that a merge function will get two instances of the same type, here ClassB, and can’t access the ClassA instance via the surrounding context like we did with the compute solution above. For a stream solution, we need a method in ClassB which returns that first ClassA instance, which we passed to its constructor, say getFirstInstanceA(). Then we can use:
Map<Key, ClassB> myMap = classAList.stream()
.collect(Collectors.toMap(Key::key, ClassB::new, (b1,b2)->{
b1.appendFieldA(b2.getFirstInstanceA().getFieldA());
return b1;
}));
You can group the entries into a map that maps the hashed key to the list of elements and then call map again to convert that map into the set you are after. Something like this:
List classAList = new ArrayList<>();
classAList.stream()
.collect(Collectors.groupingBy(instanceA -> ClassB.key(instanceB)))
.entrySet()
.map(entry -> entry.getValue().stream()
.map(instanceA -> new ClassB(instanceA))
.reduce(null, (a,b) -> a.appendFieldA(b)))
.collect(Collectors.toSet());

How do I add to a set based on a specific predicate in Java?

I have a set in Java containing people:
Set<Person> uniquePeople = new HashSet<Person>();
I also have a list of a ton of people (of whom some possess the same name, eg. there is more than one "Bob" in the world).
List<Person> theWorld = // ... a BIG list of people
I want to iterate through this list and add a person to the uniquePeople set if and only if their name doesn't exist in the set, eg:
for (Person person : theWorld) {
uniquePeople.add(person IFF uniquePeople.doesNotContain(person.name));
}
Is there an easy way to do this in Java? Also, Guava might do this (?) but I haven't used it at all so I would appreciate a point in the right direction.
A better option would be to abandon using a Set and instead use a Map<String, Person> (keyed off of the name).
If you want to use a set, I suggest you use a new object type (that will just contain a name and maybe a reference to a Person).
Make sure you override equals so that it will only compare the names and then you can get a set of all unique people.
You could also subclass person to override the equals to do what you want.
Sets by definition will not do what you want with just a person since they depend entirely on using equals so these are your workaround options. You could also implement (or find online) a set that takes a comparator to use instead of relying on equals but I don't think such a class exists in standard java.
Use Guava's Equivalence to wrap your objects if you don't want to (or can't) override equals and hashCode:
Set<Equivalence.Wrapper<Person>> set = Sets.newHashSet();
Equivalence<Person> personEquivalence = Equivalence.onResultOf(
new Function<Person, String>() {
#Override public String apply(Person p) {
return p.name;
}
});
set.add(personEquivalence.wrap(new Person("Joe", "Doe")));
set.add(personEquivalence.wrap(new Person("Joe", "Doe")));
set.add(personEquivalence.wrap(new Person("Jane", "Doe")));
System.out.println(set);
// [PersonEquivalence#8813f2.wrap(Person{firstName=Jane, lastName=Doe}),
// PersonEquivalence#8813f2.wrap(Person{firstName=Joe, lastName=Doe})]
#DanielWilliams has a good idea too, but using Equivalence.Wrapper is more self-documenting - after all you don't want to create new object other than wrapper.
I am not sure why people got downvoted here.
You absolutely want a Set. Not only do your requirements meet the definition and functionality of 'Set' but Set implementations are designed to quickly identify duplicates either via hash or Comparative identity.
Let's say you had a List implementation that took a deligate and a predicate:
List uniquePeople = new PredicatedList(new ArrayList(),UnqiuePersonPredicate.getInstance())
public class PredicatedList<T> implements List<T> {
private List<T> delegate = null;
private Predicate<T> predicate;
public PredicatedList<List<T> delegate, Predicate p) {
this.delegate = delegate;
this.predicate = p;
}
// implement list methods here and apply 'p' before calling your insertion functions
public boolean add(Person p) {
if(predicate.apply(p))
delegate.add(p);
}
}
For this to work you would need to have a predicate that iterates over the list to find an equal element. This is an O(N) operation. If you use HashSet then it's O(1) < n < O(N). Your amortized identity check is the load factor * N. And, usually much closer to O(1)
If you use TreeSet you will get O(log(n)) because the elements are sorted by identity and you need only log(n) time to binary search.
Define hashCode()/equals based on 'name' or whatever you want and use HashSet or use TreeSet and define Comparable/Comparator
If your return type MUST be a List then do:
Set uniquePeople = new HashSet();
uniquePeople.add(...);
List people = new LinkedList(uniquePeople);
You could do it with guava, the only thing is that Person is going to need an equals/hashcode method.
ImmutableSet<String> smallList = ImmutableSet.of("Eugene","Bob");
ImmutableSet<String> bigList = ImmutableSet.of("Eugene","Bob","Alex","Bob","Alex");
System.out.println(Iterables.concat(smallList, Sets.difference(bigList, smallList)));
//output is going to be : [Eugene, Bob, Alex]

Categories