0

Given 2 Class objects how can I get the Class object of a Map? For example, assume I have:

Class keyClass = Long.class;
Class valueClass = String.class;

How can I get the Class object ofMap<Long,String>?

khelwood
  • 46,621
  • 12
  • 59
  • 83
Ran Harari
  • 21
  • 1
  • Given a map object, you call `myMap.getClass()`. The class of map doesn't contain information about class of `key` or class of `value`. – ernest_k Aug 02 '18 at 08:11
  • There's only `Map.class`. There is no class object for `Map`, because the generic type parameters are erased at runtime. – marstran Aug 02 '18 at 08:11
  • Also, in your exmaple, you are missing generic type parameters. It should be `Class keyClass = Long.class` – Glains Aug 02 '18 at 08:13
  • [Get generic type for java.util.Map parameter - Stack Overflow](https://stackoverflow.com/questions/6148798/get-generic-type-for-java-util-map-parameter) – user202729 Aug 02 '18 at 08:13
  • (however looking at OP's question, `Map.class` is probably the correct answer) – user202729 Aug 02 '18 at 08:14
  • Possibly related: [Java: how do I get a class literal from a generic type?](https://stackoverflow.com/q/2390662) – Pshemo Aug 02 '18 at 08:14
  • [java - Get generic type of class at runtime - Stack Overflow](https://stackoverflow.com/questions/3403909/get-generic-type-of-class-at-runtime) – user202729 Aug 02 '18 at 08:14
  • I know there's no Class object for Map BUT, for such an instance (of the Class object), asking getGenericInterfaces() will return an array with Type and Type, how do I get the Class object with such attributes for the Map interface? – Ran Harari Aug 02 '18 at 08:31
  • @RanHarari sadly in java reflection api still misses such options, but you can use some libraries or just implement that interfaces by yourself - as you can just implement ParameterizedType interface and do what you want – GotoFinal Aug 02 '18 at 08:58
  • Look at my answer maybe it will tell you what you want – GotoFinal Aug 02 '18 at 12:45

2 Answers2

0

There is no such class of Map<Long, String>. What you want is Map.class. (or HashMap.class, etc)

Map<String, Integer> map1 = new HashMap<>();
Map<Long, String> map2 = new HashMap<>();
System.out.println(map1.getClass().equals(map2.getClass()));

The result is true.

zhh
  • 1,901
  • 1
  • 7
  • 17
  • But map1.getClass().getGenericInterfaces() != map2.getClass().getGenericInterfaces() – Ran Harari Aug 02 '18 at 08:37
  • @Ran Because they are arrays, use ```Arrays.equals(map1.getClass().getGenericInterfaces(), map2.getClass().getGenericInterfaces())```. – zhh Aug 02 '18 at 08:39
0

Map<Long, String> is not a class, but it is a type, ParameterizedType to be exact, sadly java code for constructing them is private, but they are 2 ways to get it, more dynamic way is to implement that interface:

final class ParameterizedTypeImpl implements ParameterizedType {
    private final Type[] actualTypeArguments;
    private final Class  rawType;
    @Nullable private final Type   ownerType;

    ParameterizedTypeImpl(Class rawType, Type[] actualTypeArguments, @Nullable Type ownerType) {
        this.actualTypeArguments = actualTypeArguments.clone();
        this.rawType = rawType;
        if ((ownerType != null) || (rawType.getDeclaringClass() == null)) {
            this.ownerType = ownerType;
        }
        else {
            Class declaringClass = rawType.getDeclaringClass();
            if (Modifier.isStatic(rawType.getModifiers())) {
                this.ownerType = declaringClass;
            }
            else {
                TypeVariable[] typeParameters = declaringClass.getTypeParameters();
                if (typeParameters.length == 0) {
                    this.ownerType = declaringClass;
                }
                else {
                    this.ownerType = new ParameterizedTypeImpl(declaringClass, typeParameters, null);
                }
            }
        }
    }

    @Override
    public Type[] getActualTypeArguments() { return this.actualTypeArguments.clone(); }

    @Override
    public Class getRawType() { return this.rawType; }

    @Nullable @Override 
    public Type getOwnerType() { return this.ownerType; }

    @Override public boolean equals(Object o) {
        if (o instanceof ParameterizedType) {
            ParameterizedType that = (ParameterizedType) o;
            if (this == that) return true;
            Type thatOwner = that.getOwnerType();
            Type thatRawType = that.getRawType();
            return Objects.equals(this.ownerType, thatOwner) &&  Objects.equals(this.rawType, thatRawType) && Arrays.equals(this.actualTypeArguments, that.getActualTypeArguments());
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(this.actualTypeArguments) ^ Objects.hashCode(this.ownerType) ^ Objects.hashCode(this.rawType);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(256);
        if (this.ownerType != null) {
            sb.append(this.ownerType.getTypeName());
            sb.append("$");
            if (this.ownerType instanceof ParameterizedTypeImpl) {
                 sb.append(this.rawType.getName().replace(((ParameterizedTypeImpl) this.ownerType).rawType.getName() + "$", ""));
            }
            else {
                sb.append(this.rawType.getSimpleName());
            }
        }
        else {
            sb.append(this.rawType.getName());
        }
        StringJoiner joiner = new StringJoiner(", ", "<", ">");
        joiner.setEmptyValue("");
        for (Type type : this.actualTypeArguments) {
            joiner.add(type.getTypeName());
        }
        sb.append(joiner.toString());
        return sb.toString();
    }
}

And then you can just do new ParameterizedTypeImpl(Map.class, new Type[]{String.class, Long.class}, null) note that it would be a good practice to make this class not visible to others and just create some factory methods.

Other less dynamic way is to use type tokens like in gson:

public class TypeToken<T> {
    final Type             type;
    protected TypeToken() {
        this.type = this.getClass().getGenericSuperclass();
    }
    public final Type getType() { return this.type; }

    @Override public final int hashCode() { return this.type.hashCode(); }
    @Override public final boolean equals(Object o) { return (o instanceof TypeToken<?>) && this.type.equals(((TypeToken<?>) o).type); }
    @Override public final String toString() { return this.type.toString(); }
}

and then new TypeToken<Map<String, Long>>{}.getType(); - types needs to be provided at compile time.
There should be some libraries that provide both this methods, but I don't know any right now as I needed to make my own to also support parsing from string.

GotoFinal
  • 3,260
  • 1
  • 15
  • 28