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.

+3


source to share


4 answers


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);

.

+8


source


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()));
}

      

+3


source


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 +
                '}';
    }
}

      

+2


source


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.

0


source