Java 8 - filtering a collection using an external parameter
I have a base class map (Animal) containing specific successors. I want to filter and get an array using java stream and every time I write the following complete long line of code:
(e.g. filter on concrete dog class)
MyBaseObjectAnimalMap.values().stream().
filter(x -> x instanceof Dog).
map(x -> (Dog) x).
toArray(Dog[]::new);
Is there a way to abstract this?
I want to implement a private method with the following signature:
filterMapByType(Map<String,Animal> map, Class<T extends Animal> type)
or something like that.
source to share
You can provide IntFunction
for a call toArray
to get an array of type T instead of an array of objects.
public static <T extends Animal> T[] filterMapByType(Map<String, Animal> map, Class<T> type, IntFunction<T[]> generator) {
return map.values()
.stream()
.filter(type::isInstance)
.map(type::cast)
.toArray(generator);
}
and for an example call:
Dog[] dogs = filterMapByType(map, Dog.class, Dog[]::new);
Dog[]::new
is equivalent length -> new Dog[length]
, that is, a function that takes an int parameter as and returns an array of type Dog
with size length
.
If returning a list is possible, you can use .collect(toList());
instead .toArray(generator);
.
source to share
Sure. The only thing you need to do differently is the class method # isAssignableFrom (class) , not instanceof
. Oh, and use Reflection to create that array, of course. Check Array.html # newInstance (class, int) for this.
So the end result will look something like this (although not tested):
filterMapByType(Map<String,Animal> map, Class<T extends Animal> type) {
return map.values().stream().
filter(type::isAssignableFrom).
map(type::cast).
toArray(Array.newInstance(type, map.size()));
}
source to share
Yes, you can of course preempt the predicate condition and at runtime based on the predicate condition it can decide which one to filter.
eg. I wrote a sample program to demonstrate this.
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class AnimalSelector {
public static void main(String args[]){
List<Animal> animalList = Arrays.asList(new Dog(),new Horse(),new Human(),new Dog(),new Horse(),new Human());
Predicate<Animal> dogFilter = x-> x instanceof Dog;
System.out.println(toAnimalArray(animalList,dogFilter));
Predicate<Animal> humanFilter = x-> x instanceof Human;
System.out.println(toAnimalArray(animalList,humanFilter));
Predicate<Animal> horseFilter = x-> x instanceof Dog;
System.out.println(toAnimalArray(animalList,horseFilter));
}
public static <Animal> List<Animal> toAnimalArray(List<Animal> animalList,Predicate<Animal> filterCondition){
return animalList.stream()
.filter(filterCondition)
.collect(Collectors.toList());
}
}
interface Animal{
public void eat();
}
class Dog implements Animal{
private String animalType;
public Dog( ) {
this.animalType = "dog";
}
@Override
public void eat() {
}
@Override
public String toString() {
return "Dog{" +
"animalType=" + animalType +
'}';
}
}
class Human implements Animal{
private String animalType;
public Human( ) {
this.animalType = "Human";
}
@Override
public void eat() {
}
@Override
public String toString() {
return "Human{" +
"animalType=" + animalType +
'}';
}
}
class Horse implements Animal{
private String animalType;
public Horse( ) {
this.animalType = "Horse";
}
@Override
public void eat() {
}
@Override
public String toString() {
return "Horse{" +
"animalType=" + animalType +
'}';
}
}
source to share
I recently wrote a small library called StreamEx , which, among other things, can easily solve your problem:
StreamEx.ofValues(MyBaseObjectAnimalMap).select(Dog.class).toArray(Dog[]::new)
Also note that it does not add a map step. Instead, it uses an unsafe post-filtering listing because it already knows that all flow elements are now dogs. This keeps the pipeline shorter.
source to share