如何声明具有多个具有非平凡关系的泛型类型的成员?

时间:2010-02-17 22:36:11

标签: java generics

以下是我想在我的java代码中编写的内容:

private <A extends Action<R>, R extends Result> MyType<A,R> member;

然而,这是无效的语法。所以我最后写了:

private MyType<? extends Action<? extends Result>, ? extends Result> member;

但是这忽略了这两个派生自Result的类是相同的事实。我的类方法都强制执行这种关系,因此我可以确定MyType强制执行它,但在某些情况下我仍然必须不安全地对member进行类型转换。

更多详情

以下是我想要做的确切版本,虽然它更加危险:

我希望我能做到:

private <A extends Action<R>, R extends Result> 
    Map< Class<A>, ActionHandler<A,R> > handlers;

相反,我必须这样做:

private Map< Class< ? extends Action<? extends Result> >, 
             ActionHandler<? extends Action<? extends Result>, 
                           ? extends Result> > handlers;

我的方法强制执行所需的关系,看起来像这样:

public <A extends Action<R>, R extends Result> void addHandler( 
    ActionHandler<A, R> handler ) {
  handlers.put( handler.getActionType(), handler );
}

我想要以下方法:

public <A extends Action<R>, R extends Result> ActionHandler<A, R> 
    findHandler( A action ) {
  return handlers.get( action.getClass() );
}

但这不起作用:我必须添加演员和@SuppressWarnings("unchecked")

我尝试过的事情

我尝试为此目的创建一个新类:

public class MyMap <A extends Action<R>, R extends Result> extends 
    HashMap< Class<A>, ActionHandler<A,R> > { ... }

MyMap< ?, ? > handlers;

但它没有用,我还需要演员。

3 个答案:

答案 0 :(得分:4)

没有干净的方法可以做到这一点。您可以做的最好的就是您已经完成的工作 - 隐藏强制类型安全的Map隐藏方法,并在这些方法中隐藏必要的强制转换。

Effective Java 第2版,第29项:“考虑类型安全的异构容器”(其中Josh Bloch勾勒出你正在做的更简单的版本,一个“地图”,其中的键是类和值是键类的实例):

  

接下来要注意的是favorites地图的值类型只是Object。换句话说,Map不保证键和值之间的类型关系。事实上,Java的类型系统不足以表达这一点。但我们知道这是真的,当我们找到最喜欢的时候,我们会利用它。

鉴于您无法为值获取任何有用的类型强制执行,最简单的实现可能是这样的:

public class HandlerRegistry
{
    private Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();

    public <R extends Result, A extends Action<R>>
        void addHandler(Class<A> actionClass, ActionHandler<R, A> handler) {
        map.put(actionClass, handler);
    }

    @SuppressWarnings("unchecked")
    public <R extends Result, A extends Action<R>> ActionHandler<R, A>
        findHandler(A action) {
            return (ActionHandler<R, A>) map.get(action.getClass());
    }
}

关于基于Map的解决方案需要注意的一点是:如果action不完全是用作密钥的类,map.get()将不起作用 - 例如,如果它是一个子类,或者密钥类是一个接口,action本身就是实现。你可能会更好:

    @SuppressWarnings("unchecked")
    public <R extends Result, A extends Action<R>> ActionHandler<R, ? super A>
        findHandler( A action ) {
            for ( Map.Entry<Class<?>, Object> entry : map.entrySet() )
            {
                if (entry.getKey().isAssignableFrom(action.getClass())) {
                    return (ActionHandler<R, ? super A>) entry.getValue();
                }
            }
            return null;
    }

注意:最初上面的第二个示例返回ActionHandler<R, A>,这实际上并不正确 - 它应该是? super A。(我们不知道它是什么除了A之外,它可以处理 - 它可以是A的任何超类,一直到Object。)在这种特殊情况下,它可能足够安全,但是如果你考虑类似于List,我们可能遇到很多麻烦:您可以安全地将A放入List<? super A>,但如果您认为自己只会获得A s在它之外,你将获得ClassCastExceptions。)

答案 1 :(得分:1)

因此,您希望在不进行强制转换的情况下实现类型安全的异构容器

有趣的谜题。

我认为这在Java中是不可能的。考虑:

class Entry<K,V> { 
    K key;
    V value;
}
interface Map<E extends Entry<?,?>> { 
    void put(E e);
    E get(Object k);
}

class MyEntry<T> extends Entry<Class<T>, T> { }

持有Map<MyEntry<?>>的呼叫者现在可以确保条目是类型安全的,并且Map中指定的put方法可以很好地强制执行此操作。此外,get方法保证返回正确的映射。我无法表达的是,它的参数是返回条目的密钥类型。

我尝试了如下:

<K, V, RE extends E & Entry<K,V>> V get(K k);

不幸的是,类型参数使用不同的类型参数实现两次接口是非法的,或者javac将它放入:

  

可能不会跟随类型变量   其他界限

答案 2 :(得分:0)

将参数化移动到类本身?

public class X<A, R...> {
    private Map<A,R> blah;
    public R method(A type) {}
}