Create instance of generic type in Java when param

2019-08-05 06:39发布

I have been reading the answers to the question:

Create instance of generic type in Java?

I have implemented the approach suggested by Lars Bohl. I adapted his code as follows:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class ParameterizedTypeEg<E> {
    public Class<E> getTypeParameterClass() {
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }
    private static class StringHome extends ParameterizedTypeEg<String> {
        private String _string;
        StringHome (String string) {
            _string = string;
        }
    }
    public static void main(String[] args)
        throws InstantiationException, IllegalAccessException {
        String str = new StringHome("my string").getTypeParameterClass().newInstance();
        String str2 = new ParameterizedTypeEg<String>().getTypeParameterClass().newInstance();
    }
}

This approach works fine for the str variable. Then str2 is created with what appears to me to be the same type (ParameterizedTypeEg < String >, which is basically the same thing as a StringHome). However, the approach does not work for str2, and a ClassCastException is thrown when I try to cast (ParameterizedType) type.

Even though for str2, I have parameterized ParameterizedTypeEg with a String, getGenericSuperclass() returns something very different than for str. Also, within methods str2 shows 'this' as a ParameterizedTypeEg, whereas for str, 'this' is a ParameterizedTypeEg$StringHome. I suppose that is the root of the problem. Why does Java not see that the generic type has been determined for str2 also?

I have had what appears to be the same problem when the parameterized type is passed through multiple levels of hierarchy? That is, class B< T > contains A< T > and I instantiate a B. Within A, I cannot create a String object by determining the parameterized type of A using the above approach. The approach produces an exception in the case of a containment hierarchy as well. And this causes me a problem because I want to be able to pass the parameterized type through multiple levels of containment and/or inheritance and have the same approach produce an instance of the generic type in all cases.

Thanks, John

4条回答
倾城 Initia
2楼-- · 2019-08-05 07:16

However, the approach does not work for str2, and a ClassCastException is thrown when I try to cast (ParameterizedType) type
........... Why does Java not see that the generic type has been determined for str2 also?

As specified in oracle doc for ParameterizedType:

ParameterizedType represents a parameterized type such as Collection<String>

But, super class of ParameterizedTypeEg is Object , which is not Generic Type. So, the Type returned by getClass().getGenericSuperclass(); is Class of Object itself. And type casting of non parametrized type to the ParameterizedType is giving you ClassCastException for str.

查看更多
Explosion°爆炸
3楼-- · 2019-08-05 07:27

Why does Java not see that the generic type has been determined for str2 also?

An object at runtime does not have any type parameter information. Basically, for you to do .newInstance() to create an object, you need to have the class object at runtime, which means you have to store it somewhere in a way to get it.

The reason it works for str is exactly because the type String is stored somewhere -- in the class metadata. Classes have metadata which includes the superclass and superinterfaces, enclosing class if it's an inner class, and the types of fields and methods (return type and parameter types of methods). All this information is stored, with generics, in the bytecode at runtime (the reason for this is that files in Java can be compiled separately, so when compiling one file that uses a class from an already compiled file, the compiler must be able to look at the class file and see the generics information), and is available at runtime via reflection on the class object.

So basically, the StringHome class acts as a "storage" for the String class object, because String was hard-coded as the type parameter of its superclass at compile-time. So at runtime you can get it out of this "storage".

That is, class B< T > contains A< T > and I instantiate a B. Within A, I cannot create a String object by determining the parameterized type of A using the above approach.

From my discussion above, you probably picked up that the key thing is to somehow "get" at runtime the class object for the class that is T. Making a class that subclasses A<T> will not help, because the point of that was that the actual class was hard-coded at compile-time, which requires knowing the class at compile-time. We don't know what class T is at compile-time, so we can't put it into the metadata; we can only put "T", i.e. a type variable.

So, again, the problem goes back to needing to find some way to pass or transmit the class object for the class that the type parameter represents. You cannot rely on any compile-time based mechanisms anymore. So you have to pass it in in an extra parameter or something.

查看更多
啃猪蹄的小仙女
4楼-- · 2019-08-05 07:30

getClass().getGenericSuperclass(); gives you the details of the super class. Therefore it will only work if you subclass a parameterized super class. It won't work if you instantiate a parameterized super class given type parameters.

查看更多
我命由我不由天
5楼-- · 2019-08-05 07:34

With the following change your code will work:

from

  String str2 = new ParameterizedTypeEg<String>().getTypeParameterClass().newInstance();

to

   String str2 = new ParameterizedTypeEg<String>(){}.getTypeParameterClass().newInstance();

This creates an anonymous subclass of ParameterizedTypeEg. When you call

getClass().getGenericSuperclass();

on StringHome, you get a ParameterizedTypeEg < java.lang.String>, which is what you want. If you create str2 as you did, that call simply returns Object, so the attempt to cast it to a paremeterized type fails:

Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType

Creating an anonymous subclass makes this return ParameterizedTypeEg < java.lang.String>

This is the same trick that's used in the Type Token class in the Google Guice Guave libraries, btw. You write, for example

new TypeToken<List<String>>() {}

and not

 new TypeToken<List<String>>()  
查看更多
登录 后发表回答