Meta Generic in Java

186 Views Asked by At

I'm 99% certain this is impossible, but I thought it's worth a try: Is it possible to create a class that implements a template?

Motivation: I want to create a generic composite class.

Example:

public class GenericComposite<T> implements T {
    List<T> m_items;

    void add(T item) {
        m_items.add(item);
    }

    @Override
    void f() {
        for (T item : m_items) {
            item.f();
        }
    }
}

Where f is the method defined in the interface T. Of course we don't know it's gonna be f() when we call it nor where we implement it. Is there some meta-meta-magic I'm not aware of that makes this possible? If not, is there some other language out there that supports this?

3

There are 3 best solutions below

0
On BEST ANSWER

You are correct, this is not possible in Java: the compiler cannot even be sure that T is an interface (as opposed to a class), so it cannot let you implement T.

C++ offers this possibility through templates, but even this would not help you implement a "generic composite". The problem is the f() method (or the methods in the actual class for which it stands). What you wanted to write is this:

// This is not going to work! The syntax is not real.
public class GenericComposite<T> implements T {
    List<T> m_items;

    void add(T item) {
        m_items.add(item);
    }

    @Override
    <all-methods-of-T>();
}

In order to write @Override void f() you must know that T has void f(). You need to know this about every single method of T, which, of course, defeats the purpose of hiding T's identity in the first place: your GenericComposite<T> knows everything there is to know about the interface, i.e. all its methods.

When you build a composite, having a list of sub-elements is a much smaller problem than implementing methods that let the callers treat the composite in the same way as they treat leaf objects. All these "aggregate" methods are always custom, making generic structural implementations of composite nearly useless.

5
On

Yes you can do this.

public interface Thing {
    void something(); 
}

public Composite<T implements Thing> implements Thing {
    private List<T> things = new ArrayList<>();

    @Override
    public void something() {
        for(Thing aThing:things) {
            aThing.something();
        }
    }
}

However, given everything shares a common interface, does you really need generics to solve this?

0
On

Yes you can do this in Java with Proxys and reflection. I've done it before. The extra issue you have to worry about is methods that return values. If you are wrapping multiple implementations, which return value do you actually return?

In my solution, I use an enum to tell the class to either return the first result from the list or the last result (either way all delegates are called)

Here's my code

/**
 * {@code MultipleWrapper} uses dymanic proxies to wrap
  * several instances of an interface.  This allows
 * all wrapped instances to be called by only a single
 * call to the wrapper.
 * @author dkatzel
 */
public final class  MultipleWrapper<T> implements InvocationHandler{

private final ReturnPolicy policy;
private final List<T> delegates = new ArrayList<T>();
/**
 * Since methods can only return a single
 * return value, only one of the wrapped
 * methods can be returned to the caller (even though
 * they will all be called).
 * @author dkatzel
 */
public static enum ReturnPolicy{
    /**
     * Return the first wrapped instance.
     */
    RETURN_FIRST,
    /**
     * Return the last wrapped instance.
     */
    RETURN_LAST
}
/**
 * Create a dynamic proxy to wrap the given delegate instances.
 * @param <T> the interface to proxy.
 * @param <I> the instances of T.
 * @param classType the class object of T.
 * @param policy the return policy to use on methods that return something.
 * @param delegates the list of delegates to wrap in the order in which 
 * they will be called.
 * @return a new instance of T that wraps the delegates.
 * @throws IllegalArgumentException if no delegates are given
 * @throws NullPointerException if classType ==null or policy ==null or any delegate ==null.
 */
@SuppressWarnings("unchecked")
public static <T, I extends T> T createMultipleWrapper(Class<T> classType,ReturnPolicy policy, Iterable<I> delegates){

    return (T) Proxy.newProxyInstance(classType.getClassLoader(), new Class<?>[]{classType}, 
            new MultipleWrapper<T>(policy,delegates));
}
/**
 * Convenience constructor which is the same as calling
 * {@link #createMultipleWrapper(Class, ReturnPolicy, Object...)
 * createMultipleWrapper(classType,ReturnPolicy.RETURN_FIRST,delegates)}
 * @see #createMultipleWrapper(Class, ReturnPolicy, Object...)
 */
public static <T,I extends T> T createMultipleWrapper(Class<T> classType,Iterable<I> delegates){
   return createMultipleWrapper(classType,ReturnPolicy.RETURN_FIRST,delegates);
}
/**
 * Convenience constructor which is the same as calling
 * {@link #createMultipleWrapper(Class, ReturnPolicy, Object...)
 * createMultipleWrapper(classType,ReturnPolicy.RETURN_FIRST,delegates)}
 * @see #createMultipleWrapper(Class, ReturnPolicy, Object...)
 */
@SafeVarargs
public static <T,I extends T> T createMultipleWrapper(Class<T> classType,I... delegates){
   return createMultipleWrapper(classType,ReturnPolicy.RETURN_FIRST,Arrays.asList(delegates));
}
/**
 * Convenience constructor which is the same as calling
 * {@link #createMultipleWrapper(Class, ReturnPolicy, Object...)
 * createMultipleWrapper(classType,ReturnPolicy.RETURN_FIRST,delegates)}
 * @see #createMultipleWrapper(Class, ReturnPolicy, Object...)
 */
@SafeVarargs
public static <T,I extends T> T createMultipleWrapper(Class<T> classType,ReturnPolicy policy,I... delegates){
   return createMultipleWrapper(classType,policy,Arrays.asList(delegates));
}


private MultipleWrapper(ReturnPolicy policy,Iterable<? extends T> delegates){
    if(policy==null){
        throw new NullPointerException("policy can not be null");
    }

    this.policy = policy;
    for(T delegate : delegates){
        if(delegate ==null){
            throw new NullPointerException("delegate can not be null");
        }
        this.delegates.add(delegate);
    }    
    if(this.delegates.size()==0){
        throw new IllegalArgumentException("must wrap at least one delegate");
    }
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
    List<Object> returns = new ArrayList<Object>(delegates.size());
    try{
        for(T delegate :delegates){
            returns.add(method.invoke(delegate, args));
        }
        if(policy == ReturnPolicy.RETURN_LAST){
            return returns.get(returns.size()-1);
        }
        return returns.get(0);
    }catch(InvocationTargetException e){
        throw e.getCause();
    }

}

}

Then to use it you can do something like this:

List<Type> listeners = ...

Type wrappedListener = MultipleWrapper.createMultipleWrapper(Type.class, listeners);