Java stream with method references for instanceof and class cast - java

Can I convert the following code using method reference?
List<Text> childrenToRemove = new ArrayList<>();
group.getChildren().stream()
.filter(c -> c instanceof Text)
.forEach(c -> childrenToRemove.add((Text)c));
Let me give an example to illustrate what I mean, suppose we have
myList
.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(elem -> System.out.println(elem));
using method reference it can be written as (last line)
myList
.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
What are the rules to convert an expression to a method reference?

Yes, you can use these method references:
.filter(Text.class::isInstance)
.map(Text.class::cast)
.forEach(childrenToRemove::add);
Instead of for-each-add, you can collect stream items with Collectors.toSet():
Set<Text> childrenToRemove = group.getChildren()
// ...
.collect(Collectors.toSet());
Use toList() if you need to maintain the order of children.
You can replace lambda expressions with method references if the signatures match by applying these rules:
ContainingClass::staticMethodName // method reference to a static method
containingObject::instanceMethodName // method reference to an instance method
ContainingType::methodName // method reference to an instance method
ClassName::new // method reference to a constructor

I think yes it's possible, like so
group.getChildren()
.filter(Text.class::isInstance)
.map(Text.class::cast)
.collect(Collectors.toCollection(() -> childrenToRemove));

Related

Java8 Streams: How to preserve the value before map to be accessed by collect/groupingBy functions

I'm using Java8 Streams to iterate through a list and for each of the element I invoke map and then I need to aggregate the results. My problem is that when I call groupingBy I also need to access the original object before calling map. Here is a snippet:
list.stream() //
.filter(item -> item.getType() == HUMAN) //
.map(item -> manager.itemToHuman(item.getId())) //
.filter(Objects::nonNull) //
.collect(Collectors.groupingBy(Human::getAge, Collectors.summarizingLong(item.getCount())));
The problem is with the call to Collectors.summarizingLong(item.getCount()) since item at this point is NOT accessible. Is there an elegant way to overcome this?
After doing map() stream transformed into Stream<Human> so you can't use item object in the collector.
You can transform item into a pair of Human object and count using SimpleEntry then use it on the collector.
list.stream()
.filter(item -> item.getType() == HUMAN)
.map(item ->
new AbstractMap.SimpleEntry<>(manager.itemToHuman(item.getId()), item.getCount()))
.filter(entry -> Objects.nonNull(entry.getKey()))
.collect(Collectors.groupingBy(entry -> entry.getKey().getAge(),
Collectors.summarizingLong(Map.Entry::getValue)));

Java8 stream cannot resolve a variable

I am new in Java8 and I want to refactor this piece of code and convert it in a more Java8 style,
for (RestaurantAddressee RestaurantAddressee : consultationRestaurant.getAddressees()) {
Chain chain = chainRestService.getClient().getChainDetails(getTDKUser(), RestaurantAddressee.getChain().getId());
if (chain.getOrganisation().getId().equalsIgnoreCase(event.getOrganisationId())) {
chainIds.add(restaurantAddressee.getChain().getId());
}
}
so I change it for this code:
consultationRestaurant.getAddressees()
.stream()
.map( ma -> chainRestService.getClient().getChainDetails(getTDKUser(), ma.getChain().getId()))
.filter(chain -> chain.getOrganisation().getId().equalsIgnoreCase(event.getOrganisationId()))
.forEach(chainIds.add(chain.getId()));
But I have this compilation error:
chain cannot be resolved
You forgot to specify the lambda expression parameter in your forEach call.
That said, you shouldn't use forEach to add elements to a collection. Use collect:
List<String> chainIds =
consultationRestaurant.getAddressees()
.stream()
.map( ma -> chainRestService.getClient().getChainDetails(getTDKUser(), ma.getChain().getId()))
.filter(chain -> chain.getOrganisation().getId().equalsIgnoreCase(event.getOrganisationId()))
.map(Chain::getId)
.collect(Collectors.toList());
Here. Your loop defines:
Chain chain = chainRestService.getClient()...
But your stream statement simply misses to define that variable.
So: in places that need that variable, you have to provide, for example as parameter:
filter(chain -> chain.getOrganisation().getId().equalsIgnoreCase(event.getOrganisationId()))

Enums Returning Arrays Stream

I have a requirement to validate a field against some predefined values (that can grow in future). So for this I have created a Enum and defined a method that returns the stream of the allowed values.
public enum EnumDemo {
VERSION("1.0.0","2.0.3");
private List<String> ver;
EnumDemo(String... ver) {
this.ver = Arrays.asList(ver);
}
public List<String> getVer() {
return ver;
}
public static Stream<EnumDemo> stream() {
return Arrays.stream(EnumDemo.values());
}
}
Now I need to validate a field against the values defined in this Enum.
I'm using:
Optional<EnumDemo> ab = EnumDemo.stream()
.map(l -> {l.getVer().stream()
.filter(c -> c.equals("2.0.3"))
.findFirst();})
.findFirst();
System.out.println(ab.get().getVer());
But it is giving me compilation error. Any help would be appreciated.
Edit:
Compilation Error:
The method map(Function<? super EnumDemo,? extends R>) in the type Stream<EnumDemo> is not applicable for the arguments ((<no type> l) -> {})
You should write it this way:
Optional<EnumDemo> ab = EnumDemo.stream().filter(l -> l.getVer().contains("2.0.3"))
.findFirst();
By the way, it wasn't working because you used {} for the lambda expression, so it was expecting a return statement in the {}. You could either remove the {} (along with the ;) or add in the return.
Anyway the original codes looked confusing, not sure if I guessed the intention correctly, but this implementation should be clearer.
Edit
Based on your comment, this is what you need:
EnumDemo.stream().flatMap(l -> l.getVer().stream())
.filter("2.0.3"::equals)
.findAny()
.ifPresent(System.out::println);
Update
Holger commented that there is a shorter and more meaningful way, with better performance:
if(EnumDemo.stream()
.anyMatch(l -> l.getVer().contains(userString))) {
System.out.println(userString);
}
To understand it, you have to think about lambdas. Lambdas represent interfaces but are specially treated by the JVM, so not every Lambda needs a class to represent. (Stateless lambdas can be just methods).
Now when looking at the map() method in the Stream interface:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
You see that it expects an implementation of the Function interface. You now have many different ways to provide that mapper. In this example lets map from Object to String:
1. Using an inline lambda:
.map(o -> o.toString())
2. Using a multiline lambda:
.map(o -> {
return o.toString();
})
3. Using method references:
.map(Object::toString)
4. Using an anonymous class:
.map(new Function<Object, String>(){
#Override
public String apply(Object o){
return o.toString();
}
})
Your current code uses the 2. approach. But without a return statement. This is even better seen when looking at the anonymous class at 4.. It seems natural, that when not using a return statement in a method that no value is returned.
And that's why you get the compilation error.
You just have to add the return statement:
.map(l -> {
return l.getVer().stream()
.filter(c -> c.equals("2.0.3"))
.findFirst();
});
Or remove the brackets {}:
.map(l -> l.getVer().stream()
.filter(c -> c.equals("2.0.3"))
.findFirst());
Or even use the approach provided by #Jai in his answer. Which works even better, than what you currently have.
You are using lambda expression and not returning any value so it is giving compilation error. It is better to use ifPresent()
String val="2.0.3";
EnumDemo.stream()
.flatMap(l -> l.getVer().stream())
.filter(c -> c.equals(val))
.findAny()
.ifPresent(x -> System.out.println(x));

Better way to create a stream of functions?

I wish to do lazy evaluation on a list of functions I've defined as follows;
Optional<Output> output = Stream.<Function<Input, Optional<Output>>> of(
classA::eval, classB::eval, classC::eval)
.map(f -> f.apply(input))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
where as you see, each class (a, b & c) has an Optional<Output> eval(Input in) method defined. If I try to do
Stream.of(...)....
ignoring explicit type, it gives
T is not a functional interface
compilation error. Not accepting functional interface type for T generic type in .of(T... values)
Is there a snappier way of creating a stream of these functions? I hate to explicitly define of method with Function and its in-out types. Wouldn't it work in a more generic manner?
This issue stems from the topic of the following question;
Lambda Expression and generic method
You can break it into two lines:
Stream<Function<Input, Optional<Output>>> stream = Stream
.of(classA::eval, classB::eval, classC::eval);
Optional<Output> out = stream.map(f -> f.apply(input))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
or use casting:
Optional<Output> out = Stream.of(
(<Function<Input, Optional<Output>>>)classA::eval,
classB::eval,
classC::eval)
.map(f -> f.apply(input))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
but I don't think you can avoid specifying the type of the Stream element - Function<Input, Optional<Output>> - somewhere, since otherwise the compiler can't infer it from the method references.
There is a way that allows to omit the Function<Input, Optional<Output>> type, but it’s not necessarily an improvement
Optional<Output> o =
Stream.concat(Stream.of(input).map(classA::eval),
Stream.concat(Stream.of(input).map(classB::eval),
Stream.of(input).map(classC::eval)))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
and it doesn’t scale.
It seems, the best option is to wait for Java-9 where you can use
Optional<Output> o = classA.eval(input)
.or(() -> classB.eval(input))
.or(() -> classC.eval(input));

Generic chceck in the stream collect java

List<String> namesOfMaleMembersCollect = roster
.stream()
.filter(p -> p.getGender() == Person.Sex.MALE)
.map(p -> p.getName())
.collect(Collectors.toList());
I've got such a code, where roster is defined as List<Person>. In which place JVM checks if the returned List consist Strings? I mean we've got the List defined, but then there is no information about the String of retiring value. Is this:
.map(p -> p.getName())
.collect(Collectors.toList());
the place where JVM see that .map() getting String and know that the type of the list returned by .collect() will be same?
Type inference is a powerful tool that comes with generics. When you call .map(p -> p.getName()) it returns a Stream<String>, now the the Stream has type parameter String instead of T.
Now you call collect which takes a Collector of the following signature.
<R, A> R collect(Collector<? super T, A, R> collector)
And in the case Stream<String> it will be infered to
Collector<String, ?, List<String>>
Giving us List<String>
You can rewrite your code to the following
Collector<String, ?, List<String>> collector = Collectors.toList();
...map(p -> p.getName())
.collect(collector);
Meaning the type is infered from the type of the variable the result is being assigned to.
The map method return a stream of the result of applying the function in parameter so this return to you a stream of string and then we collect the result as a List using the collector.
In this cas its in this map function :
map(p -> p.getName())
it can be written like this (:
map(Person p -> {return p.getName();})

Categories