How to loop through a List of Objects - java

I have a class called Group. Inside Group there is a list the contains objects of type Customer, like so:
Group.java:
private List<Customers> customers;
public List<Customer> getCustomers() {
return customers;
}
Then, like above, the class Customer has list that contains object of type EmailAddress, like so:
Customer.java:
private List<EmailAddress> emailAddresses;
public List<EmailAddress> getEmailAddress() {
return emailAddresses;
}
I want to be able to put this list of email addresses into my own list that I can then manipulate how I want. So in my Main.java I have:
Main.java:
List<Customer> customerList = group.getCustomers(); //group is the object to get <Customer> List
for (int i=0; i<customerList.size(); i++) {
List<EmailAddress> emails = customerList.get(i).getEmailAddresses();
}
Will I then, outside of this for loop, have a List of all the emails of the customers that I can use? Is this the correct way to populate a list that I can then utilize and pull data from outside of this loop? Outside of the loop I want to be able to look at different parts of the list like:
emails.get(3);
or
emails.get(7);
at my own discretion.

You need to initialize the list outside the for cycle
List<EmailAddress> emails = new ArrayList<>();
for (int i=0; i<customerList.size(); i++) {
emails.add(customerList.get(i).getEmailAddresses());
}

List<Customer> customerList = group.getCustomers();
List<EmailAddress> finalEmailAddressList = new ArrayList<>();
for (Customer : customerList) {
if (customer != null) {
finalEmailAddressList.add(customer.getEmailAddresses());
}
}
This should give you a list of all email addresses in the end. You can also use the addAll method to add a collection to a collection (at specific indexes also), in this case, a list.

If access to data is required from an outer scope, the data has to exist in the outer scope. Therefore, you need to declare it in the outer scope of the loop:
List<Customer> customerList = group.getCustomers();
List<EmailAddress> emails = new ArrayList<>(customerList.size());
for (Customer customer : customerList) {
emails.addAll(customer.getEmailAddresses();
}

You can do it with streams:
List<Group> groupList = ...
List<EmailAddress> address = groupList
.stream()
.flatMap(g -> g.getCustomers().stream())
.flatMap(c -> c.getEmailAddresses().stream())
.collect(Collectors.toList());

You can do it in functional style:
List<EmailAddress> emails = costumerList.stream()
.map(Customer::getEmailAddresses)
.flatMap(Collection::stream)
.collect(Collectors.toList());
The most interesting part here is the flatMap() method which does two things:
It maps a List<EmailAddress> to a Stream<EmailAddress> by using method reference of the Collection.stream() method
It flattens the result so that the result is of type Stream<EmailAdress>
Consequently, the result of the collect() method will be of type List<EmailAdress>
In contrast, if the flatMap() line was omitted, the result would be of type List<List<EmailAddress>>.
Similarly, if the map() method was used instead of flatmap(), the result would be of type List<Stream<EmailAddress>>

Related

How to get unique values from list of list and store in set by using streams

I have classes Employee and Address as follows:
public static class Employee {
private List<Address> address;
// getters, etc.
}
public static class Address {
private String city;
// getters, etc.
}
I am learning streams, I am trying to iterate over the objects from list of list and create a set of unique values. I was able to get it working by using nested for loop. How can I convert below code to streams?
public static Set<String> getCityUniquName(List<Employee> emp){
Set<String> cityUniquName = new HashSet<>();
for(Employee e: emp){
List<Address> addList = e.getAddress();
for(Address add: addList){
cityUniquName.add(add.getCity());
}
}
return cityUniquName;
}
Since each Employee is associated with a collection of Address instances, you need to apply of the stream operations that allow to flatten the data, namely either flatMap(), or mapMulty().
Note that flatMap() expects a function producing a Stream,not a Collection like shown in another answer.
Stream.flatMap
To turn a Stream of Employee into a Stream of Address in the mapper function of flatMap() we need to extract the collection of addresses and create a stream over it.
The only thing left is to get the city name and collect the result into a set.
public static Set<String> getCityUniqueName(List<Employee> emp) {
return emp.stream()
.flatMap(e -> e.getAddress().stream())
.map(Address::getCity)
.collect(Collectors.toSet());
}
Stream.mapMulti
mapMulti() expects a BiConsumer, which in turn takes two arguments: stream element (having an initial type) and a Consumer of the resulting type. Every object offered to the consumer would appear in the resulting stream.
public static Set<String> getCityUniqueName1(List<Employee> emp) {
return emp.stream()
.<Address>mapMulti((e, c) -> e.getAddress().forEach(c))
.map(Address::getCity)
.collect(Collectors.toSet());
}
Use flatMap to flatten the Address list and then use map to get the city from each Address object and then collect into Set
Set<String> cityUniquName = emp.stream()
.map(Employee::getAddress)
.flatMap(List::stream)
.map(Address::getCity)
.collect(Collectors.toSet());

Null safe concatenation of two nullable list of items

I'm trying to add two nullable list using below:
List<Employee> employees = null;
if (<some condition>) {
employees = employeeService.getEmployees(<some criteria>);
// Add another list of employees
if (<some condition>) {
List<Employee> employeesSubList = employeeService.getEmployees(<some other criteria>));
if (!isEmpty(employeesSubList)) {
if (!isEmpty(employees)) {
employees.addAll(employeesSubList);
} else {
employees = employeesSubList;
}
}
}
}
This works but the code looks horribly ugly. There is a hard condition that the parent List<Employee> employees will be null instead of empty list in case no employees are present.
Is there a cleaner way to do the same?
I tried Java 8 approach but IntelliJ throws some warnings on .orElse(emptyList()).addAll(employeesSubList):
List<Employee> employees = null;
if (<some condition>) {
employees = employeeService.getEmployees(<some criteria>);
// Add another list of employees
if (<some condition>) {
List<Employee> employeesSubList = employeeService.getEmployees(<some other criteria>));
if (!isEmpty(employeesSubList)) {
Optional.ofNullable(employees).orElse(emptyList()).addAll(employeesSubList);
}
}
}
The "problem" Intellij is probably telling you is that emptyList().addAll() is 1) an error because that's an immutable list 2) is never assigned to anything, and it returns a boolean, so you need a different way to get that data
Plus, you could get rid of the if statement because adding an empty list to another list is a no-op
For example
// assumes employees is not null
employees.addAll(
Optional.ofNullable(employeeService.getEmployees(<some other criteria>))).orElse(emptyList())
);
Is there a cleaner way?
IMO no.
You could argue that testing for null is cleaner than calling an isEmpty method, but I'm not convinced.
You could argue that hiding the null tests behind an Optional is cleaner, but I'm not convinced.
But the real problem is this:
There is a hard condition that the parent List employees will be null instead of empty list.
That is what is causing you to have to deal with the null-safety issue. The clean solution is to use an empty list; i.e. a list with no elements.
Then you can rewrite your example code as:
List<Employee> employees = Collections.emptyList();
if (<some condition>) {
employees = employeeService.getEmployees(<some criteria>);
// Add another list of employees
if (<some condition>) {
List<Employee> employeesSubList = employeeService.getEmployees(<some other criteria>));
if (!employees.isEmpty()) {
employees.addAll(employeesSubList);
} else {
employees = employeesSubList;
}
}
}
(Note: if you use Collections.emptyList(), be aware that it returns an immutable list. So you can't add elements to it. The alternative is to use new ArrayList<>() to create a mutable list that is initially empty.)
In short, if you want "clean" you are looking the wrong part of your code-base. IMO.

How to get Array List from an Object of Lists in Java Spring Boot?

public class EmployeeDetails {
// this itself is an Array List defined in other model as List<EmployeeDetails> employeeDetails
String name;
String age;
List<EmployeeDetails> employee // How to get this ?
}
I tried below but not able to access all values in that Array List..
public List<EmployeeDetails> printEmployeeDetails(List<EmployeeDetails> data) {
return data.getEmployeeDetails().stream().findFirst().get().getEmployee()
// this is only giving me first list. How can i access all values present in this Array List ?
}
If you want to get the combined distinct list of all the employee field inside EmployeeDetails class, you can do as follows:
return data.stream() // Stream<EmployeeDetails> (elements of data)
.map(EmployeeDetails::getEmployee) // Stream<List<EmployeeDetails>> (stream of employee lists inside EmployeeDetails)
.flatMap(Collection::parallelStream) // Stream<EmployeeDetails> (each details of Employee, which is EmployeeDetials)
.distinct()
.collect(Collectors.toList());
In order to get a stream of a list from another stream, you can use the flatMap operator.
return data.getEmployeeDetails()
.stream()
.flatMap(employee -> employee.getEmployee().stream())
.collect(Collectors.toList());
I'm not sure if that answer is sufficient, you didn't specify if it should recursively get all employees as well as what happens with duplicates.

Java - Filter a list based on multiple values

I am trying to filter out a list based on values. I have two List. One is a list of names which i want to remove i.e present in animalList. And another is the main primary list AnimalPrimaryDataPojoFilterList from where i have to remove the object which matches the names from animalList. Now i do have the solution but i think it takes lot of time. Below is the code. I am using Java 8. Can it be optimised?
if(animalList!=null && animalList.size()>0)
{
for(AnimalFilterPojo dtoObject:animalList)
{
if(!dtoObject.getApproved())
{
for(AnimalPrimaryDataPojo mainDtoObject: AnimalPrimaryDataPojoFilterList)
{
if(mainDtoObject.getAnimalName().equalsIgnoreCase(dtoObject.getValue()))
{
AnimalPrimaryDataPojoFilterList.remove(mainDtoObject);
}
}
}
}
Use removeAll() method.
AnimalPrimaryDataPojoFilterList.removeAll(animalList);
It will remove the objects of animalList from AnimalPrimaryDataPojoFilterList
N.B: You need to implement hashCode() and equals() method in AnimalFilterPojo
You can use Java 8 streams to filter the list. In below example Parent is the object which has abc property of type String. We are filtering List<Parent> objs using List<String> names
public class ListFilterDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
List<Parent> objs = new ArrayList<>();
List<Parent> filtersObjs = objs.parallelStream().filter((obj) -> names.contains(obj.getAbc())).collect(Collectors.toList());
}
}
class Parent {
private String abc;
public Parent(String abc) {
this.abc = abc;
}
public String getAbc() {
return this.abc;
}
}
You can try this:
if(animalList!=null && animalList.size()>0)
animalList.removeIf(animal ->
AnimalPrimaryDataPojoFilterList.stream()
.filter(filter -> !filter.getApproved())
.map(AnimalFilter::getValue)
.collect(Collectors.toList()).contains(animal.getAnimalName()));
to explain the code: here we use removeIf() on the List to remove the objects using a Predicate that is a lambda that receives the animal and filters the list by removing the elements by name where name is taken from a list generated as a selection of the AnimalPrimaryDataPojoFilterList of the elments that have the approved flag (the second filter), extracting the value (using the map) and constructing a list out of it using a Collector.
The portion:
AnimalPrimaryDataPojoFilterList.stream()
.filter(filter -> !filter.getApproved())
.map(AnimalFilter::getValue)
.collect(Collectors.toList())
generates the list to be used as a filter
animalList.removeIf(animal ->
<generated list>.contains(animal.getAnimalName()));
uses list generated in place to apply the filter.
Beware that this of course modifies the list you have
Besides, you should not start a variable with a capital letter like you did for AnimalPrimaryDataPojoFilterList.
you can use removeIf then use AnimalPrimaryDataPojoFilterList as the source in which case you'll need to invert your logic within the if block i.e:
if(animalList != null && animalList.size() > 0){
AnimalPrimaryDataPojoFilterList.removeIf(x ->
animalList.parallelStream()
.filter(e -> !e.getApproved() && x.getAnimalName().equalsIgnoreCase(e.getValue())).findAny().orElse(null) != null);
}

Collect values from list of POJO using Functional interfaces (lambdas)

How can I iterate over list of POJO classes for collecting result of some methods in a standard way to avoid copy past?
I want to have code like this:
//class 'Person' has methods: getNames(), getEmails()
List<Person> people = requester.getPeople(u.getId());
String names = merge(people, Person::getNames);
String emails = merge(people, Person::getEmails);
instead of such copy-pasted logic:
List<Person> people = requester.getPeople(u.getId());
Set<String> namesAll = new HashSet<>();
Set<String> emailsAll = new HashSet<>();
for (Person p : people) {
if(p.getNames()!=null) {
phonesAll.addAll(p.getNames());
}
if(p.getEmails()!=null) {
emailsAll.addAll(p.getEmails());
}
}
String names = Joiner.on(", ").skipNulls().join(namesAll);
String emails = Joiner.on(", ").skipNulls().join(emailsAll);
Thus, is it possible to implement some standard approach for iterating and processing special method of POJO in list that could be reused?
If I understand you correctly, you want something like this :
String names = people.stream().flatMap(p->p.getNames().stream()).distinct().collect(Collectors.joining(", "));
Now, if you want to save typing that line for each property, you can have this merge method as you suggested :
public static String merge (List<Person> people, Function<Person, Collection<String>> mapper)
{
return people.stream().flatMap(p->mapper.apply(p).stream()).distinct().collect(Collectors.joining(", "));
}
This would make your first snippet work.
Now, you can make this method generic :
public static <T> String merge (List<T> list, Function<T, Collection<String>> mapper)
{
return list.stream().flatMap(p->mapper.apply(p).stream()).distinct().collect(Collectors.joining(", "));
}
I think this should work (haven't tested it).

Categories

Resources