具有可选多边界的泛型,例如名单<! - ?扩展Integer OR String - >

时间:2017-07-04 10:56:07

标签: java generics unbounded-wildcard

我的方法应该只接受Map,其键的类型为String,值为Integer String,但,比如,Boolean

例如,

map.put("prop1", 1); // allowed
map.put("prop2", "value"); // allowed
map.put("prop3", true); // compile time error

无法按以下方式声明Map(以强制执行编译时检查)。

void setProperties(Map<String, ? extends Integer || String> properties)

除了将值类型声明为unbounded wildcard并在运行时验证IntegerString之外,最佳选择是什么?

void setProperties(Map<String, ?> properties)

此方法接受一组属性以配置基础服务实体。该实体仅支持StringInteger类型的属性值。例如,属性maxLength=2有效,defaultTimezone=UTC也有效,但allowDuplicate=false无效。

5 个答案:

答案 0 :(得分:5)

另一种解决方案是自定义Map实施,并覆盖putputAll方法来验证数据:

public class ValidatedMap extends HashMap<String, Object> {
    @Override
    public Object put(final String key, final Object value) {
        validate(value);
        return super.put(key, value);
    }

    @Override
    public void putAll(final Map<? extends String, ?> m) {
        m.values().forEach(v -> validate(v));
        super.putAll(m);
    }

    private void validate(final Object value) {
        if (value instanceof String || value instanceof Integer) {
            // OK
        } else {
            // TODO: use some custom exception
            throw new RuntimeException("Illegal value type");
        } 
    }
}

注意:使用符合您需求的Map实现作为基类

答案 1 :(得分:3)

由于类层次结构中IntegerString最接近的共同祖先是Object,你无法实现你想要做的事情 - 你可以帮助编译器将类型缩小到{{1只有。

你可以

  • 将您的值包装到一个类中,该类可以包含ObjectInteger,或
  • 延长{@ 1}},如@ RC的答案,或

  • 在课程中包装2 String

答案 2 :(得分:2)

定义两个重载:

void setIntegerProperties(Map<String, Integer> properties)

void setStringProperties(Map<String, String> properties)

它们必须被称为不同的东西,因为你不能有两种方法具有相同的擦除。

答案 3 :(得分:1)

我相当肯定,如果任何语言不允许多个接受类型的值,它将是Java。如果你真的需要这种能力,我建议你研究其他语言。 Python绝对可以做到。

将整数和字符串作为地图值的用例是什么?如果我们真的只处理整数和字符串,那么您将不得不:

  1. 定义一个可以包含String或Integer的包装器对象。我会建议不要这样做,因为它看起来很像下面的其他解决方案。
  2. 选择String或Integer作为值(String似乎更容易选择),然后在地图之外做额外的工作以使用这两种数据类型。
  3. Map<String, String> map;
    Integer myValue = 5;
    if (myValue instanceof Integer) {
        String temp = myValue.toString();
        map.put(key, temp);
    }
    
    // taking things out of the map requires more delicate care.
    try { // parseInt() can throw a NumberFormatException
        Integer result = Integer.parseInt(map.get(key)); 
    }
    catch (NumberFormatException e) {} // do something here
    

    这是一个非常难看的解决方案,但它可能是使用Java提供的唯一合理解决方案之一,以保持对您的值的强烈打字感。

答案 4 :(得分:1)

您不能将类型变量声明为两种类型之一。但是您可以创建一个帮助程序类来封装没有公共构造函数的值,而不是专用类型的工厂方法:

public static final class Value {
    private final Object value;
    private Value(Object o) { value=o; }
}
public static Value value(int i) {
    // you could verify the range here
    return new Value(i);
}
public static Value value(String s) {
    // could reject null or invalid string contents here
    return new Value(s);
}
// these helper methods may be superseded by Java 9’s Map.of(...) methods
public static <K,V> Map<K,V> map(K k, V v) { return Collections.singletonMap(k, v); }
public static <K,V> Map<K,V> map(K k1, V v1, K k2, V v2) {
    final HashMap<K, V> m = new HashMap<>();
    m.put(k1, v1);
    m.put(k2, v2);
    return m;
}
public static <K,V> Map<K,V> map(K k1, V v1, K k2, V v2, K k3, V v3) {
    final Map<K, V> m = map(k1, v1, k2, v2);
    m.put(k3, v3);
    return m;
}
public void setProperties(Map<String, Value> properties) {
    Map<String,Object> actual;
    if(properties.isEmpty()) actual = Collections.emptyMap();
    else {
        actual = new HashMap<>(properties.size());
        for(Map.Entry<String, Value> e: properties.entrySet())
            actual.put(e.getKey(), e.getValue().value);
    }
    // proceed with actual map

}

如果您使用带有地图构建器的第三方库,则不需要map方法,它们仅适用于短地图。使用此模式,您可以调用类似

的方法
setProperties(map("mapLength", value(2), "timezone", value("UTC")));

由于Valueint只有两个String工厂方法,因此不能将其他类型传递给地图。请注意,这也允许使用int作为参数类型,因此可以将byteshort等扩展为int