Olivier Grégoire
Olivier Grégoire

Reputation: 35467

I can't compile a Guava's Cache's builder because of Java generics

I have the following short, self-contained code that shows an error at compile time. I've desperately tried to have it compiling. I usually don't have trouble with generics anymore, but well I give up on this one and I ask the help of the team.

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

public class CacheWithGenericsDoesNotCompile {

  // Class definition can't be modified
  static class Resource<T extends Resource<T>> {}

  // Class definition can't be modified
  static class ResourceType<T extends Resource<T>> {
    public ResourceType(Class<T> type) {}
  }

  // Variable definition may be modified
  static LoadingCache<Class<? extends Resource<?>>, ResourceType<? extends Resource<?>>> cache = CacheBuilder.newBuilder().build(
      new CacheLoader<Class<? extends Resource<?>>, ResourceType<? extends Resource<?>>>() {
        @Override public ResourceType<? extends Resource<?>> load(Class<? extends Resource<?>> key) throws Exception {
          return new ResourceType<? extends Resource<?>>(key);
        }
    });

  // Method definition can't be modified, method content may.
  @SuppressWarnings("unchecked")
  static <T extends Resource<T>> ResourceType<T> getResourceType(Class<T> type) {
    return (ResourceType<T>)cache.getUnchecked(type);
  }
}

The line that fails to compile is:

return new ResourceType<? extends Resource<?>>(key);

I know why it fails: I may not write new Xxxx<...> with a question mark (?). I just can't write this line differently to have the other lines compiling.

I have a fallback solutions in the case where Resource has no generics, but in the limit of the possible I'd like to keep Resource with generics.

I have no restriction about the generics of the LoadingCache except that I need it to be called as in getResourceType(Class).

So... how can I fix this code?

Upvotes: 3

Views: 2197

Answers (3)

ZhongYu
ZhongYu

Reputation: 19682

If we can cast key to a proper type

Class<T> for some T that T<:Resource<T>

then we have no problem to write

ResourceType<? extends Resource<?>> result = new ResourceType<>(casted_key)

Usually this kind of casting can be done through wildcard capture, but it doesn't work if the type variable has a self referential bound. See the workaround at Generics and Class<? extends Enum<?>>, EnumSet.allOf(class) vs class.getEnumConstants() .

Upvotes: 0

Olivier Gr&#233;goire
Olivier Gr&#233;goire

Reputation: 35467

I played a bit more with gontard's solution (so if you upvote this, make sure to upvote gontard's answer too) and millimoose's comment. I came to this solution which removes the generics completely, for a far more readable code.

@SuppressWarnings("rawtypes")
static LoadingCache<Class, ResourceType> cache = CacheBuilder.newBuilder().build(
    new CacheLoader<Class, ResourceType>() {
      @SuppressWarnings("unchecked")
      @Override public ResourceType load(Class key) throws Exception {
        return new ResourceType(key);
      }
  });

Upvotes: 1

gontard
gontard

Reputation: 29520

A solution with some ignored warning :

  static LoadingCache<Class<? extends Resource<?>>, ResourceType<? extends Resource<?>>> cache = CacheBuilder.newBuilder().build(
      new CacheLoader<Class<? extends Resource<?>>, ResourceType<? extends Resource<?>>>() {
        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Override public ResourceType<? extends Resource<?>> load(Class<? extends Resource<?>> key) throws Exception {
          return new ResourceType(key);
        }
    });

As suggested by @John B in a comment. This code is not different at run-time than the code in the question due to type-erasure.

Upvotes: 5

Related Questions