List.stream().filter() vs removeIf - java-8

Please help me understand the difference.
I'm trying to remove elements from list. If I use stream().filter(), this works but when I use removeIf, it throws UnsupportedOperationException.
private void filterEmployees(EmployeeResponse employeeResponse) {
List<Employee> employees = employeeResponse.getEmployees();
List<Employee> employeesFiltered = employees
.stream()
.filter(employee -> employee.getRole().equals("01") || employee.getRole().equals("02") || employee.getRole().equals("03"))
.collect(Collectors.toList());
employeeResponse.setEmployees(employeesFiltered);
}
Below code throws UnsupportedOperationException.
private void filterEmployees(EmployeeResponse employeeResponse, List<String> rolesList) {
List<Employee> employees = employeeResponse.getEmployees();
employees.removeIf(employee -> !rolesList.contains(employee.getRole()));
employeeResponse.setEmployees(employees);
}
Exception:
java.lang.UnsupportedOperationException: remove
at java.base/java.util.Iterator.remove(Iterator.java:102)
at java.base/java.util.Collection.removeIf(Collection.java:545)

Streaming list with a filter simply filters the current list, skipping the unwanted values, and creates a new one. I would be equivalent to you iterating the list and when you see the value, skip it. Otherwise add it to the new list.
removeIf is a new method in Java 8. It is in the Collections interface which List extends. That method is not supported in any immutable collection or in List.of which creates an ImmutableCollection.
I can't tell if that is the problem so you may want to check if that instance is immutable by doing the following:
List<Employee> employees = employeeResponse.getEmployees();
System.out.println(employees.getClass().getName());
One way to possibly solve that problem is to do the following:
List<Employee> employee = new ArrayList<>(employeeResponse.getEmployees());
Which creates a mutable ArrayList and populates it with the employee list. Then removeIf should work as expected.

Related

Get a list of entities by a list of attributes in Spring Boot eg. findAllByIdIn(List<Integer> ids)

I need help getting a list of entities by a list of attributes, say "id', "name' etc. Below is what I have so far and I'm expecting three entities but the result shows only one. The logic of the code below is basically to grab all entries with LOGIN as eventType and customer Id in the event repo. We're then getting the "eventdetailsIds" into a separate collection from the result found and searching(findByIdIn) for them in the LoginEventRepo. EventDetailsId is more or less a foreign key that joins event repo and loginEvent Repo. PS. I have implemented manually but I just need a more concise approach. Thanks.
public List<CustomerEventResponse> getAllCustomerLogins(String customerId) {
List<CustomerEventResponse> customerEventResponses;
List<EventEntity> eventEntityList = eventRepository.findByCustomerIdAndEventType(customerId, LOGIN_EVENT);
List<LoginEventDetailsEntity> loginEventDetailsEntityList = new ArrayList<>();
List<Integer> eventDetailsIds = new ArrayList<>();
for(EventEntity e: eventEntityList){
eventDetailsIds.add(e.getEventDetailsId());
Optional<LoginEventDetailsEntity> loginEventDetails = loginEventDetailsRepository.findById(e.getEventDetailsId());
loginEventDetails.ifPresent(loginEventDetailsEntityList::add);
}
List<LoginEventDetailsEntity> loginDetails = loginEventDetailsRepository.findAllById(eventDetailsIds);
The database is below:
Thanks.

How to filter object lists and create another filtered lists from it

I receive a List of MediaDTO and this Object has two attributes:
String sizeType and String URL.
In 'sizeType' comes the image´s size: small, medium, large, and thumbnail.
So I have to filter the sizeType of these objects and create 4 new lists based on them.
This is how I get the List<MediaDTO> mediaDTO:
medias=[MediaDTO(sizeType=THUMBNAIL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/thumbnail/celular-iphone-11-azul.png), MediaDTO(sizeType=SMALL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/small/celular-iphone-11-azul.png), MediaDTO(sizeType=SMALL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/medium/celular-iphone-11-azul.png), MediaDTO(sizeType=LARGE, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/large/celular-iphone-11-azul.png), MediaDTO(sizeType=THUMBNAIL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/thumbnail/celular-iphone-11-vermelho.png), MediaDTO(sizeType=SMALL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/small/celular-iphone-11-vermelho.png), MediaDTO(sizeType=MEDIUM, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/medium/celular-iphone-11-vermelho.png), MediaDTO(sizeType=LARGE, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/large/celular-iphone-11-vermelho.png)]
I achieved filtering for one of the sizes. This works!
However, I could not figure out how can I filter over the 4 sizes and create 4 new lists of it.
If I fix some error another appears ... so I´m really stuck.
And by the way I´ve been searching for a solution on the internet and in the forum for a couple of days but did´nt find something that fits.
If someone might help, I´d really be grateful.
I was thinking about using a 'forEach' to filter but even like that, I could filter just one size.
Thanks in advance.
**This is what I got till now: **
public class ProcessProductDTO {
String processId;
OperationProcess operation;
String categoryId;
ProductDTO productDTO;
}
public class ProductDTO {
String id;
Boolean active;
String displayName;
String longDescription;
List<MediaDTO> medias;
List<AttributeDTO> attributes;
}
public class MediaDTO {
String sizeType;
String liveloUrl;
}
public Properties toOccProductPropertiesDTO(ProcessProductDTO processProductDTO) throws JsonProcessingException {
String pSpecs = convertAttributes(processProductDTO.getProductDTO().getAttributes());
//List<String> medias = convertMedias(processProductDTO.getProductDTO().getMedias());
return Properties.builder()
.id(processProductDTO.getProductDTO().getId()) .active(processProductDTO.getProductDTO().getActive())
.listPrices(new HashMap())
.p_specs(pSpecs)
//.medias(medias)
.displayName(processProductDTO.getProductDTO()
.getDisplayName())
.longDescription(processProductDTO.getProductDTO().getLongDescription())
.build(); }
private String convertAttributes(List<AttributeDTO> attributes) throws JsonProcessingException {
Map<String, String> attribs = attributes.stream()
.collect(Collectors.toMap(AttributeDTO::getName, AttributeDTO::getValue));
return objectMapper.writeValueAsString(attribs);
}
private List<MediaDTO> convertMedias(ProcessProductDTO processProduct, List<MediaDTO> mediaDTO){
List<MediaDTO> filteredList = processProduct.getProductDTO().getMedias();
Set<String> filterSet = mediaDTO.stream().map(MediaDTO::getSizeType).collect(Collectors.toSet());
return filteredList.stream().filter(url -> filterSet.contains("SMALL")).collect(Collectors.toList());
}
UPDATE
I got the following result:
private Properties toOccProductPropertiesDTO(ProcessProductDTO processProductDTO) throws JsonProcessingException {
String pSpecs = convertAttributes(processProductDTO.getProductDTO().getAttributes());
MediaOccDTO medias = convertMedias(processProductDTO.getProductDTO().getMedias());
return Properties.builder()
.id(processProductDTO.getProductDTO().getId())
.active(processProductDTO.getProductDTO().getActive())
.listPrices(new HashMap())
.p_specs(pSpecs)
.medias(medias)
.displayName(processProductDTO.getProductDTO().getDisplayName())
.longDescription(processProductDTO.getProductDTO().getLongDescription())
.build();
}
private MediaOccDTO convertMedias(List<MediaDTO> mediaDTOs){
String smallImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.SMALL);
String mediumImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.MEDIUM);
String largeImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.LARGE);
String thumbImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.THUMB);
return MediaOccDTO.builder()
.p_smallImageUrls(smallImageUrls)
.p_mediumImageUrls(mediumImageUrls)
.p_largeImageUrls(largeImageUrls)
.p_thumbImageUrls(thumbImageUrls)
.build();
}
private String generateOccUrl(List<MediaDTO> mediaDTOs, ImageSizeType imageSizeType){
return mediaDTOs.stream()
.filter(m -> m.getSizeType().equals(imageSizeType))
.map(MediaDTO::getLiveloUrl)
.reduce(",", String::concat);
}
The problem is:
the comparison: m.getSizeType().equals(imageSizeType)
is always false, so the list gets created empty...
Though the question is laborious, I could think of the requirement being, you need to create 4 new lists based on sizeType.
Stream collector, can collect the results to a single data structure. It can be a list, set, Map etc.
Since you need 4 lists based on sizeType, you will need to pass through the stream 4 times to create 4 lists.
Another Alternate will be to create a Map<SizeType, List<MediaDTO>>
This can be achieved through,
mediaDTO.stream().collect(Collectors.toMap(i -> i.getSizeType(), i->i)
I think the toMap doesn't collect the values in a list. We need groupingBy instead.
mediaDTO.stream()
.collect(Collectors.groupingBy(MediaDTO::getSizeType));

Functional programming in java: Cloning Vs Mutating. Good or bad?

Mutating:
"transformEmployeeNameToUpperCase" function to transform employee name to uppercase.
List<Employee> employeesStartsWithDInUppercase1 = employees.stream()
.filter(employee -> employee.getName().startsWith("D"))
.map(Main::transformEmployeeNameToUpperCase)
.collect(Collectors.toList());
public static Employee transformEmployeeNameToUpperCase(Employee employee){
employee.setName(employee.getName().toUpperCase());
return employee;
}
Cloning:
"createEmployeeWithUpperCaseName" function to new employee with name in uppercase.
List<Employee> employeesStartsWithDInUppercase2 = employees.stream()
.filter(employee -> employee.getName().startsWith("D"))
.map(Main::createEmployeeWithUpperCaseName)
.collect(Collectors.toList());
public static Employee createEmployeeWithUpperCaseName(Employee e){
return new Employee( e.getId(), e.getName().toUpperCase(), e.getDesignation(), e.getAge());
}
Does "createEmployeeWithUpperCaseName" follow rule 1(above) as they say
yes: the employee is not being modified
In case of "transformEmployeeNameToUpperCase", does it follow rule 2(above)?
yes, although the rule uses an incorrect terminology. It creates an object, not a variable. You can't create a variable.
Is it good practice to use transformEmployeeNameToUpperCase way?
No, at least not the way you're doing it. There's nothing bad per se in modifying mutable objects: they're mutable for a reason. But a map() operation shouldn't modify its input and return it. You're perverting its purpose. A future reader of your code wouldn't expect a map operation to mutate its input, and you're thus making your code do unexpected things, leading to bugs and/or misunderstandings. It would be better to do it this way:
employees.stream()
.filter(employee -> employee.getName().startsWith("D"))
.forEach(e -> e.setName(e.getName().toUpperCase()));
That way, it makes it clear that the point of the pipeline is to have a side effect on the elements of the list. And it doesn't create a (probably) useless copy of the list, too.
Agree with #JB Nizet, but still if you don't want to change the original object but want to change the name of employee to Uppercase. use object cloning.
pseudo code:
List<Employee> employeeWithUpperCaseName = employees.parallelStream()
.filter(e -> e.getName().startsWith("D"))
.map(x -> {
Employee s = null;
try {
s = (Employee) x.clone();
s.setName(x.getName().toUpperCase());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
} finally {
return s;
}
})
.collect(Collectors.toList());
you can write it in better way.

How to use lambda/Stream api to filter distinct elements by object Attribute/Property

I have a List of Object. Every object has a map with a key named "xyz". I want elements in the list which has unique value to that particular key.
I know we can do this easily with set/map but I'm particularly looking for lambda solution.
I thought this would work.
list.stream()
.filter(distinctByXyz(f -> f.getMap.get("xyz")))
.collect(Collectors.toList()));
I've a function to distinct them
private <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor){
Map<Object, Boolean> map = new ConcurrentHashMap<>();
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
The problem is the function f.getMap() inside filter isnt working. Showing compilation error (Cannot resolve method)
You seem to have a few typos in your code, this should work:
list
.stream()
.filter(distinctByKey(f -> f.getMap().get("xyz")))
.collect(Collectors.toList());
You are using distinctByXyz when it should really be distinctByKey. Then f.getMap that should probably be f.getMap() and also you are slightly off with your parenthesis.

Find the difference between two collections in Java 8?

I am trying to make a List of all of the books in one Collection that are not present in another. My problem is that I need to compare based on book ID, so I can't just test to see whether a book in the first is contained in the second, I have to determine whether any book in the second collection has the same ID as a book in the first.
I have the below code to compare two collections of books and filter the first collection:
List<Book> parentBooks = listOfBooks1.stream().filter(book->
!listOfBooks2.contains(book)).collect(Collectors.toList());
The code doesn't work correctly because I am comparing the objects themselves. I need to compare the objects based on the bookId instead of the whole book object. How should I change the code so it can do the comparison based on the bookId (book.getId())?
List<Book> books1 = ...;
List<Book> books2 = ...;
Set<Integer> ids = books2.stream()
.map(Book::getId)
.collect(Collectors.toSet());
List<Book> parentBooks = books1.stream()
.filter(book -> !ids.contains(book.getId()))
.collect(Collectors.toList());
The problem is complex, but it boils down to one thing, knows your data. Is it immutables, entities with an id, duplicate entries etc?
The code below works for immutables with only values (and with possible duplicates).
It first tries to remove all entries in the before list (from the copied after-list).
What is left will be the added elements. The ones from the before-list that can be removed from the after-list are the unchanged ones.
The rest are the removed ones
public class ListDiffer<T> {
private List<T> addedList = new ArrayList<>();
private List<T> unchangedList = new ArrayList<>();
private List<T> removedList = new ArrayList<>();
public ListDiffer(List<T> beforeList, List<T> afterList) {
addedList.addAll(afterList); // Will contain only new elements when all elements in the Before-list are removed.
beforeList.forEach(e -> {
boolean b = addedList.remove(e) ? unchangedList.add(e) : removedList.add(e);
});
}
public List<T> getAddedList() {
return addedList;
}
public List<T> getUnchangedList() {
return unchangedList;
}
public List<T> getRemovedList() {
return removedList;
}
}

Resources