Map.put使用泛型

时间:2015-11-08 15:11:36

标签: java generics inheritance covariance

我使用以下代码收到错误(如下面的代码段所示):

public class MyClass {
  private Map<String, Subclass1> mapToSubclass1;
  private Map<String, Subclass2> mapToSubclass2;

  public void update(
      final boolean updatesAreSubclass1,
      final List<? extends Superclass> updates) {

    Map<String, Superclass> mapToUpdate;
    if (updatesAreSubclass1) {
      mapToUpdate = mapToSubclass1;
    } else {
      mapToUpdate = mapToSubclass2;
    }


    updates.stream().forEach((entity) -> {
      mapToUpdate.put(entity.getId(), entity);
    });
  }
}

其中Subclass1Subclass2延伸SuperclassSuperclass提供public String getId();

如上所述,我在尝试定义mapToUpdate时遇到错误 - Incompatible types. Required: Map<String, foo.bar.Superclass>, Found: Map<String, foo.bar.Subclass1>(或者子句中的子类2)。

如果我将mapToUpdate更改为Map<String, ? extends Superclass>,则在尝试put - Wrong 2nd argument type. Found: 'foo.bar.Superclass', required '? extends foo.bar.Superclass'

时出错

认为这与协方差的概念有关,但我不确定如何解决问题。我提出了几个解决方案,既不令人满意:

  • 我是否需要两个update方法,每个子类一个(如果有两个以上,这会很快变得混乱)?
  • 我应该移动put子句中的if (updatesAreSubclass1),并将updates投射到相应的List<Subclass>吗?

5 个答案:

答案 0 :(得分:2)

这是一个解决方案,可以使用无数个可能的子类,因为据我所知,你只是创建了一个类加上Id的地图 - &gt;超类。

private Map<Class,Map<String,Superclass>> map = new HashMap<>();
void update(List<? extends Superclass> l) {
    l.stream().forEach(o -> put(o));
}

public void  put(Superclass obj) {
    String id = obj.getId();
    Map<String,Superclass> submap = map.get(obj.getClass());
    if(null == submap) {
        submap = new HashMap<>();
        map.put(obj.getClass(), submap);
    }
    submap.put(id, obj);
}

public Superclass get(Class clss, String id) {
    return Optional.ofNullable(map)
            .map(m -> m.get(clss))
            .map(m2 -> m2.get(id))
            .orElse(null);
}

答案 1 :(得分:1)

我认为解决此问题的最佳方法是创建两种更新方法。一个用于Subclass1,一个用于Subclass2。原因很简单,更好的是有两个单一的方法做一件事,而不是一个带布尔参数的方法做两件事。

这段代码看起来不错,而且更容易测试。

public void update1(final List<Subclass1> updates) {
    updates.stream().forEach((entity) -> {
        mapToSubclass1.put(entity.getId(), entity);
    });
}

public void update2(final List<Subclass2> updates) {
    updates.stream().forEach((entity) -> {
        mapToSubclass2.put(entity.getId(), entity);
    });
}

答案 2 :(得分:1)

Check this

正常继承在泛型中不起作用。因此,5并未从Map<String, Subclass1>延伸。

您的选择是明确地投射对象

Map<String, SuperClass>

答案 3 :(得分:1)

关于继承,你的方法没有多大意义。您有两个单独的子类映射,并希望在其中一个中添加超类实例。我建议考虑一种更合适的方法来处理这个用例。

但是,如果你想保持原样,那就可以了:

public void update(
            final boolean updatesAreSubclass1,
            final List<? extends Superclass> updates) {
  updates.stream().forEach((entity) -> {
    if(updatesAreSubclass1)
      mapToSubclass1.put(entity.getId(), (Subclass1) entity);
    else
      mapToSubclass2.put(entity.getId(), (Subclass2) entity);
    });
}

如果没有显式强制转换,则无法将Superclass个对象存储在为子类定义的映射中。这应该会让您认为您的实施可能存在问题。

答案 4 :(得分:1)

你不能这样做:

mapToUpdate = mapToSubclass1;

因为那时你的代码可以继续向Subclass1添加非mapToUpdate对象,并且编译器将无法标记它(即,它将无法提供类型安全性)

解决这个问题的一种方法是告诉编译器“我知道我在做什么”,而不是对mapToUpdate变量使用泛型。像这样:

@SuppressWarnings("unchecked")
public void update(final boolean updatesAreSubclass1,
        final List<? extends Superclass> updates) {

    if (updates.size() == 0) {
        return;
    }

    Map mapToUpdate;
    if (updatesAreSubclass1) {
        mapToUpdate = Collections.checkedMap(mapToSubclass1, Integer.class,
                Subclass1.class);
    } else {
        mapToUpdate = Collections.checkedMap(mapToSubclass2, Integer.class,
                Subclass2.class);
    }

    updates.stream().forEach(
            (entity) -> {
                System.out.println("Adding..." + entity.toString()
                        + " to map " + mapToUpdate.toString());
                mapToUpdate.put(entity.getId(), entity);
            });
}

需要注意的是,您确实需要知道自己在做什么,因为如果您使用update = updatesAreSubclass1致电true,那么该列表实际上并不是{ {1}}对象,您将在运行时获得Subclass1

注意:您将获得ClassCastException因为我们正在使用ClassCastException。如果你把它排除在外,你就不会得到例外情况,但你的Collections.checkedMap地图中会有Subclass2个对象 - 更糟糕的是,对吗?