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

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.

Related

List.stream().filter() vs removeIf

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.

Project Reactor: Obtain Size of List Contained Within a Mono

I'm trying to do something again here in project reactor that I'm sure is reeeeeal simple for any of you project reactor gurus out there!
I've been searching and scratching around with this one for a while now, and feel I'm once again hitting a wall with this stuff.
All I'm trying to do is determine if a List of objects contained within a Mono is empty or not.
This is what I have so far:
private Mono<Boolean> isLastCardForAccount(String accountId) {
return cardService.getAccountCards(accountId)
.hasElement();
}
I'm thinking the above might work, but I'm having difficulty figuring out how to extract/access the 'Boolean' contained within the returned Mono. I think I have to use 'subscribe' somehow right?
I've mucked around with this stuff for a while now, but still no luck.
Here is how 'getAccountCards' is defined:
public Mono<List<Card>> getAccountCards(final String accountId) {
return cardCrudRepository.getCardsByAccountId(accountId)
.collectList();
}
From CardCrudRepository:
// #Query("SELECT * FROM card WHERE account_id = :accountId") <-Not sure if I need this
Flux<Card> getCardsByAccountId(String accountId);
And lastly, how I'm using 'isLastCardForAccount':
public Mono<Void> updateCardStatus(String accountId, String cardId, String cardStatus) {
return accountService.getAccount(accountId)
.map(Account::getClientId)
.map(clientId -> createUpdateCardStatusServiceRequestData(clientId, cardId, cardStatus))
.flatMap(requestData -> cartaClient.updateCardStatus(requestData)
.then(Mono.defer(() -> isCardBeingCancelled(cardStatus) ? allCardsCancelledForAccount(accountId) ? removeAccount(accountId) :
(isLostOrStolen(cardStatus) ? replaceCard(cardId, cardStatus).flatMap(this::updateCardNumber) : Mono.empty()) : Mono.empty())));
}
As always, any and all help and insight is tremendously appreciated!
I am not sure if this would resolve the issue but this way you can try to write your logic
return accountService.getAccount(accountId)
.map(Account::getClientId)
.map(clientId -> createUpdateCardStatusServiceRequestData(clientId, cardId, cardStatus))
.flatMap(requestData -> cartaClient.updateCardStatus(requestData)
.then(Mono.defer(() ->
Mono.zip(
Mono.just(isCardBeingCancelled(cardStatus)),
isLastCardForAccount(accountId),
Mono.just( isLostOrStolen(cardStatus) )
)
.map(tuple -> {
WRITE YOUR IF ELSE LOGIC
})
The idea is to use zip and then use the tuple for writing logic. The Tuple would be of type Tuple3 of <Boolean, Boolean ,Boolean>. I made the assumption that isLostOrStolen(cardStatus) returns Boolean.
One way of doing that is by using filterWhen operator like this:
.then(Mono.defer(() -> {
if (isCardBeingCancelled(cardStatus)) {
return Mono.just(accountId)
.filterWhen(this::allCardsCancelledForAccount)
.flatMap(this::removeAccount);
} else if (isLostOrStolen(cardStatus)) {
return replaceCard(cardId, cardStatus).flatMap(this::updateCardNumber);
}
return Mono.empty();
}))
You can use filterWhen in the case of asynchronous filtering. Check this section of Which operator do I need? reference and this How to filter Flux asynchronously.
As a side note, this is not going to work as you expect:
private Mono<Boolean> isLastCardForAccount(String accountId) {
return cardService.getAccountCards(accountId)
.hasElement();
}
public Mono<List<Card>> getAccountCards(final String accountId) {
return cardCrudRepository.getCardsByAccountId(accountId)
.collectList();
}
The collectList() will emit an empty List if there is no card. I'd use exists query instead:
public Mono<Boolean> isLastCardForAccount(final String accountId) {
return cardCrudRepository.existsByAccountId(accountId);
}

map from string to multiple different strings and add to list with Java stream

I'm new to Java 8 and Streams .
I got a PolicyDefinition object, that got to two method : getAlias,getName which both returns a string .
Is there an elegant way to create a list with all aliases and names of policy definitions using Stream (created from collection of PolicyDefinition) in one statement ?
with two statements its not a problem :
List<String> policyNames =
policyDefinitions.stream()
.map(definition -> definition.getName())
.collect(Collectors.toList());
List<String> policyAlias =
policyDefinitions.stream()
.map(definition -> definition.getAlias())
.collect(Collectors.toList());
But Is it possible in one ?
Thanks a lot for the help
flatMap it!
List<String> policyNames = policyDefinitions.stream()
.flatMap(definition -> Stream.of(definition.getName(), definition.getAlias()))
.collect(Collectors.toList());
As mentioned in the comments - for tidyness, create a method in Definition
public Stream<String> allNames() {
return Stream.of(getName(), getAlias())
}
Then
List<String> policyNames = policyDefinitions.stream()
.flatMap(Definition::allNames)
.collect(Collectors.toList());
OP comments "I forgot to mention that getAlias might be null, what do you do than[sic]"
In that case, use Optional:
public Stream<String> allNames() {
return Stream.concat(Stream.of(getName()), Optional.ofNullable(getAlias()).stream())
}
Also you can create a Map with Alias as a Key and Name as a Value using groupingBuy operator

How can I use Optional to return empty if there is any exception in the code in it?

Pretty new to Optional/Java8 usage and I had a doubt regarding its usage.
I have a function in my API which returns a String which can be null or empty. Its similar to finding the id in email like: abc#gmail.com -> abc is o/p.
Now one way of doing this was:
public Optional<String> getUser(final String emailId) {
if (TextUtils.isEmpty(emailId)) {
return Optional.empty();
}
String userIDSeparator = "#";
int userNameEndIndex = emailId.indexOf(userIDSeparator);
if (userNameEndIndex == -1) {
return Optional.empty();
}
return Optional.of(emailId.substring(0, userNameEndIndex));
}
I was wondering if there is any neater way of doing this to return an Optional?
Also, Optional was introduced in Java8 so is there anyway the code can be java7 compatible? I am aware of preprocessors in C not sure if something similar is available.
Note:
I have not compiled this code, so there might be some errors. I wanted the input on Optional.
Thanks!
Well, the code can certainly be reduced. i.e.
public Optional<String> getUser(final String emailId) {
return Optional.of(emailId)
.filter(email -> email.contains("#"))
.map(email -> email.substring(0, email.indexOf("#")));
}
if this method can ever receive a null value then you'd need to change Optional.of(emailId) to Optional.ofNullable(emailId).
As for:
Also, Optional was introduced in Java8 so is there any way the code can
be java7 compatible?
Not that I know of. There may be other libraries that have similar functionality to Java's Optional type, so a little bit of research online may get you to the right place.
Maybe you mean something like this :
public Optional<String> getUser(final String emailId) {
return Optional.ofNullable(emailId)
.filter(email -> email.contains("#"))
.map(email -> Optional.of(email.replaceAll("(.*?)#.*", "$1")))
.orElseGet(Optional::empty);
}
Example
null -> Optional.empty
"" -> Optional.empty
"abc#gmail.com" -> abd
As #Aominè mention, there are some unnecessary parts in my solution, you can use this version instead :
public Optional<String> getUser(final String emailId) {
return Optional.ofNullable(emailId)
.filter(email -> email.contains("#"))
.map(email -> email.replaceAll("(.*?)#.*", "$1"));
}

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.

Resources