我的目标是基于Java集合创建一个到一个容器类。 (粗略地类似于Map<String,Collection<Double>>
- 对于单个字符串,地图可以容纳n个双精度。例如,对于关键“坐标”,容器可以保存值[3.1,4.3,7.2]。
一个必要的特征是,应该可以自由地定义每个键保存n个元素的类型。因此,上例中的Collection应该可以由List或Set替换。
以下必须是可能的,如果是,则达到目标:
OneToN2<String, Double, List<Double>> cl =
new OneToN2<>( ArrayList.class );
OneToN2<String, Double, Set<Double>> cs =
new OneToN2<>( HashSet.class );
现在我开始使用以下代码:
package com.fun.with.generics;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.HashMap;
/**
* A map that maps a single key to n values.
*
* @param <K> The key type.
* @param <V> The value type.
* @param <V> The type used as container for the to-n elements.
*/
@SuppressWarnings("serial")
public class OneToN2<K,V,CT extends Collection<V> > extends HashMap<K, Collection<V>>
{
private final Constructor<CT> _containerCtor;
/**
* Create an instance.
*/
public OneToN2( Class<CT> claß )
{
try
{
_containerCtor = claß.getConstructor();
}
catch ( Exception e )
{
throw new IllegalArgumentException( "Need default ctor." );
}
}
/**
* Add a single key value pair.
*
* @param key The key.
* @param element The element to add.
*/
synchronized public void add( K key, V element )
{
Collection<V> ct = get( key );
if ( ct == null )
{
ct = makeToN();
put( key, ct );
}
ct.add( element );
}
/**
* Add an element for multiple keys.
*
* @param keys The keys to add.
* @param element The element the keys refer to.
*/
synchronized public void add( Collection<K> keys, V element )
{
for ( K c : keys )
add( c, element );
}
/**
* Get a newly allocated container holding the n values.
*/
@Override
public Collection<V> get( Object key )
{
Collection<V> n =
super.get( key );
Collection<V> result =
makeToN();
result.addAll( n );
return result;
}
/**
* Create a new instance of the to-n container.
*/
private Collection<V> makeToN()
{
try
{
return _containerCtor.newInstance();
}
catch ( Exception e )
{
throw new InternalError( "Could not create to-n collection." );
}
}
}
请注意,我只保留了最低限度的操作来证明我的意图。
到目前为止一切顺利:我能够在技术上表达我的意图并且编译器接受了它,所以第一步就成功了。
现在问题:我无法创建上述类型的实例:)
以下示例用例显示了预期用途,但它们都不起作用:
预期的解决方案是:
// Declaration (this compiles):
OneToN2<String, Double, List<Double>> x;
// Initialisation (does not compile). Error is Type mismatch: cannot
// convert from OneToN2<String,Double,ArrayList>
// to OneToN2<String,Double,List<Double>>
x = new One2N2<>( ArrayList.class );
Bummer,但据我所知,编译器无法推断出ArrayList.class是一个List。所以我尝试了不太理想但可接受的替代方案:
// Declaration (this compiles):
OneToN2<String, Double, ArrayList<Double>> x;
// Initialisation (does not compile). Error is Type mismatch: cannot
// convert from OneToN2<String,Double,ArrayList>
// to OneToN2<String,Double,ArrayList<Double>>
x = new One2N2<>( ArrayList.class );
再次变得无聊,或多或少有一个不变的错误信息。
问题是:我哪里出错?
(我为什么要这样?学术兴趣,对Generics很有趣。但老实说,我在几个系统中创建了类似的类型导致代码重复,因为to-n容器是固定的。为了防止这种情况,我正在寻找一个通用实现。)
答案 0 :(得分:2)
删除CT
类型参数。不是在构造函数中提供Class
,而是提供用于创建内部集合的工厂(通过接口,或者在Java 8中,您只能提供方法)。
public interface CollectionFactory<V> {
Collection<V> createCollection();
public class OneToN2<K,V> implements Map<K, Collection<V>> {
public OneToN2(CollectionFactory<V> factory) { ... }
...
}
另外,我建议你使用 HashMap
而不是继承它(即更喜欢组合而不是继承)。
答案 1 :(得分:2)
如果你正在使用Java 8,那么Supplier
将为你清理。
public class OneToN2<K,V, CT extends Collection<V> > extends HashMap<K, Collection<V>>
{
private final Supplier<CT> _containerCtor;
/**
* Create an instance.
*/
public OneToN2( Supplier<CT> _containerCtor )
{
this._containerCtor = _containerCtor;
}
...
/**
* Create a new instance of the to-n container.
*/
private Collection<V> makeToN()
{
return _containerCtor.get();
}
}
然后您可以像这样实例化您的集合:
public static void main(String[] args) {
OneToN2<String, Double, List<Double>> x = new OneToN2<>(ArrayList::new);
}
答案 2 :(得分:0)
这在我的IDE中编译得很好:
OneToN2<String, Double, List<Double>> x = new OneToN2(ArrayList.class);
摆脱了<>
new OneToN2
此链接也可能有用:Passing a class with type parameter as type parameter for generic method in Java