Pateman
Pateman

Reputation: 2757

Create a generic map using type given at runtime

The title may be a bit hard to understand, but let me just briefly describe my problem.

Let's assume I have an annotation like this:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Identifier {

}

Now, I make a class which annotates any of its fields with it:

public class Student {
    private String name;
    private String surname;
    @Identifier
    private String idNumber;
    ...
}

Finally, at runtime I want to create a Map with the key type of typeof(field annotated with @Identifier) and the value type of Student. Note that any field can be annotated with @Identifier.

Any ideas?

EDIT

Ok, let me clarify this a bit:

class Student {
    private String name;
    private String surname;
    @Identifier
    private String idNumber;
}

class Foo {
    @Identifier
    private Integer x;
}

//  Now, what I want to have are two maps:

SortedMap students;     //  key type: String
                        //  value type: Student
SortedMap foos;         //  key type: Integer
                        //  value type: Foo

Thanks in advance!

Upvotes: 3

Views: 609

Answers (2)

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 280112

I'm still not exactly sure what you want to do.

at runtime I want to create a Map with the key type of typeof(field annotated with @Identifier) and the value type of Student

You can create a raw Map or a Map<Object, Object>. You can get the type of the field annotated with @Identifier. I'm not sure what you mean by value type of Student so I'll assume you mean the type Student, ie. its Class object.

public static void main(String[] args) throws Exception {
    Class<?> clazz = Student.class;
    Map<Object, Object> map = new HashMap<>();
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        Identifier annotation = field.getAnnotation(Identifier.class);
        if (annotation != null) {
            map.put(field.getType(), clazz);
        }
    }
    System.out.println(map);
}

With your example class in your question, this prints

{class java.lang.String=class com.spring.Student}

So the annotated field type is mapped to the class type.

You won't be able to have a Map<String,Student> though because you don't know the type String (and possibly not even Student) at compile time. You can try casting, but you're setting yourself up for a number of ClassCastExceptions.

Upvotes: 1

Adam Arold
Adam Arold

Reputation: 30548

So you are going to have a method (myMethod in my example) which will be passed objects which may hold a field annotated with @Identifier.

Sorry to burst your bubble but there is no way to keep generic information at runtime. The closest you can get is having a Map<Field, Class<?>> which holds key-value pairs with your desired type. This is how you do it:

public Map<Field, Class<?>> myMethod(Object obj) {
    Map<Field, Class<?>> result = new HashMap<Field, Class<?>>();
    for(Field field : obj.getClass().getDeclaredFields()) {
        Identifier identifier = field.getAnnotation(Identifier.class);
        if(identifier != null) {
            result.put(field, obj.getClass());
            return result;
        }
    }
    return result;
}

In my example the result will either be an empty Map or a Map with one key-value pair. I suggest you should use a separate type for the result instead of a Map. Of course you can tamper with the code if you want something other than a Field for example you can use Field's getType() or getGenericType() methods.

Upvotes: 0

Related Questions