Java中是否可以使用部分类型参数应用程序?

时间:2015-07-25 12:43:42

标签: java generics language-agnostic

我知道Josh Bloch的typesafe heterogeneous container

<T> void container.put(Class<T>, T);
<T> T container.get(Class<T>);

我也读过关于Neal Gafter的"Super" typesafe hetereogeneneous container

<T> void container.put(TypeToken<T>, T);
<T> T container.get(TypeToken<T>);

但是,以下类型的容器让我望而却步:

<T> void container.put(Foo<T>, Bar<T>);
<T> Bar<T> container.put(Foo<T>);

其中FooBar以及任何通用引用类型。

尝试写出来,你必须做类似的事情:

// Setting T at the class level would force some fixed
// T or at best a bounded range of types
class Container<A, B/*, T */> {

    // So we can't have this:
    // Map<A<T>, B<T>> map = new HashMap<>();

    // And must do this (at the expense of type safety):
    Map<A<?>, B<?>> map = new HashMap<>();


    <T> void container.put(A<T> a, B<T> b) {
        map.put(a, b);
    } 

    <T> B<T> container.get(A<T> a) {
        // Not sure what to do here
    }
}

然后您就可以执行以下操作:

// This doesn't compile because T is not specified and thus this is not actual Java:

class FooBarContainer extends Container<Foo, Bar, T> {   
   ...
}  

FooBarContainer container = new FooBarContainer();

// Can do this:
container.put(new Foo<String>(), new Bar<String>());
Bar<String> bar = container.get( /* some Foo<String> */ );

// Cannot do this:
container.put(new Foo<Long>(), new Bar<String>());
Bar<String> bar = container.get( /* some Foo<Long> */ );

但是,这不是合法的Java。

我在这里看到的是,类级泛型与方法级泛型正交。那是问题吗?如果是这样,有没有办法在Java中协调它?

无论如何,在允许这样的容器的类型系统中你需要什么。所谓的必要功能是什么?如果我必须给它命名,我称之为部分类型参数应用程序,但我确信这个概念必须以一些我不了解的一般形式存在。

1 个答案:

答案 0 :(得分:1)

类级别泛型和方法级泛型相结合,它们不是正交的。

我看到一些选项来解决我认为你想要做的事情。但首先,您必须明白,您将无法将泛型声明为另一个泛型的协变,因此您不能说:

class Container<A<B>> {}

因为“A”是一个类型参数,所以没有关联的类型信息,包括它本身是否是泛型类型。如果指定“A”是具有参数的类型,则可以执行此操作,例如:

class Container<A extends Foo<B>> {}

因此,对于第一种情况,您可以使用如下的API:

interface Container<KeyType, ValueType>
{
   void put(KeyType k, ValueType v);
   ValueType get(KeyType k);
}

允许每个键/值类型的具体子类型(实际上只是java.util.Map),例如:

interface StringContainer extends Container<String, String>
{
   void put(String k, String v);
   String get(String k);
}

interface FooBarContainer<C> extends Container<Foo<C>, Bar<C>>
{
   void put(Foo<C> k, Bar<C> v);
   Bar<C> get(Foo<C> k);
}

其中所有键/值对都具有相同的KeyType和ValueType。

在混合中添加方法级泛型允许您使KeyType和ValueType仅与方法调用相关联,而不是与方法的所有调用相关联:

interface VarContainer
{
   <VariantType> void put(Foo<VariantType> k, Bar<VariantType> v);
   <VariantType> Bar<VariantType> get(Foo<VariantType> k);
}

允许在同一个VarContainer中使用变量键/值对:

VarContainer vc = ...
vc.put(new Foo<String>(), new Bar<String>());

vc.put(new Foo<Long>(), new Bar<Long>());

这将使您能够在具体的子类中指定协变类型:

interface VarContainer<VariantType> extends Container<Foo<VariantType>, Bar<VariantType>>
{
   void put(Foo<VariantType> k, Bar<VariantType> v);
   Bar<VariantType> get(Foo<VariantType> k);
}

此另一个选项仅在您知道容器中有Foo和Bar时才有效:

interface FooBarVarContainer
{
   <VariantType> void put(Foo<VariantType> k, Bar<VariantType> v);
   Bar<VariantType> get(Foo<VariantType> k);
}

并且容器现在仅限于Foo / Bar对,但是可以使用相同的变量类型来调用put和两个参数以及在参数和返回值之间获取:

FooBarVarContainer vc = ...
vc.put(new Foo<String>(), new Bar<String>()); // ok
vc.put(new Foo<String>(), new Bar<Long>()); // fails to compile
Bar<Long> = vc.get(new Foo<Long>()); // ok

这有助于解决您的问题所需吗?

还应该提到的是,将可变实例作为键放入映射并不是一种好习惯,因为映射键不仅应该正确实现hashCode和equals方法,而且一旦插入它们,地图就需要根据需要动态重新索引。钥匙改变了。例如,如果hashCode值发生更改,将来在哈希映射中查找该键可能会失败或到达错误的项目。