0

我有一个方法,它应该只接受Map其键的类型String和值的类型Integer or 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除了将值类型声明为 an并在运行时验证Integer或在运行时验证之外,最好的选择是什么String

void setProperties(Map<String, ?> properties)

此方法接受一组属性来配置底层服务实体。String实体支持类型和Integer单独的属性值。例如,一个属性maxLength=2有效,defaultTimezone=UTC也有效,但allowDuplicate=false无效。

4

5 回答 5

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适合您需要的实现作为基类

于 2017-07-04T11:07:40.597 回答
3

由于类层次结构IntegerString最接近的共同祖先是Object您无法实现您想要做的事情 - 您可以帮助编译器将类型缩小为Objectonly。

你可以

  • 将您的值包装到一个可以包含Integeror String, or的类中
  • 扩展Map为@RC 的答案,或

  • 将 2 s 包装Map在一个类中

于 2017-07-04T10:58:59.983 回答
2

定义两个重载:

void setIntegerProperties(Map<String, Integer> properties)

void setStringProperties(Map<String, String> properties)

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

于 2017-07-04T11:00:52.133 回答
2

您不能将类型变量声明为两种类型中的任何一种。但是您可以创建一个辅助类来封装没有公共构造函数但具有专用类型的工厂方法的值:

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

}

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

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

由于and只有两个Value工厂方法,因此不能将其他类型传递给映射。请注意,这也允许使用as 参数类型,因此可以在此处扩展等。intStringintbyteshortint

于 2017-07-04T17:16:58.650 回答
1

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

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

  1. 定义一个可以容纳 String 或 Integer 的包装器对象。不过,我建议不要这样做,因为它看起来很像下面的其他解决方案。
  2. 选择 String 或 Integer 作为值(String 似乎是更容易的选择),然后在地图之外做额外的工作来处理这两种数据类型。
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 提供的合理解决方案之一,以保持对您的值的某种强类型感。

于 2017-07-04T11:07:31.700 回答