如何创建一个泛型类作为类型的泛型类?

时间:2015-07-08 14:57:52

标签: java generics

在我的项目中,我有一个Model抽象类,它定义了一个getId()方法和一个扩展Model的不同类。 为了使每个类能够根据需要定义ID字段的类型,我将Model类设为通用:

public abstract class Model<T> {
    public abstract T getId();
}

所以我现在可以扩展它:

public class Person extends Model<String> {
    @Override
    public String getId() {
        return getName();
    }
}

没关系。

现在我想创建一个充当变更集的类,我可以在其中管理更新和删除的模型。在本课程中,我想使用地图管理更新的模型,模型ID是键,模型本身是值。 (变更集只有一种类型,例如ChangeSet只包含person,ChangeSet只包含汽车等等。)

我想像这样实例化它:

ChangeSet<Person> changeSet = new ChangeSet<Person>();

对我来说,有可能推断出ID将是一个String,因为Person扩展了Model。

但它似乎不可能。我最接近的是这两种选择:

public class ChangeSet<M extends Model, T>{
    private final Map<T, M> updated = new HashMap<>();
...
}

所以我可以在一组中存储更新的模型。但Eclipse正在证明我应该指定Model的泛型类型,创建一个这样的签名:

public class ChangeSet<M extends Model<T>, T>{
    private final Map<T, M> updated = new HashMap<>();
...
}

Eclipse很好用,但现在我需要这段代码来实例化一个对象。

ChangeSet<String, Person> changeSet = new ChangeSet<Person<String>, String>();

它不那么优雅,并且重复了#34; String&#34;很尴尬。

有什么方法可以得到(接近)我想要的声明吗?

2 个答案:

答案 0 :(得分:2)

接口使用和泛型的良好组合肯定会导致简单的更清晰的实现,也是可扩展的。

这是我的建议 - 我实际上实现了它并且它工作正常:

根据OP的评论进行更新

模型界面可以“生成”以约束返回类型:

package org.example;

/**
 * Created by prahaladd on 08/07/15.
 */
public interface Model<T extends  Identifier>
{
    T getIdentifier();
}

实现使用特定类型标识符的模型类:

package org.example;



/**
 * Created by prahaladd on 08/07/15.
 */
public class Person implements Model<StringIdentifier>
{

    private final String name;
    private final String id;

    public Person(String id, String name)
    {
        this.id = id;
        this.name = name;
    }
    @Override
    public StringIdentifier getIdentifier()
    {
        return new StringIdentifier(id);
    }

    public String getName()
    {
        return  name;
    }
}

ChangeSet实现现在改变了一点,以模仿Map接口,如下所示。它实际上现在接受将存储为键的标识符的类型:

package org.example;

import java.util.Map;

/**
 * Created by prahaladd on 08/07/15.
 */
public class ChangeSet<T extends Identifier, M extends  Model<T>>
{
    //Refer to PECS - http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs

    private Map<? super Identifier, M> changeMap;

    public void addChange(M element)
    {
        changeMap.put(element.getIdentifier(),element);
    }

    public M getChangedElementForId(T id)
    {
        return changeMap.get(id);
    }
}

所有这些变化都不是那么糟糕 - 您可以非常轻松地实例化ChangeSet,如下所示:

package org.example;

public class Main {

    public static void main(String[] args)
    {
        Person p1 = new Person("1", "Tom");
        Person p2 = new Person("2", "Jerry");

         //change set is instantiated without any redundant generic parameters
        ChangeSet<StringIdentifier, Person> changes = new ChangeSet<StringIdentifier,Person>();

        //assume that there were some changes and you want to add them to the changeset.
        changes.addChange(p1);
        changes.addChange(p2);

        //retrieve element from the changeset for an id

        p1= changes.getChangedElementForId(new StringIdentifier("1"));
        p2 = changes.getChangedElementForId(new StringIdentifier("2"));


    }
}

替代解决方案

首先 - 定义一个封装你的ID的接口。这不是一种矫枉过正;鉴于你有不同类型的ID使用接口来定义标识符的合同将使你的代码清洁和可扩展有很长的路要走:

package org.example;

/**
 * Created by prahaladd on 08/07/15.
 */
public interface Identifier<T>
{
    T getIdentifier();
}

现在您已经定义了一个Identifier接口,您可以为它定义与您的各种ID类型相对应的不同实现。对于例如下面我提供了StringIdentifier的实现,它生成了string类型的ID:

package org.example;

/**
 * Created by prahaladd on 08/07/15.
 */
public class StringIdentifier implements Identifier<String>
{

    private final String identifier;

    public StringIdentifier(String id)
    {
        identifier = id;
    }

    @Override
    public String getIdentifier()
    {
        return "someId";
    }
}

现在定义Model接口。理想情况下,Model接口不应该处理任何ID类型,它应该知道它必须返回一个Identifier(就像你的用例一样)。

package org.example;

/**
 * Created by prahaladd on 08/07/15.
 */
public interface Model
{
    Identifier getIdentifier();
}

现在提供Model接口的实现。对于例如下面是您的查询中提到的Person类:

package org.example;

/**
 * Created by prahaladd on 08/07/15.
 */
public class Person implements Model
{

    private final String name;
    private final String id;

    public Person(String id, String name)
    {
        this.id = id;
        this.name = name;
    }
    @Override
    public Identifier getIdentifier()
    {
        return new StringIdentifier(id);
    }

    public String getName()
    {
        return  name;
    }
}

现在定义ChangeSet。 ChangeSet应该只知道它存储ID对象和相应Model之间的映射。它并不真正了解ID对象的类型。这使得ChangeSet类非常灵活,甚至可以支持异构集合以及您想要的同类集合。

package org.example;

import java.util.Map;

/**
 * Created by prahaladd on 08/07/15.
 */
public class ChangeSet<M extends  Model>
{
    //Refer to PECS - http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs

    private Map<? super Identifier, M> changeMap;

    private Class identifierType;

    public void addChange(M element)
    {
        //prahaladd - update :  save the identifier type for a later check.
        if(identifierType != null)
        {
             identifierType = element.getIdentifier.getClass();
        } 
        changeMap.put(element.getIdentifier(),element);
    }

    public M getChangedElementForId(Identifier id)
    {
        //prahaladd updated - verify that the type of the passed in id
        //is the same as that of the changeset identifier type.
        if(!id.getClass().equals(identifierType))
        {
                 throw new IllegalArgumentException();
        }
        return changeMap.get(id);
    }
}

现在努力工作得到了回报。看看下面的客户端实现:

package org.example;

public class Main {

    public static void main(String[] args)
    {
        Person p1 = new Person("1", "Tom");
        Person p2 = new Person("2", "Jerry");

        ChangeSet<Person> changes = new ChangeSet<Person>();

        //assume that there were some changes and you want to add them to the changeset.
        changes.addChange(p1);
        changes.addChange(p2);

        //retrieve element from the changeset for an id

        p1= changes.getChangedElementForId(new StringIdentifier("1"));
        p2 = changes.getChangedElementForId(new StringIdentifier("2"));
    }
}

正如您所设想的那样!正如你所看到的,这里没有任何花哨的东西。简单的面向对象概念和界面与泛型的深思熟虑的组合。

希望这会有所帮助!!

答案 1 :(得分:0)

如果更新的地图是ChangeSet的实现细节,您可以简单地写:

public class ChangeSet<M extends Model<?>> {
    private final Map<Object,M> updated = new HashMap<>();

    public void add(M model) {
        updates.put(model.getId(), model);
    }
}

但如果ChangeSet应该具有依赖于模型的id类型T的方法,那么这不是一个好的解决方案。