.map and mutation of original object's content - java

Consider this code
List<Employee> employees = new ArrayList<Employee>();
employees.add(new Employee("A", "B"));
employees.add(new Employee("A1", "B1"));
employees.stream().map(e-> { e.setfName("Mr. " + e.getfName()); return e; }); // 1. DOES NOT mutate employees' content
employees.stream().map(e-> { e.setfName("Mr. " + e.getfName()); return e; }).collect(Collectors.toList()); // 2. DOES mutate employees' content
employees.stream().map(e-> new Employee ("Mrs. " + e.getfName(), e.getlName())).collect(Collectors.toList()); // 3. DOES NOT put new objects in original list
Not sure if I understand the reason for this behavior. If the stream does create a separate memory, shouldn't 2 also NOT mutate the original list's content?

employees.stream().map(e-> { e.setfName("Mr. " + e.getfName()); return e; }); // 1. DOES NOT mutate employees' content
Line 1 only doesn't mutate employees' content because it doesn't do anything at all. Streams aren't evaluated until a terminal operation is called.
Line 2 creates a new list to hold the updated employees, but the original employees are updated -- so you have two independently modifiable lists that contain exactly the same employees with exactly the same data where you can't modify one without the other. So with the result of line 2, you could add new Employee objects and not modify employees, but you couldn't modify the employees in that List without modifying the originals too (because they're the same employees).
Only Line 3 creates new Employee objects which exist independently of the originals.

An important thing to understand about streams is that they are lazy. If you map the values, the mapping function is only called when you retrieve values from the stream. collecting a stream is one way to use the values and force the mutations, which is why the second example mutates the list and the first one doesn't. The third example doesn't mutate the objects in place, instead creating a new Employee for each one, so it creates a new cloned list that is unrelated to the original list.

Related

Java console - Remove element from arraylist, still shows up when printing

I have an arraylist which I can add to using constructors. That works perfectly fine, and they show up when printing, but when I remove them, it still shows up in the list when printing, even though when I try to remove it for a second time, it says "NullPointerException", or goes to my failsafe else if. The problem therefore have to be in the printing part, or am I thinking completely incorrectly? Is it not updating the arraylist after removing an element?
Thank you.
Instance stuff - Creating Arraylist/constructor(employeeVar = int)
static Employees[] employee = new Employees[employeeVar];
static List<Employees> EmployeesAL = new ArrayList<>(Arrays.asList(employee));
Printing Arraylist
else{
System.out.println("id: " + EmployeesAL.get(i).getEmployeeID() + "Name: " + EmployeesAL.get(i).getEmployeeName());
}
Removing element
else if (employee[idToRemove].getEmployeeID() == idToRemove && customer[idToRemove].getEmployeeName() != null){
EmployeesAL.remove(idToRemove);
employee[idToRemove] = null;
}
}
The way you set it up, EmployeesAL and employee are completely unbound. A change in one is not reflected in the other.
So, when you have: [Jane, Joe, Jack] in your employee, that means at first, EmployeesAL is also [Jane, Joe, Jack]. But if you then remove an employee, the effect is that the entry in the array is nulled out, but the entry in the arraylist is removed. So, removing Joe results in employee being [Jane, null, Jack] and EmployeesAL being [Jane, Jack]. Now Jack's position in the array is index 2 (employee[2]) and in the arraylist it's 1: EmployeesAL.get(1). Your code checks employee[idToRemove] and then asks EmployeeAL to remove that index, which, obviously, is going to fail.
Why do you have 2 data structures with the same data? Stop doing that. Pick one: Either have Employee[] employees, or have List<Employee>. You probably want only the latter:
List<Employee> employees = new ArrayList<>();
employees.addAll(Arrays.asList(new Employee("Jane"), new Employee("Joe"), etc);

Fetching and modifying an object in a set in Java

I have MyFinalSalad class consisting of the following elements:
AppleClass apple;
BananaClass banana;
PearClass pear;
List<SpicesClass> spices;
I have equals implemented such as 2 MyFinalSalad objects are equal, if they have same AppleClass, BananaClass, PearClass objects in them.
Now, I am creating a set of MyFinalSalad objects.
And I have the following code:
MyFinalSalad mySalad = new MyFinalSalad(apple, banana, pear);
SpiceClass cinnamon = new SpiceClass("cinnamon");
if (mySet.contains(mySalad)) {
// I want to fetch mySalad object in the set and add cinnamon to the list of spices
} else {
List<SpiceClass> spices = new ArrayList<>();
spices.add(cinnamon);
mySalad.setSpices(spices);
mySet.add(mySalad);
}
To summarize, if mySalad is already present in mySet, add the spice object to the list of spices in mySalad from mySet, else add mySalad to mySet after creating a new spice list, adding cinnamon to it and inserting list in mySalad.
My question is, if set already has mySalad and I want to add a new spice to the list in that object, how do I achieve it?
From https://stackoverflow.com/a/7283419/887235 I have the following:
mySet.stream().filter(mySalad::equals).findAny().orElse(null).getSpices().add(cinnamon);
Is this the only way or the right way to do it? Or is there a better way?
I was thinking that as I am already entering if after doing a contains check, orElse(null) will never be encountered. Thus null.getSpices() will never occur. Is this assumption correct?
Is there a better way to do it?
I cannot change Set to Map.
Your assumption is correct. The orElse(null) will never take place since you check if the set contains the salad right before. You could replace it with get().
However, I would also go one level before and handle it as an Optional, taking the advantage of isPresent and get method.
Salad mySalad = new Salad();
Optional<Salad> possibleSalad = set.stream().filter(mySalad::equals).findAny();
if (possibleSalad.isPresent()) {
Salad alreadyExistingSalad = possibleSalad.get();
// combine spices
} else {
// add new salad
}

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);

ConcurrentModificationExample and possible code simplification

I am throwing a ConcurrentModificationExample in the following code. I checked the API and it has to do with me trying to modify an object while another thread is iterating over it. I am clueless on the matter. I have created a comment above the line causing the exception. The Employee class doesn't contain anything other than the three variables for storing information.
I will be including the entire class as I would also like to know if there is a way to simplify my code as it repeats many things such as object creation and adding everything to the lists.
When you call employeesByAge in here with dep.employees:
dep.employeesByAge(dep.employees)
that will pass in dep.employees to employeesByAge such that in:
public class Department{
LinkedList<Employee> employees = ...;
public LinkedList<Employee> employeesByAge(LinkedList<Employee> outputList) {
...
}
}
both the employee member field and the outputList parameter refers to the same list, not just two list with the same content, but the same list instance.
Then you do:
for (Employee emp: employees){
//the list is null. add the first employee
if (outputList.isEmpty()){
outputList.add(emp);
} else
...
}
which iterates the employee and modifies outputList, but remember that these two are the same list object. Thus, ConcurrentModificationException.
What you're attempting to do is similar to this...
List list = ...;
for(item: list) {
list.add(item);
}
That is, you're updating a collection with elements by iterating over the same collection. All
outputList.add(...);
in Department are adding elements to the collection from the same collection 'employees'.
In main(), by doing
dep.employeesByAge(dep.employees)
you're attempting to update 'dep.employees' with 'dep.employees.' which results in concurrent modification exception.

How to add items found while iterating to an ArrayList?

ArrayList<Persons> persList = new ArrayList<Persons>();
for(Persons p : persList){
Persons pers = new Persons();
pers = service.getPersons(id);
p.setAddress(pers.getAddress());
persList.add(pers);
}
Is this the right way to add all found Persons to persList? Thank you in advance.
No, you shouldn't modify a list while you're iterating over it, other than via the Iterator.remove method. Aside from anything else, even if this code didn't throw an exception, it would go on forever unless persList was empty... there's always be new people to iterate over!
You should basically create a new list collecting the items to add, and then use addAll at the end:
ArrayList<Persons> persList = new ArrayList<Persons>();
// Populate the list, presumably
List<Persons> extraPeople = new ArrayList<Persons>();
for(Persons p : persList){
// Note: there's no point in creating a new object only to ignore it...
Persons pers = service.getPersons(id);
p.setAddress(pers.getAddress());
extraPeople.add(pers);
}
persList.addAll(extraPeople);
This code still doesn't make much sense in my view, as you're fetching via the same id value on every iteration... I can only hope this was an example rather than real code.
Also note that if each instance of your Persons class is meant to be a single person, it would be better to call it Person.

Categories