ofchrq
ofchrq

Reputation: 15

Unchecked casting: Wildcard wrapped by Class to another type wrapped by Class

I'm trying to collect Set of classes by Annotation criteria with reflections (for performance benetifs):

Reflections reflections = new Reflections(packagePrefix);
Set<Class<Foo>> foos = reflections.getTypesAnnotatedWith(BooAnnotation.class);

The problem is that I'm getting Set<Class<?>> instead of Set<Class<Foo>>. I managed to check if the class is a type of Foo by filtering the collection with .filter(Foo.class::isAssignableFrom). At this point, I know that I'm working with classes that are of type Foo. Unfortunately when casting from Class<?> to Class<Foo> I am getting Unchecked cast and I want to avoid that.

Here is the full block of code:

Reflections reflections = new Reflections(packagePrefix);
Set<Class<Foo>> foos = reflections.getTypesAnnotatedWith(BooAnnotation.class)
        .stream()
        .filter(Foo.class::isAssignableFrom)
        .map(uncheckedFoo -> {
          @SuppressWarnings("unchecked")
          Class<Foo> foo = (Class<Foo>) uncheckedFoo;
          return foo;
        })
        .collect(Collectors.toSet());

Any suggestions on this?

Upvotes: 1

Views: 153

Answers (2)

newacct
newacct

Reputation: 122439

First, Class<Foo> is for the class object representing exactly the class Foo, and no other class. There can only be one object of type Class<Foo>. So having a Set of them makes no sense. It seems like you are collecting a Set of class objects representing Foo and its subclasses. In that case, it should be Set<Class<? extends Foo>>.

Second, you need to get uncheckedFoo of type Class<?> to Class<? extends Foo>. If you don't want to do with with an unchecked cast, you can do this in a type-safe way using Class::asSubclass(). So you can do something along the lines of:

Set<Class<? extends Foo>> foos = reflections.getTypesAnnotatedWith(BooAnnotation.class)
        .stream()
        .filter(Foo.class::isAssignableFrom)
        .map(uncheckedFoo -> uncheckedFoo.asSubclass(Foo.class))
        .collect(Collectors.toSet());

Upvotes: 1

user12026681
user12026681

Reputation:

You can use a good old loop instead of a stream to make clear to the compiler that each item is assignableFrom Foo when casting. Otherwise it forgets that all items are of type Foo in the next step.

Cast checking and casting have to must be in 1 step

Set<Class<Foo>> foos = new HashSet<>();

for(Class<?> i : reflections.getTypesAnnotatedWith(
                   BooAnnotation.class ) )
{
  if(Foo.class.isAssignableFrom(i))
  {
    foos.add(Foo.class.cast(i)); //cast from Foo.class
  }
}

Note: you can use forEach instead of the for(:) loop

Upvotes: 0

Related Questions