views:

139

answers:

4

This is my code: The ExecutorImp extends AbstractExecutor which extract the same execute logics of its implementers(ExecutorImp is one case),when calling the execute() method of ExecutorImp, it will call the method in its supertype,but the supertype (the AbstractExcutor) should know another class binding to the implementer(in the example, it is the User class):

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

abstract class AbstractExecutor<E> {
    public void execute() throws Exception {
        ArrayList<E> list = new ArrayList<E>();
        // here I want to get the real type of 'E'
        Class cl = this.getClass().getTypeParameters()[0].getGenericDeclaration().getClass();
        Object o = cl.getConstructor(String.class).newInstance("Gate");
        list.add((E) o);
        System.out.println(format(list));
    }
    public abstract String format(ArrayList<E> list);
    public abstract String getType();
}

public class ExectorImp<E> extends AbstractExecutor<User> {
    @Override
    public String getType() {
        return "user";
    }
    @Override
    public String format(ArrayList<User> list) {
        StringBuffer sb = new StringBuffer();
        for (User u : list) {
            sb.append(u.toString() + " ");
        }
        return sb.toString();
    }
    public static void main(String[] args) throws Exception {
        new ExectorImp().execute();
    }
}
class User {
    String name;
    public User(String name) {
        this.name = name;
    }
}

SO, what is the problem with my codes?

+3  A: 

I think you should use getActualTypeParameters; as getTypeParameters does not refer to what has been put in your current instantiation in place of E, but to E itself (to describe how is it bounded, etc.).

In order to get the ParameterizedType you should use getGenericSuperclass first.

update: but the above only works if the current object is derived from a generic class with the generic argument instantiated, like:

class StringList extends ArrayList<String> {
    public Type whatsMyGenericType() {
        return ((ParameterizedType)getGenericSuperClass()).getActualTypeParameters()[0];
    }
}

should return String.class.

fortran
Just to be precise: getGenericSuperclass() will give meaningful results for you only if the class is not a generic class, so: new LinkedList<String>().getClass().getGenericSuperclass() won't be enough for you, but: new LinkedList<String>(){}.getClass().getGenericSuperclass() is OK - noticed the difference? By adding the {} I created subclass of LinkedList.
iirekm
@iirekm true, then I guess there's no way for an object to detect how it was reflectively declared, but it could be done "from outside" (using `Field.getGenericType()`, for example)...
fortran
+2  A: 

I don't think you could get the generic type at runtime. The generic type is a restriction that applies at compile time. As I remember at runtime there is no difference between a generic collection and a collection without a generic type.

Seitaridis
Not entirely true: read the solution by 'fortran' and my comment to it.
iirekm
He he, I thought that once too... The erasure means that at compile time you could ignore the generic restrictions as the bytecode running both instances is the same, but the information about how the instances were declared is kept.
fortran
+1  A: 

Usual approach to fix the problem is to slightly change the code. Define constructor on the base class accepting Class<E> parameter. Assign this parameter to internal field.

On the subclass define constructor without parameters and call super(User.class) from there.

This way you will know class of argument without much overburden for clients of subclasses.

iYasha
+2  A: 

There's some confusion here. Due to type erasure you can't get type information from the runtime parameterized type like:

Class<E> cls = E.getClass(); // Error.
E e = new E(); // Error.

However, you can obtain compiletime parameterized type information from class, field and method declaration by ParameterizedType#getActualTypeArguments().

abstract class AbstractExecutor<E> {

    public void execute() throws Exception {
        List<E> list = new ArrayList<E>();
        Class<E> cls = (Class<E>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        E e = cls.getConstructor(String.class).newInstance("Gate");
        list.add(e);
        System.out.println(format(list));
    }

    // ...
}

Update: as to whether this is recommended or not, although this will work, this is sensitive to runtime problems whenever minor changes in the class declaration occur. You as developer should document it properly. As a completely different alternative, you can make use of polymorphism.

abstract class AbstractExecutor<E> {

    public void execute() throws Exception {
        List<E> list = new ArrayList<E>();
        E e = create("Gate");
        list.add(e);
        System.out.println(format(list));
    }

    public abstract E create(String name);

    // ...
}

and implement UserExecutor accordingly.

class UserExecutor extends AbstractExecutor<User> {

    @Override
    public User create(String name) {
        return new User(name);
    }

    // ...
}
BalusC
Thanks you very much, and thanks for all the guys in this page. BalusC's answer is what I wanted.However I wonder if my code is recommended? Here is my applaction context:http://forums.oracle.com/forums/message.jspa?messageID=7006003#7006003
hguser