Java 8 list processing - add elements conditionally - java

I have the following piece of code:
List<Object> list = new ArrayList<>();
list.addAll(method1());
if(list.isEmpty()) { list.addAll(method2()); }
if(list.isEmpty()) { list.addAll(method3()); }
if(list.isEmpty()) { list.addAll(method4()); }
if(list.isEmpty()) { list.addAll(method5()); }
if(list.isEmpty()) { list.addAll(method6()); }
return list;
Is there a nice way to add elements conditionally, maybe using stream operations? I would like to add elements from method2 only if the list is empty otherwise return and so on.
Edit: It's worth to mention that the methods contain heavy logic so need to be prevented from execution.

You could try to check the return value of addAll. It will return true whenever the list has been modified, so try this:
List<Object> list = new ArrayList<>();
// ret unused, otherwise it doesn't compile
boolean ret = list.addAll(method1())
|| list.addAll(method2())
|| list.addAll(method3())
|| list.addAll(method4())
|| list.addAll(method5())
|| list.addAll(method6());
return list;
Because of lazy evaluation, the first addAll operation that added at least one element will prevent the rest from bein called. I like the fact that "||" expresses the intent quite well.

I would simply use a stream of suppliers and filter on List.isEmpty:
Stream.<Supplier<List<Object>>>of(() -> method1(),
() -> method2(),
() -> method3(),
() -> method4(),
() -> method5(),
() -> method6())
.map(Supplier<List<Object>>::get)
.filter(l -> !l.isEmpty())
.findFirst()
.ifPresent(list::addAll);
return list;
findFirst() will prevent unnecessary calls to methodN() when the first non-empty list is returned by one of the methods.
EDIT:
As remarked in comments below, if your list object is not initialized with anything else, then it makes sense to just return the result of the stream directly:
return Stream.<Supplier<List<Object>>>of(() -> method1(),
() -> method2(),
() -> method3(),
() -> method4(),
() -> method5(),
() -> method6())
.map(Supplier<List<Object>>::get)
.filter(l -> !l.isEmpty())
.findFirst()
.orElseGet(ArrayList::new);

A way of doing it without repeating yourself is to extract a method doing it for you:
private void addIfEmpty(List<Object> targetList, Supplier<Collection<?>> supplier) {
if (targetList.isEmpty()) {
targetList.addAll(supplier.get());
}
}
And then
List<Object> list = new ArrayList<>();
addIfEmpty(list, this::method1);
addIfEmpty(list, this::method2);
addIfEmpty(list, this::method3);
addIfEmpty(list, this::method4);
addIfEmpty(list, this::method5);
addIfEmpty(list, this::method6);
return list;
Or even use a for loop:
List<Supplier<Collection<?>>> suppliers = Arrays.asList(this::method1, this::method2, ...);
List<Object> list = new ArrayList<>();
suppliers.forEach(supplier -> this.addIfEmpty(list, supplier));
Now DRY is not the most important aspect. If you think your original code is easier to read and understand, then keep it like that.

You could make your code nicer by creating the method
public void addAllIfEmpty(List<Object> list, Supplier<List<Object>> method){
if(list.isEmpty()){
list.addAll(method.get());
}
}
Then you can use it like this (I assumed your methods are not static methods, if they are you need to reference them using ClassName::method1)
List<Object> list = new ArrayList<>();
list.addAll(method1());
addAllIfEmpty(list, this::method2);
addAllIfEmpty(list, this::method3);
addAllIfEmpty(list, this::method4);
addAllIfEmpty(list, this::method5);
addAllIfEmpty(list, this::method6);
return list;
If you really want to use a Stream, you could do this
Stream.<Supplier<List<Object>>>of(this::method1, this::method2, this::method3, this::method4, this::method5, this::method6)
.collect(ArrayList::new, this::addAllIfEmpty, ArrayList::addAll);
IMO it makes it more complicated, depending on how your methods are referenced, it might be better to use a loop

You could create a method as such:
public static List<Object> lazyVersion(Supplier<List<Object>>... suppliers){
return Arrays.stream(suppliers)
.map(Supplier::get)
.filter(s -> !s.isEmpty()) // or .filter(Predicate.not(List::isEmpty)) as of JDK11
.findFirst()
.orElseGet(Collections::emptyList);
}
and then call it as follows:
lazyVersion(() -> method1(),
() -> method2(),
() -> method3(),
() -> method4(),
() -> method5(),
() -> method6());
method name for illustration purposes only.

Related

Lambda Expression : use function result as next parameter in a map

I have a function in my mapper with several parameter: function(A,B,C)
All of them are coming from differents functions, all in the same service, but parameter B and C need the result of parameter A
service.getRequests()
.stream()
.map(r ->
mapper.function(
service.getAList(r), //return List<A> aList
service.getBList(aList), //use the previous aList as parameter
service.getCList(aList) //same
)
.sorted()
.collect(Collectors.toList());
Is there a way to do this?
Just do
.map(r -> {
List<A> aList = service.getAList(r);
return mapper.function(
aList,
service.getBList(aList),
service.getCList(aList);
})
It may be better to move this functionality into a separate method:
private MapperResult remap(Request request) {
List<A> resultA = service.getAList(request);
return mapper.function(
resultA,
service.getBList(resultA),
service.getCList(resultA)
);
}
And then use a reference to this method:
service.getRequests()
.stream()
.map(MyClass::remap)
.sorted()
.collect(Collectors.toList());

JAVA8 Optional and Lambdas

Suppose I have this class model hierarchy:
public class A {
private Integer id;
private List<B> b;
}
And:
public class B {
private Integer id;
private List<C> c;
}
And finally:
public class C {
private Integer id;
}
And a simple Service:
#Service
public class doSome {
public void test() {
Optional<A> a = Optional.of(a) // Suppose a is an instance with full hierarchy contains values
/** *1 **/ // what I want to do
}
}
Now what I want to do at the *1 position is to use lambda to extract the Optional value (if exixsts) and map the subrelation to obtain all id of the C class. I have tried something like this:
public void test() {
Optional<A> a = Optional.of(a);
List<Integer> temp = a.get().getB()
.stream()
.map(x -> x.getC())
.flatMap(List::stream)
.map(y -> y.getId())
.collect(Collectors.toList()); // works
}
Now I would like to put inside my lambda the a.get().getB(), I have tried several ways but with no luck.
Anyway I don't understand why I can't use two consecutive map like
.map(x -> x.getC())
.flatMap(List::stream)
.map(y -> y.getId())
without using flatMap(List::stream) in the middle... the map doesn't return a new Stream of Type R (class C in this case)? Why I have to Stream it again? where am I wrong?
----------------------- UPDATE ------------------
This is just an example, It's pretty clear that the Optional here is useless but in real case could comes by a findById() JPA Query.
Holger for this reasons I would put all inside a part of code, doing something like:
public <T> T findSome(Integer id) {
Optional<T> opt = repository.findById(id);
return opt.map(opt -> opt).orElse(null);
}
I have read here some solution like follows:
Optional.ofNullable(MyObject.getPeople())
.map(people -> people
.stream()
.filter(person -> person.getName().equals("test1"))
.findFirst()
.map(person -> person.getId()))
.orElse(null);
And I would like to adapt at my case but without no luck.
As of java-9 and newer, you can call Optional#stream:
List<Integer> temp = a.map(A::getB)
.stream()
.flatMap(Collection::stream)
.map(B::getC)
.flatMap(Collection::stream)
.map(C::getId)
.collect(Collectors.toList());
If you are stuck with java-8, you need to map to Stream (or return the empty one) and continue chaining:
List<Integer> temp = a.map(A::getB)
.map(Collection::stream)
.orElse(Stream.empty())
.map(B::getC)
.flatMap(Collection::stream)
.map(C::getId)
.collect(Collectors.toList());
Note: Optional<A> a = Optional.of(a) is not valid as a is already defined.

Java stream get single element from collection

I am using a non stream way to get single element from collection.
List<MyCustomClass> list = OtherObject.getMyList();
if (list.size() != 1) {
throw new RuntimeException();
}
MyCustomClass customClass = list.get(0);
Instead of this multi liner approach, is there some way to achieve this via streams?
You can use reduce(accumulator) and orElseThrow(exceptionSupplier) to ensure the stream produces exactly one result.
MyCustomClass customClass = list.stream()
.reduce((a,b) -> { throw new RuntimeException("Too many values present"); })
.orElseThrow(() -> { throw new RuntimeException("No value present"); });
I was looking for a version with a single collect statement, although it turned out not as concise or elegant as the solution by Andreas. It uses an implementation of Collector that accumulates to a one-element list, while the combiner raises an exception if we have more than one element; the finisher raises an exception when the list is empty.
list.stream().collect(
Collector.of( ArrayList::new,
(a, t) -> { if (!a.isEmpty())
throw new RuntimeException();
a.add(t); },
(a, b) -> { throw new RuntimeException(); },
a -> { if( a.isEmpty() )
throw new RuntimeException();
return a.get(0);} );
You could try returning an optional from findFirst() or findAny().
List<String> strings = new ArrayList<>();
Optional<String> maybeFirst = strings.stream().findFirst();
// we now have an optional, lets force a value
String value = maybeFirst.orElseThrow(IllegalArgumentException::new);
// if there isn't a value, we'll throw an illegal argument exception.
This can collapsed into the following.
String value = strings.stream()
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("There must be at least one string."));
Hope that helps.

Use same variable in filter() and map() of a Java 8 stream

To improve performance I want to use the same variable in both filter() and map() of a Java 8 stream.
Example-
list.stream()
.filter(var -> getAnotherObject(var).isPresent())
.map(var -> getAnotherObject(var).get())
.collect(Collectors.toList())
The called method getAnotherObject() looks like-
private Optional<String> getAnotherObject(String var)
In the above scenario I have to call the method getAnotherObject() twice.If I go with a regular for loop then I have to call the method getAnotherObject() only once.
List<String> resultList = new ArrayList<>();
for(String var : list) {
Optional<String> optionalAnotherObject = getAnotherObject(var);
if(optionalAnotherObject.isPresent()) {
String anotherObject = optionalAnotherObject.get();
resultList.add(anotherObject)
}
}
Even with stream I can put all my code in map()-
list.stream()
.map(var -> {
Optional<String> anotherObjectOptional = getAnotherObject(var);
if(anotherObjectOptional.isPresent()) {
return anotherObjectOptional.get();
}
return null;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
But I believe there must be an elegant way using filter().
You can create a stream like this
list.stream()
.map(YourClass::getAnotherObject)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
YourClass refer to the name of the class where getAnotherObject method is defined
You can use flatMap. Usually this is used to flatten stuff, but here you can
map the element to that element if the optional has a value
map the element to an empty stream if the optional has no value
Like this:
stream
.map(x -> getAnotherObject(x))
.flatMap(x -> x.map(Stream::of).orElse(Stream.of())))

Java 8: Stream a list and map to another list based on different filters

I have the following
code:
public boolean foo(List<JSONObject> source, String bar, String baz) {
List<String> myList = newArrayList();
source.forEach(json -> {
if (!(json.get(bar) instanceof JSONObject)) {
myList.add(json.get(bar).toString());
} else {
myList.add(json.getJSONObject(attribute).get("key").toString());
}
});
/**
* do something with myList and baz
*/
}
I'm just wondering if there's a way to do the if-else condition inline using a filter.
Something along the lines of:
List<String> myList = source.stream()
.filter(json -> !(json.get(bar) instanceof JSONObject))
.map(item -> item.get(attribute).toString())
.collect(Collectors.toList());
If I go by the approach above, I will miss the supposed to be "else" condition. How can I achieve what I want using a more java-8 way?
Thanks in advance!
The only way I see is putting the condition in the map call. If you use filter you lose the "else" part.
List<String> myList = source.stream()
.map(item -> {
if (!(item.get(bar) instanceof JSONObject)) {
return item.get(bar).toString();
} else {
return item.getJSONObject(attribute).get("key").toString();
}
})
.collect(Collectors.toList());
or, as Holger suggested in a comment, use the ternary conditional operator:
List<String> myList = source.stream()
.map(i -> (i.get(bar) instanceof JSONObject ? i.getJSONObject(attribute).get("key") : i.get(bar)).toString())
.collect(Collectors.toList());
What about something like this?
List<String> myList = source.stream()
.map(json -> !(json.get(bar) instanceof JSONObject) ?
json.get(bar).toString() :
json.getJSONObject(attribute).get("key").toString())
.collect(Collectors.toList());
Not tested but you get the idea.
You can use the function partitioningBy that takes a Predicate and returns Map<Boolean, List<T>>. The true key contains the values that are true for the predicate and the false key contains the other values.
So you can rewrite your code like this :
Map<Boolean, List<String>> partition = source.stream()
.collect(Collectors.partitionBy(json -> !(json.get(bar) instanceof JSONObject));
In this case, no need to use the filter function.
List<String> valuesWhenTrue = partition.get(Boolean.TRUE).stream().map(item -> item.get(attribute).toString()).collect(Collectors.toList());
List<String> valuesWhenFalse = partition.get(Boolean.FALSE).stream().map(json.getJSONObject(attribute).get("key").toString()).collect(Collectors.toList());
How about extract the if-else into a private function
private String obtainAttribute(JSONObject json){
if (!(json.get(bar) instanceof JSONObject)) {
return json.get(bar).toString();
}
return json.getJSONObject(attribute).get("key").toString();
}
and call it in your lambda expression.
List<String> myList = source.stream()
.map(item -> obtainAttribute(item))
.collect(Collectors.toList());

Categories

Resources