0

Is it possible to artificially create a ParameterizedType object that would be the definition of a collection of a particular specified type? If I have a named Collection field I can have the proper definition, ie. for field in a class like this

public class MyContainerClass {
  List<String> myElementsList;
}

I can extract all the information I need through the following code.

public class GetGenericsTest {
    public static class MyContainerClass {
        List<String> myElementsList;
    }

    public static void main(String[] args) throws Exception {
        Field field = MyContainerClass.class.getDeclaredField("myElementsList");

        ParameterizedType pt = (ParameterizedType) field.getGenericType();
        System.out.println("collection type: " + pt.getRawType().getTypeName());
        System.out.println("elt type:        " + ((Class<?>)pt.getActualTypeArguments()[0]).getName());
    }
}

which produces:

collection type: java.util.List
elt type:        java.lang.String

But I can't figure out how to create such a ParameterizedType through Reflection only.

In other words, I need a generic solution so that the following test code would pass:

Class<?> elementClass = MyElement.class;

ParameterizedType parameterizedType = implementMe(elementClass, List.class);

Assertions.assertEquals(List.class.getName(), parameterizedType.getRawType().getTypeName());
Assertions.assertEquals(elementClass.getName(), ((Class<?>)pt.getActualTypeArguments()[0]).getName());

kaya3
  • 47,440
  • 4
  • 68
  • 97
Dariusz
  • 21,561
  • 9
  • 74
  • 114

2 Answers2

1

Here is implementMe

public class TypeImpl implements Type {
    private final Class<?> clazz;

    public TypeImpl(Class<?> clazz) {
        this.clazz = clazz;
    }

    @Override
    public String getTypeName() {
        return clazz.getName();
    }
}

public ParameterizedType implementMe(Class<?> elementClass, Class<?> collectionClass) {
    return new ParameterizedType() {
        @Override
        public Type[] getActualTypeArguments() {
            return new Type[] {
                    new TypeImpl(elementClass)
            };
        }

        @Override
        public Type getRawType() {
            return new TypeImpl(collectionClass);
        }

        @Override
        public Type getOwnerType() {
            return null;
        }
    };
}

Testing:

Class<?> elementClass = String.class;
ParameterizedType parameterizedType = implementMe(elementClass, List.class);
System.out.println(List.class.getName().equals(parameterizedType.getRawType().getTypeName()));//true
System.out.println(elementClass.getName().equals(((Class<?>)pt.getActualTypeArguments()[0]).getName()));//true

But I can't figure out how to create such a Class through Reflection only.

You can't create your own Class. It's a final class and has private constructor.

Javadoc for Class says

Class has no public constructor. Instead Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and by calls to the defineClass method in the class loader.

How to extend a final class?(Reflection, Javassist)

How to extend a final class in Java

Is it possible to extend a final class in Java?

If you try to overcome the private access of Class constructor with reflection you'll have SecurityException

Class<Class<?>> classClass = (Class<Class<?>>) (Object) Class.class;
Constructor<Class<?>> constructor = classClass.getDeclaredConstructor(ClassLoader.class);
constructor.setAccessible(true);//java.lang.SecurityException: Cannot make a java.lang.Class constructor accessible
Class<?> newClass = constructor.newInstance(this.getClass().getClassLoader());

Also please notice that for two types List<String> and List<Integer> types are different but class is the same List<?>. So you already have collectionClass.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • I edited my question. I don't need to create a `Class`, I just needed the `ParameterizedType`. I'm sorry for that. Please remove the class creation parts, as they are actually offtopic - once again, my bad. I hoped one could somehow use the exiting implementation of `ParameterizedType`, but apparently it is not so. – Dariusz Oct 04 '20 at 14:59
0

Your question doesn't make sense. What I think you need is a short primer on what generics is.

They are compiler-checked comments.

That's all they are. The compiler will use them to generate warnings, errors, and silent casts. The compiler will then toss that info. The one exception is where generics show up in types that can affect the compilation of code in other source files, so, any type that shows up in a signature (the type of a field, any extends clauses you have, or the type of any method parameter or the return type of any method) - but that's handled by the compiler by writing this info in the class file, 'as a comment' - the JVM itself completely ignores all of that stuff. Does not care one iota.

What you suggest would imply that, at compile time, there's nothing you can do; it's dynamic.

But generics only exist at compile time.

Therefore, what you want, would be completely useless. Hence, the actual answer to your question (which is: What you want is completely impossible) - isn't actually a problem. Because there is no point to wanting to do this.

A few further issues:

containerClass.getGenericSuperclass();

This doesn't do what you think it does. A java.lang.Class variable is incapable of holding generics. Try it out yourself:

List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
sysout(list1.getClass() == list2.getClass();

The above prints 'true', thus proving that the generics parts aren't in class variables. Also, Class<?> c = List<Integer>.class; is a compiler error.

What .getGenericSuperclass() gets you is the AbstractList<String> in:

public class MyClass extends AbstractList<String>.

Note if you try this stunt on arraylists, you get what's literally in ArrayList.java: AbstractList<T>.

If you want to make this method work out:

public <T> List<T> makeListOf(T type) {
    // .. stuff here
}

such that:

List<String> list = makeListOf(String.class);
assertEquals(list.getClass()
   .getGenericSuperclass().toString(), 
   "AbstractList<String>");

Then the only way is to use ASM or BCEL or other bytecode-creating tools to create a brand new class at runtime and load it in via a classloader's defineClass, permanently eating up memory of your process. This would serve absolutely no useful purpose, so I very much doubt you'd want to do that. It's also quite complicated.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • I'm afraid you are wrong. While this information is lost for simple variables as in your example, it is possible to obtain geberics information at runtime from classes regarding their *fields*, and those fields contain information about the classes inside any generics-based class. Feel free to ask such a question and I'll be happy to provide the code. I'll try to clarify the question in the meantime. – Dariusz Oct 02 '20 at 14:02
  • I actually put working code in the question, take a look. – Dariusz Oct 02 '20 at 14:11
  • @Dariusz - ' it is possible to obtain generics information at runtime from classes regarding their fields' I explicitly mention this in my answer. Please read it in full and understand it before chiming in with 'this is wrong'. – rzwitserloot Oct 02 '20 at 16:00
  • @Dariusz and your edit doesn't change that my answer is what you'rel ooking for. Specifically the section on: You can't reflectively create new Class instances. At all. You can define entirely new classes, generating them on the fly with BCEL or ASM or whatnot, and you definitely don't want to do any of that. – rzwitserloot Oct 02 '20 at 16:01