具有可选多个边界的泛型,例如列表<?扩展整数或字符串>

Generics with optional multiple bounds, e.g. List<? extends Integer OR String>

我有一个方法应该只接受一个Map,它的键是String类型,值是IntegerString,但 不是 ,比如说 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是无效的。

由于 IntegerString 在 class 层次结构中最接近的共同祖先是 Object 你无法实现你想要做的 - 你可以帮助编译器缩小类型仅 Object

您可以

  • 将您的值包装到 class 中,其中可以包含 IntegerString,或
  • 扩展Map如@RC的回答,或

  • 在 class

  • 中包装 2 Maps

定义两个重载:

void setIntegerProperties(Map<String, Integer> properties)

void setStringProperties(Map<String, String> properties)

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

我敢肯定,如果有任何语言会禁止一个值接受多种接受类型,那将是 Java。如果您真的需要这种功能,我建议您研究其他语言。 Python一定能做到

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

  1. 定义一个可以容纳字符串或整数的包装器对象。不过我建议不要这样做,因为它看起来很像下面的其他解决方案。
  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 可以提供的唯一合理的解决方案之一,可以为您的值保持某种强类型感。

另一种解决方案是自定义 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 实现作为基础 class

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

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")));

因为只有 intString 这两个 Value 工厂方法,所以不能将其他类型传递给映射。请注意,这也允许使用 int 作为参数类型,因此在这里可以将 byteshort 等扩展为 int