Java 8 Optional / functional code optimization - optimization

There is a method that receives "someObj" and its purpose is to check the var ASet of type Set<>, iterate through it and replace its objects with the database object. For that I written the following code:
if(!CollectionUtils.isEmpty(someObj.getASet())){
someObj.setASet(
someObj.getASet()
.stream()
.map( c -> AService.getExistingA(c.getId()))
.collect(Collectors.toSet())
);
}
It does the purpose but I'm really not happy with it's readability.
Optional.ofNullable(someObj.getASet())
.ifPresent( ASet-> someObj.setASet(
ASet.stream()
.map( c -> AService.getExistingA(c.getId()))
.collect(Collectors.toSet())
));
Now it looks even less readable, can you recommend a better way? I think the worst problem is that someObj.setASet, it simply looks weird, is ther any functional way to replace that object after the collect?

Try using Optional.map:
Optional.ofNullable(someObj.getASet())
.map(set -> set.stream()
.map(c -> AService.getExistingA(c.getId()))
.collect(Collectors.toSet()))
.ifPresent(set -> someObj.setASet(set));
Now, the transform logic and the conditional imperative "set the result" are separate.
Or, the old standby works too -- no shame in using it:
Set set = someObj.getASet();
if (set != null) {
Set newSet = set.stream()
.map(c -> AService.getExistingA(c.getId()))
.collect(Collectors.toSet());
someObj.setASet(newSet);
}

Related

how to set multiple kotlin variables in one line

I want to fill two variables in the same line, but I don't know the best way to do it at kotlin
var a:String? = null
var b:String? = null
a, b = "Text"
Not possible in Kotlin (unless you are ready to resort to some contrived constructs with repetition as described in other answers and comments). You cannot even write
a = b = "Text"
because weirdly enough, assignments are not expressions in Kotlin (as opposed to almost everything else like if, return, throw, swicth, etc., which are expressions in Kotlin, but not in Java, for example).
So, if you want to assign exactly the same value without repetition (of the assigned value), you'll have to write
a = "Text"
b = a
Note, that there is also an also function (pun intended), so technically you can write the following if you really want to stay on one line
a = "Text".also { b = it }
but I doubt it is really worth it.
var a: String? = null; var b: String? = null
or
var (a: String?, b: String?) = null to null
But please don't ever do so
Simply create an inline array, iterate through and assign values.
arrayListOf(a, b, c, d).forEach { it = "Text" }

Why is kotlin stream evaluating an .all predicate to true where the first Iterable is empty

I have a List of objects.
importerResponse.applications is empty (size=0)
This is my code:
val isDeployed = importerResponse.applications
.flatMap(Application::instances)
.map(Instance::state)
.all { state -> DEPLOYED == state }
isDeployed is true in this case. How can this be? I want it to resolve into false if applications is empty.
Why would you want that? All the elements in the collection satisfy your predicate.
You can check the documentation:
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/all.html
If you want you can explicitly check for the collection being empty.
This should give you what you want:
val isDeployed = importerResponse.applications
.flatMap(Application::instances)
.map(Instance::state)
.count { state -> DEPLOYED == state } > 0```
The all method might be looking for any element that doesn't meet the condition, since you don't have any, it defaults to true.
You can achieve what you want by doing something similar to this:
val isDeployed = importerResponse.applications
.flatMap(Application::instances)
.map(Instance::state)
.let { it.size() > 0 && it.all { state -> DEPLOYED == state } }
Note that let allows you to reuse the same expression without recalculating it twice.
It can be a little bit confusing, why "any" returns "false" on empty collections, but "all" return true, because "all" seems to be more limiting than "any" (based on human language).
But if you ask as one example "if all persons in a room are male", than that's still true, if the room is empty. 0 out of 0 persons are ALL.

How to properly sanitize a list of items received from server using RX | filter{} map{}

I have the following code which I am trying to use for two purposes:
1) Call an API and get result as a POJO
2) Sanitize this object (POJO) before I display it in the UI
private fun getWinbackDataItems(rewardPurpose: String) /*Single<WinbackBaseItem>*/ {
val x = repository.getRewardsList(rewardPurpose)
.filter {
it.result?.rewards != null
}.map { winback ->
winback.result?.rewards?.asSequence()?.filter { rewardsItem ->
rewardsItem?.id != null && rewardsItem.title != null
}?.toList()?.take(3)?.map {
WinbackListItem(it?.id, it?.title!!, false)
}?.toList()
}
}
The point of contention for me is the line below:
itemListSanitized.add(WinbackListItem(it.id, it.title, false))
At this point I assume the filter has removed all nulls from the original list but to my amazement I find that I have to null check on it and all its content while adding them to the new list.
What do I miss here, pardon my naivety as I have just begun reactive
I take it that you are working not against executing code but against your IDE's warning messages or just the ability for this code to compile. What you're probably running up against is that earlier checks for null won't necessarily allow the compiler to assume non-null values later on, because in the meantime, other code in a different thread could have run and changed the values.
So when you create a WinbackListItem, you can safely assume that certain items are not null, and yet the compiler can't be sure of this, because it can't know what else is going on in your process space. So the compiler requires that you tell it not to worry about null values (!!) or that you check the values again. This is just the way Kotlin works. It's often a PITA, but it's just how it is.
I played with the posted code just to be sure I knew what I was talking about. Here is code that I was able to run:
private fun getWinbackDataItems(rewardPurpose: String) /*Single<WinbackBaseItem>*/ {
val x = repository.getRewardsList(rewardPurpose)
.filter {
it.result?.rewards != null
}.map { winback ->
winback.result?.rewards?.asSequence().filter { rewardsItem ->
rewardsItem.id != null && rewardsItem.title != null
}.toList().take(3).map {
println(it.id)
println(it.title)
WinbackListItem(it.id!!, it.title!!, false)
}.toList()
}.count()
}
I created some very simple classes and objects to satisfy this code and let it run. Note that I took out some unnecessary '?' null checks. I played with input values until I was convinced that it.id and it.title can never be null when the WinbackListItem constructor is called. And yet, the two !! on its parameters, or something else making sure they are not null, are required given this definition of WinbackListItem that won't accept null parameter values:
class WinbackListItem(val id: Int, val title: String, val huh: Boolean)

Kotlin nested for loops to asSequence

I'm trying to convert my nested for loop to asSequence in Kotlin. Here, my goal is to get and update the value of all my object array from another object array with the same key.
nested for loop:
val myFields = getMyFields()
val otherFields = getOtherFields()
for (myField in myFields) { // loop tru the my fields
for (otherField in otherFields) { // find the same fields
if (myField.key == otherField.key) { // if the same, update the value
val updatedMyField = myField.copy(value = otherValue.value)
myFields[myFields.indexOf(myField)] = updatedMyField // update my field value
break
}
}
}
What I've tried:
val updatedMyFields = getMyFields().asSequence()
.map { myField ->
getOtherFields().asSequence()
.map { otherField ->
if (myField.key == otherField.key) {
return#map otherField.value
} else {
return#map ""
}
}
.filter { it?.isNotEmpty() == true }
.first()?.map { myField.copy(value = it.toString()) }
}
.toList()
but this does not compile as it will return List<List<MyField>>.
I'm just looking for something much cleaner for this.
As comments suggest, this would probably be much more efficient with a Map.
(More precisely, a map solution would take time proportional to the sum of the list lengths, while the nested for loop takes time proportional to their product — which gets bigger much faster.)
Here's one way of doing that:
val otherFields = getOtherFields().associate{ it.key to it.value }
val myFields = getMyFields().map {
val otherValue = otherFields[it.key]
if (otherValue != null) it.copy(value = otherValue) else it
}
The first line creates a Map from the ‘other fields’ keys to their values.  The rest then uses it to create a new list from ‘my fields’, substituting the values from the ‘other fields’ where present.
I've had to make assumptions about the types &c, since the code in the question is incomplete, but this should do the same.  Obviously, you can change how it merges the values by amending the it.copy().
There are likely to be even simpler and more efficient ways, depending on the surrounding code.  If you expanded it into a Minimal, Complete, and Verifiable Example — in particular, one that illustrates how you already use a Map, as per your comment — we might be able to suggest something better.
Why do you want to use asSequence() ? You can go for something like that:
val myFields = getMyFields()
val otherFields = getOtherFields()
myFields.forEach{firstField ->
otherFields.forEach{secondField ->
if (firstField.key == secondField.key) {
myFields[myFields.indexOf(firstField)] = secondField.value
}
}
}
This will do the same job than your nested for loop and it's easier to read, to understand and so to maintain than your nested asSequence().

Comparing and removing object from ArrayLists using Java 8

My apologies if this is a simple basic info that I should be knowing. This is the first time I am trying to use Java 8 streams and other features.
I have two ArrayLists containing same type of objects. Let's say list1 and list2. Let's say the lists has Person objects with a property "employeeId".
The scenario is that I need to merge these lists. However, list2 may have some objects that are same as in list1. So I am trying to remove the objects from list2 that are same as in list1 and get a result list that then I can merge in list1.
I am trying to do this with Java 8 removeIf() and stream() features. Following is my code:
public List<PersonDto> removeDuplicates(List<PersonDto> list1, List<PersonDto> list2) {
List<PersonDto> filteredList = list2.removeIf(list2Obj -> {
list1.stream()
.anyMatch( list1Obj -> (list1Obj.getEmployeeId() == list2Obj.getEmployeeId()) );
} );
}
The above code is giving compile error as below:
The method removeIf(Predicate) in the type Collection is not applicable for the arguments (( list2Obj) -> {})
So I changed the list2Obj at the start of "removeIf()" to (<PersonDto> list2Obj) as below:
public List<PersonDto> removeDuplicates(List<PersonDto> list1, List<PersonDto> list2) {
List<PersonDto> filteredList = list2.removeIf((<PersonDto> list2Obj) -> {
list1.stream()
.anyMatch( list1Obj -> (list1Obj.getEmployeeId() == list2Obj.getEmployeeId()) );
} );
}
This gives me an error as below:
Syntax error on token "<", delete this token for the '<' in (<PersonDto> list2Obj) and Syntax error on token(s), misplaced construct(s) for the part from '-> {'
I am at loss on what I really need to do to make it work.
Would appreciate if somebody can please help me resolve this issue.
I've simplified your function just a little bit to make it more readable:
public static List<PersonDto> removeDuplicates(List<PersonDto> left, List<PersonDto> right) {
left.removeIf(p -> {
return right.stream().anyMatch(x -> (p.getEmployeeId() == x.getEmployeeId()));
});
return left;
}
Also notice that you are modifying the left parameter, you are not creating a new List.
You could also use: left.removeAll(right), but you need equals and hashcode for that and it seems you don't have them; or they are based on something else than employeeId.
Another option would be to collect those lists to a TreeSet and use removeAll:
TreeSet<PersonDto> leftTree = left.stream()
.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(PersonDto::getEmployeeId))));
TreeSet<PersonDto> rightTree = right.stream()
.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(PersonDto::getEmployeeId))));
leftTree.removeAll(rightTree);
I understand you are trying to merge both lists without duplicating the elements that belong to the intersection. There are many ways to do this. One is the way you've tried, i.e. remove elements from one list that belong to the other, then merge. And this, in turn, can be done in several ways.
One of these ways would be to keep the employee ids of one list in a HashSet and then use removeIf on the other list, with a predicate that checks whether each element has an employee id that is contained in the set. This is better than using anyMatch on the second list for each element of the first list, because HashSet.contains runs in O(1) amortized time. Here's a sketch of the solution:
// Determine larger and smaller lists
boolean list1Smaller = list1.size() < list2.size();
List<PersonDto> smallerList = list1Smaller ? list1 : list2;
List<PersonDto> largerList = list1Smaller ? list2 : list1;
// Create a Set with the employee ids of the larger list
// Assuming employee ids are long
Set<Long> largerSet = largerList.stream()
.map(PersonDto::getEmployeeId)
.collect(Collectors.toSet());
// Now remove elements from the smaller list
smallerList.removeIf(dto -> largerSet.contains(dto.getEmployeeId()));
The logic behind this is that HashSet.contains will take the same time for both a large and a small set, because it runs in O(1) amortized time. However, traversing a list and removing elements from it will be faster on smaller lists.
Then, you are ready to merge both lists:
largerList.addAll(smallerList);