Java Generics:如何使用可配置的值保持器类型创建一个to-n集合

时间:2016-11-24 07:55:16

标签: java generics collections

我的目标是基于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容器是固定的。为了防止这种情况,我正在寻找一个通用实现。)

3 个答案:

答案 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