Java lambda expression redundant call to external method -- how to rewrite? - java

I'm trying to get used to using lambda expressions, but I frequently get caught up against something basic like this:
public List<Location> findAllAccessByUser(User user) {
return listDao.getAccessList(user).stream()
.filter(list -> findBySubOrgId(list.getOwnerOrg().getId()).isPresent())
.map(list -> findBySubOrgId(list.getOwnerOrg().getId()).get())
.collect(Collectors.toList());
}
This method
Gets a list of AccessList objects,
reads the subOrgId property from each object
uses the findBySubOrgId() method to return an (Optional) Location object.
Collect the objects into a list of Location objects.
Since the findBySubOrgId() returns an Optional which may not be present, I figure I need to filter it so the return List doesn't contain any empty elements. But then I have the repeat call to the same method, which seems wasteful.
Ordinarily I'd assign it to a variable and reuse that, but I can't find a reference to how to do this with lambdas -- or if it's even necessary.
Alternately I could put a conditional in the map() expression instead, but again I'm not sure how to do this and be sure to remove nulls.
As written it works exactly as I expect, but I always try to optimize my code, and it never hurts to learn the right way to do things. What is the "correct" way to rewrite this? Or is it fine as is?

return listDao.getAccessList(user).stream()
.map(list -> findBySubOrgId(list.getOwnerOrg().getId()))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList())

Related

Java Optional - How to convert one type of list to another

I have an optional Optional<TypeA> from which I can do a map function and get Optional<List<TypeB>>.
But I would like to now map it to Optional<List<TypeC>> and then orElse() it. So putting in pseudo code
Optional.ofNullable(fooReturnsTypeA()).map(TypeA::barReturnsListTypeB()).{?}.orElse(new ArrayList<TypeC>());
I know how to use map function, but it's been a while dealing with Optional with map. So, I might be just staring at the answer. Could someone help me with {?} part?
Regards,
What about this?
Optional.ofNullable(fooReturnsTypeA())
.map(TypeA::barReturnsListTypeB())
.map(typeBList->typeBList
.stream()
.map(TypeB::barReturnsTypeC)
.collect(Collectors.toList())
)
.orElse(new ArrayList<TypeC>())
This basically creates a new Stream in .map and uses it to convert all elements of TypeB to TypeC (assuming TypeB has a method barReturnsTypeC without parameters that returns an instance of TypeC).
You can use another map and write method to convert List<TypeB> to List<TypeC>.
You're code will look like this:
Optional.ofNullable(fooReturnsTypeA())
.map(TypeA::barReturnsListTypeB())
.map(this::convert)
.orElse(new ArrayList<TypeC>());
And your method to convert:
private List<TypeC> convert(List<TypeB> list) {
// conversion logic
// return List<TypeC>
}
First, I would replace the orElse with orElseGet. Depending on your code, using orElseGet instead of orElse can improve your performance. The parameter of orElse is evaluated even when the Optional is non-empty. On the other hand, the parameter of orElseGet is evaluated only when the Optional is empty. In your specific case, you may be instantiating unnecessary ArrayLists. From the Javadoc:
Supplier method passed as an argument is only executed when an
Optional value is not present.
You can also dismiss the {?} by using a stream in the map.
Optional.ofNullable(fooReturnsTypeA())
.map(typeA -> typeA.barReturnsListTypeB().stream()
.map(TypeB::barReturnsTypeC).collect(Collectors.toList()))
.orElseGet(() -> new ArrayList<TypeC>());
Note: see this for more information about the difference between orElse and orElseGet.

Optional::isPresentOrElse -> List<> analog

Faced to some interesting issue: for example, I have to choose some solutions, depending on whether my List<> is blank or not, but I don't want to use simple if-statement for refactoring reasons (for example, there are three ugly inner if-s). If I had a nullable object, I could use the Optional::ifPresentOrElse solution, but what if the array is not null but just empty? are there any built-in language solutions? I understand that I could write something like ListOptional, create my own ListOptional::isNotEmptyOrElse there, but perhaps there is another solution?
Understand Optional as a wrapper against a possibly null instance providing chainable null-safe methods. Suggested ListOptional doesn't make to me any sense as null and empty lists semantically represent the same - a bunch of nothing.
If you want to use Optional, you must filter the single list in it whether is empty or not and provide an alternative:
List<String> nonEmpty = Optional.of(list)
.filter(l -> !l.isEmpty()) // is the list empty?
.orElseGet(() -> Arrays.asList("Hi", "Bye")); // if so, yield a default one
How is this better than using a ternary operator is questionable. I see no advantage over using this or if-else construct or a wrapper from a 3rd party library:
if (list.isEmpty() {
list = Arrays.asList("Hi", "Bye");
}
A built-in language solution for handling potentially empty collections is Stream API as declarative calls of chained methods, that are not executed on Stream from an empty collection. You can understand Stream as a wrapper against a possibly empty collection with the goal of processing each element. The same goes to the Optional with a difference a single instance is handled (regardless of whether it is a collection or not).
List<String> list = Collections.emptyList(); // empty list
List<String> updated = list.stream()
.filter(str -> str.startsWith("A") // won't happen
.map(String::toLowerCase) // won't happen
.collect(Collectors.toList()); // yields empty list

Assertions in Streams

I know I can filter a stream to get only those elements that are not null, and then do with them whatever I need. Something like this:
myList.stream().filter(element -> element != null).doOtherThings...
Is there a way to assert that the elements are not null in a stream function, so that it goes through all elements and if it finds one being null, it throws an exception? I've been thinking of something like the following:
myList.stream().assert(Objects::nonNull).doOtherThings...
Use something like
.map(Objects::requireNonNull)
You just need to apply an operation which will throw an exception, but otherwise passes the argument through.
Although, unless there is a good reason not to iterate the list multiple times, it might be clearer to separate the checking and the processing:
if (myList.stream().anyMatch(Objects::isNull)) {
throw ...
}
// Do things with list.
There are some very good suggestions already. Allow me to supplement. If what you are after is an assertion as in an assert statement, I would like to make this explicit in the code in order to guide the reader about my purpose. To assert that your original list doesn’t contain any nulls:
assert ! myList.contains(null);
If the assertion is to be checked somewhere down the stream pipeline, the simple way is:
assert myList.stream().map(this::transform).allMatch(Objects::nonNull);
If you don’t want to create a separate stream for the assertion but prefer to assert in the middle of your existing stream pipeline, use for example:
myList.stream()
.peek(e -> { assert e != null; })
.toArray();
You may worry that the use of peek is not so nice, which is also why I mention this option last. peek is documented to exist “mainly to support debugging” (quote taken out of its context), so you may say that it’s related to the purpose of assert and thus defend its use in this case.
To the map method I prefer to use peek method. I think it is more expressive then uing map method that has to return some value.
list.stream()
.peek(Objects::requireNonNull)
However what i think is hard to understand is that stream is not executed until the collect() is called. That is why doing this kind of conditional logic is hard using streams. To be honest if you want to achieve the desired effect you have to do something like this.
list.stream()
.peek(Objects::requireNonNull)
.collect(Collectors.toList())
.stream()
.map(t -> do whatever ... )

java 8 change list to map using instance of list

I'm try to convert a list to a map using the Collectors.toMap call. The list consists of ActivityReconcile objects. I want to pass an instance for every entry in the list into the toMap call.
The code is below and where I need the instances is denoted by ??.
final List<ActivityReconcile> activePostedList = loader.loadActivePosted(accessToken);
Map<AccountTransactionKey, ActivityReconcile> postedActiveMap =
activePostedList.stream().collect(
Collectors.toMap(
AccountTransactionKey.createNewAccountTransactionKeyFromActivityReconcileRecord(??),??));
If I understood you correctly, you will need something like
Map<AccountTransactionKey, ActivityReconcile> result = choices
.stream()
.collect(Collectors.toMap(
AccountTransactionKey::generate,
Function.identity()));
And the method (in AccountTransactionKey class) will look like
public static AccountTransactionKey generate(ActivityReconcile reconcile) {...}
I've replaced createNewAccountTransactionKeyFromActivityReconcileRec by generate for making the answer more readable and understandable.
To "fix" your code with the least changes, add a lambda parameter:
activePostedList.stream().collect(Collectors.toMap(
ar -> AccountTransactionKey.createNewAccountTransactionKeyFromActivityReconcileRecord(ar)),
o -> o));
or use a method reference:
activePostedList.stream().collect(Collectors.toMap(
AccountTransactionKey::createNewAccountTransactionKeyFromActivityReconcileRecord, o -> o));
btw, I can't recall ever seeing a method name as long as createNewAccountTransactionKeyFromActivityReconcileRecord - for readability, consider reducing it to just create(), since the return type and parameter type are enough to distinguish it from other factory methods you may have.

Return from lambda forEach() in java

I am trying to change some for-each loops to lambda forEach()-methods to discover the possibilities of lambda expressions. The following seems to be possible:
ArrayList<Player> playersOfTeam = new ArrayList<Player>();
for (Player player : players) {
if (player.getTeam().equals(teamName)) {
playersOfTeam.add(player);
}
}
With lambda forEach()
players.forEach(player->{if (player.getTeam().equals(teamName)) {playersOfTeam.add(player);}});
But the next one doesn't work:
for (Player player : players) {
if (player.getName().contains(name)) {
return player;
}
}
with lambda
players.forEach(player->{if (player.getName().contains(name)) {return player;}});
Is there something wrong in the syntax of the last line or is it impossible to return from forEach() method?
The return there is returning from the lambda expression rather than from the containing method. Instead of forEach you need to filter the stream:
players.stream().filter(player -> player.getName().contains(name))
.findFirst().orElse(null);
Here filter restricts the stream to those items that match the predicate, and findFirst then returns an Optional with the first matching entry.
This looks less efficient than the for-loop approach, but in fact findFirst() can short-circuit - it doesn't generate the entire filtered stream and then extract one element from it, rather it filters only as many elements as it needs to in order to find the first matching one. You could also use findAny() instead of findFirst() if you don't necessarily care about getting the first matching player from the (ordered) stream but simply any matching item. This allows for better efficiency when there's parallelism involved.
I suggest you to first try to understand Java 8 in the whole picture, most importantly in your case it will be streams, lambdas and method references.
You should never convert existing code to Java 8 code on a line-by-line basis, you should extract features and convert those.
What I identified in your first case is the following:
You want to add elements of an input structure to an output list if they match some predicate.
Let's see how we do that, we can do it with the following:
List<Player> playersOfTeam = players.stream()
.filter(player -> player.getTeam().equals(teamName))
.collect(Collectors.toList());
What you do here is:
Turn your input structure into a stream (I am assuming here that it is of type Collection<Player>, now you have a Stream<Player>.
Filter out all unwanted elements with a Predicate<Player>, mapping every player to the boolean true if it is wished to be kept.
Collect the resulting elements in a list, via a Collector, here we can use one of the standard library collectors, which is Collectors.toList().
This also incorporates two other points:
Code against interfaces, so code against List<E> over ArrayList<E>.
Use diamond inference for the type parameter in new ArrayList<>(), you are using Java 8 after all.
Now onto your second point:
You again want to convert something of legacy Java to Java 8 without looking at the bigger picture. This part has already been answered by #IanRoberts, though I think that you need to do players.stream().filter(...)... over what he suggested.
If you want to return a boolean value, then you can use something like this (much faster than filter):
players.stream().anyMatch(player -> player.getName().contains(name));
This what helped me:
List<RepositoryFile> fileList = response.getRepositoryFileList();
RepositoryFile file1 = fileList.stream().filter(f -> f.getName().contains("my-file.txt")).findFirst().orElse(null);
Taken from Java 8 Finding Specific Element in List with Lambda
You can also throw an exception:
Note:
For the sake of readability each step of stream should be listed in new line.
players.stream()
.filter(player -> player.getName().contains(name))
.findFirst()
.orElseThrow(MyCustomRuntimeException::new);
if your logic is loosely "exception driven" such as there is one place in your code that catches all exceptions and decides what to do next. Only use exception driven development when you can avoid littering your code base with multiples try-catch and throwing these exceptions are for very special cases that you expect them and can be handled properly.)

Categories