简体   繁体   中英

How do I instantiate an instance of a generic method parameter in Java?

Consider the following code:

// ...
public class BaseClass
{
    public BaseClass (int theParam)
    {
        // ...whatever...
    }
}
public class DerivedType
{
    // ...Content does not matter...    
}


// ...elsewhere:

public <ElemType extends BaseClass> boolean doIt (ArrayList<ElemType> target)
{
    ElemType newElem=new ElemType (5) ; // "Cannot instantiate this type"

    // ...other code does not matter...

    return true ;
}

// ..

How can I create an instance of type ElemType in doIt ?

The construct shown yields the error indicated.

ElemType.newInstance does not exist, which surprises me.

I've read practically all FAQs, answers and googleable material, but I cannot find anything helpful.

EDIT: Yes I know reflection has its downsides, and is not the ultimate solution, for numerous reasons. The question is not "should I do it", but "how would I do it".

Please consider that generic-informations are erased by the compiler at compile-time and are replaced with object. Internally generics is just casting from and to java.lang.Object.

This also why it's difficult to obtain generic information at runtime, even though it's possible.

See here: Google .

On a persopnal note: If you need to do anything like this, it is usually bad design. I was at this situation a couple of time, but I found a better solution every time :). So just consider if you really want such a dirty hack in your code.

Edit: Regarding to the comment section a more detailed explanation is needed.

Reflection in general should be used with caution anyway, since from a software engineering point-of-view it is considered bad design. Why? It might introduce some hard to find bugs, since reflection alters the natural flow of your application and uses informations which are not always visible to you at development-time. This screams out for unexpected behavior.

And even though I have no formal proof for this, but I state that each time you need reflection, there is another solution for your problem (if generative software-development is an option ;)).

So in the end, in 99% of all cases reflection is nothing more then a dirty hack of a lazy programmar. This might be related to the fact that 100% of all programmars are lazy, but anyway.

Edit 2:

Since you want the code anyway:

abstract class Foo<T> {
    private Class<T> tClass;

    T field;

    public void bar(Class<T> clazz) {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
                ParameterizedType paramType = (ParameterizedType)type;
                tClass = (Class<T>) paramType.getActualTypeArguments()[0];

                field = tClass.newInstance();
        }
    }
}

(taken from: Here )

As mentioned, type erasure of generic types does not allow that. But you can achieve what you want like this:

public class BaseClass {

  public BaseClass(int theParam) {
    // ...whatever...
  }
  public BaseClass() {  
  }          
}

public class DerivedType extends BaseClass {
}

And now doIt() method gets the class argument for reference:

public <D extends BaseClass> boolean doIt (ArrayList<D> target, Class<D> c)
{
    try {
        D newElem = c.getDeclaredConstructor(int.class).newInstance(5);
    } catch (Exception e) {}

    // ...other code does not matter...

    return true ;
}

And you should call it like this:

    ArrayList<DerivedType> testList = new ArrayList<DerivedType>();
    testList.add(new DerivedType());
    testList.add(new DerivedType());
    doIt(testList, DerivedType.class);

Hope that helps :)

Note that, one may really want to be hacky and get rid of the class parameter and try this:

 public static <D extends BaseClass> boolean doIt (ArrayList<D> target)
 {
    try {
        D newElem1 =  ((Class<D>) ((ParameterizedType) target.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).getDeclaredConstructor(int.class).newInstance(5);

    } catch (Exception e) { e.printStackTrace();}

    return true ;
    }
}

In fact I thought so before the second edit :) But this gets a " java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.Class " exception as you mention (I didn't see it because of an overlooked catch statement). In short, Java runtime system does not store the parameterized types (in favor of backwards compatibility; so this may change in the future).

So, it looks like it is not possible without 'touching' some class.

However, other than the mentioned methods, I can think of two more things . First, if both the BaseClass and the DerivedType 'D' class implement clone() method, you can get a clone of an object from the array and then use it:

         D o = target.get(0);

         D oNew = (D)((BaseClass)o).clone();
         target.add(oNew);

Polymorphism will take care of the rest :)

The second one is not a real 'solution', but can be used if all you want is a new instance for an array of objects parameterized by type. Type Erasure only happens for parameterized types, but it does not happen for basic arrays (arrays are reified in JVM). So if we have the freedom to change the signature of the method and working with arrays is ok, then the following would work:

    public <D extends BaseClass> boolean doIt(D[] target) {
    try {
        D newD = (D) (target.getClass().getComponentType().getConstructor(int.class).newInstance(8));
        target[0] = newD;

        // The following is optional, if we want to work with Collections internally
        List<D> l = new ArrayList<D>(Arrays.asList(target));
        l.add(newD);  


    } catch (Exception e) {
        e.printStackTrace();
    }
    return true;
}

Note: Super type tokens would not work for this problem if we cannot introduce new parameters. Please correct me if I'm wrong.

You can't create an ElemType object here since the compiler can't know exactly what an ElemType will be once the generic code is instantiated.

To allow creation of an ElemType, I would supply a factory object of some sort. You could use a Java reflection class, but it's probably easier to supply your own factory base class or interface.

Imagine that you have this:

public class DerivedType extends BaseClass
{
    // No more one argument constructor
    public DerivedType() {
        super(0);
    }
}

Then the call

DerivedType d = new DerivedType(5);

is not valid...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM