Chris
Chris

Reputation: 1058

How do I use generics to call a method with a Class parameter?

I have a class that uses reflection to manipulate other classes:

package com.cw.cmt;

public class Container<T extends Class<?>> {
    private final Class<T> clazz;

    public Container(final Class<T> clazz) {
        this.clazz = clazz;
        System.out.println("Expensive constructor for " + this);
    }

    @Override
    public String toString() {
        return String.format("Container [clazz=%s]", clazz);
    }
}

Because these are potentially expensive to construct, I want to cache them in a Map so that they are only constructed as needed:

package com.cw.cmt;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class ContainerCache {
    private static final ConcurrentMap<Class<?>, Container<? extends Class<?>>> cache = new ConcurrentHashMap<>();

    public static <C extends Class<?>> Container<C> getContainer(final Class<C> clazz) {
        final Container<? extends Class<?>> result = cache.computeIfAbsent(clazz, k -> new Container<C>(clazz));
        // it would be nice to eliminate this cast!
        return (Container<C>) result;
    }
}

I'm having trouble working out the correct generic syntax for invoking this getContainer method.

// The parameterized method <Class<String>>getContainer(Class<Class<String>>) of type ContainerCache 
// is not applicable for the arguments (Class<String>)
ContainerCache.getContainer(String.class);

// Bound mismatch: The generic method getContainer(Class<C>) of type ContainerCache is not applicable 
// for the arguments (Class<String>). The inferred type String is not a valid substitute for the bounded 
// parameter <C extends Class<?>>
ContainerCache.<String>getContainer(String.class);

// The parameterized method <Class<String>>getContainer(Class<Class<String>>) of type ContainerCache 
// is not applicable for the arguments (Class<String>)
ContainerCache.<Class<String>>getContainer(String.class);

Upvotes: 1

Views: 237

Answers (2)

NiziL
NiziL

Reputation: 5140

I think there is a little mistake in your Container

public class Container<T extends Class<?>> {
  private final Class<T> clazz;
}

So, clazz is a Class<Class<?>> because T extends Class<?>.

A simple correction:

public class Container<T> {
  private final Class<T> clazz;
}

and

public class ContainerCache {
    private static final ConcurrentMap<Class<?>, Container<?>> cache = new ConcurrentHashMap<>();

    public static <T> Container<T> getContainer(final Class<T> clazz) {
        return (Container<T>) (cache.computeIfAbsent(clazz, k -> new Container<T>(clazz)));
    }
}

Actually, I don't have a nice solution to avoid the cast :)

Upvotes: 0

assylias
assylias

Reputation: 328568

I don't think you can eliminate the cast because your map contains several "types" of containers so you need to cast to the correct one. As pointed out your Container should be a Container<T> and a few more casts in your getContainer should do what you wanted:

public static class ContainerCache {
  private static final Map<Class<?>, Container<?>> cache = new ConcurrentHashMap<>();

  public static <T> Container<T> getContainer(final Class<T> clazz) {
    return (Container<T>) cache.computeIfAbsent(clazz, c -> new Container<> ((Class<T>) c));
  }
}

public static class Container<T> {
  private final Class<T> clazz;

  public Container(final Class<T> clazz) {
    this.clazz = clazz;
    System.out.println("Expensive constructor for " + this);
  }

  @Override
  public String toString() {
    return String.format("Container [clazz=%s]", clazz);
  }
}

It's a bit ugly but I don't think you can do much better...

Upvotes: 2

Related Questions