Werner de Groot
Werner de Groot

Reputation: 977

Filtering List of generic object for single generic instance

I have a List of generic objects of type MyClass<T> of which I do not know the exact type: List<MyClass<? extends Object>>.

Is there a way to filter this list to obtain only those objects that have a specific T without casting or using instanceof?.

I have the feeling this can be done with the Visitor pattern and subclassing per specific instance of T (like MyClassString extends MyClass<String>, which is not a problem), but I do not want to implement a Visitor for every subclass:

class MyClassString extends MyClass<String> {

    public <U> U accept(MyClassVisitor<U> visitor) {
        return visitor.visitMyClassString(this);
    }

}

interface MyClassVisitor<U> {

    U visitMyClassString(MyClassString myClassString);
    U visitMyClassDouble(MyClassDouble myClassDouble);
    ...

}

class FilterMyClassStringService implements MyClassVisitor<List<MyClassString>> {

    public List<MyClassString> filterMyClassString(List<MyClass<? extends Object>> toFilter) {
        List<MyClassString> filteredList = new ArrayList<>();
        for (MyClass<? extends Object> elem : toFilter) {
            filteredList.addAll(elem.accept(this));
        }
        return filteredList;
    }

    @Override
    public List<MyClassString> visitMyClassString(MyClassString myClassString) {
        List<MyClassString> listWithMyClassString = new ArrayList<>();
        listWithMyClassString.add(myClassString);
        return listWithMyClassString;
    }

    @Override
    public List<MyClassString> visitMyClassDouble(MyClassDouble myClassDouble) {
        return new ArrayList<MyClassString>();
    }

}

Is there any way?

Upvotes: 2

Views: 1714

Answers (1)

rsutormin
rsutormin

Reputation: 1649

I think I understand what you would like to have. And it's possible in some way. If we apply the discussion from this thread (How to get the generic type at runtime?) to your case then we'll have something like this code:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

public class TypedReflectTest {
    public static void main(String[] args) {
        List<MyClass<?>> list = new ArrayList<MyClass<?>>();
        list.add(new MyClassInteger(123));
        list.add(new MyClassDouble(123.45));
        list.add(new MyClassString("1234"));
        System.out.println(filterMyClass(list, Integer.class));
    }

    public static List<MyClass<?>> filterMyClass(List<MyClass<?>> toFilter, Class<?> innerType) {
        List<MyClass<?>> filteredList = new ArrayList<MyClass<?>>();
        for (MyClass<?> elem : toFilter) {
            if (!elem.getInnerType().equals(innerType))
                continue;
            filteredList.add(elem);
        }
        return filteredList;
    }
}

abstract class MyClass<T> {
    private T value;
    private Class<?> innerType;

    public MyClass(T value) {
        this.value = value;
        Type superClass = getClass().getGenericSuperclass();
        innerType = (Class<?>)(((ParameterizedType) superClass).getActualTypeArguments()[0]);
    }

    public T getValue() {
        return value;
    }

    public Class<?> getInnerType() {
        return innerType;
    }

    @Override
    public String toString() {
        return "MyClass(" + value + ")";
    }
}

class MyClassInteger extends MyClass<Integer> {
    public MyClassInteger(Integer value) {
        super(value);
    }
}

class MyClassDouble extends MyClass<Double> {
    public MyClassDouble(Double value) {
        super(value);
    }
}

class MyClassString extends MyClass<String> {
    public MyClassString(String value) {
        super(value);
    }
}

You should notice that abstract super class is essential here. You can not deal with instances of MyClass type itself. Unfortunately you have to extend this class in order to get parameterized type in runtime.

Upvotes: 2

Related Questions