具有可选多个边界的泛型,例如列表<?扩展整数或字符串>
Generics with optional multiple bounds, e.g. List<? extends Integer OR String>
我有一个方法应该只接受一个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
并在运行时验证 Integer
或 String
之外,最好的替代方法是什么?
void setProperties(Map<String, ?> properties)
此方法接受一组属性来配置底层服务实体。该实体仅支持 属性 类型 String
和 Integer
的值。比如一个属性maxLength=2
是有效的,defaultTimezone=UTC
也是有效的,但是allowDuplicate=false
是无效的。
由于 Integer
和 String
在 class 层次结构中最接近的共同祖先是 Object
你无法实现你想要做的 - 你可以帮助编译器缩小类型仅 Object
。
您可以
- 将您的值包装到 class 中,其中可以包含
Integer
或 String
,或
扩展Map
如@RC的回答,或
在 class
中包装 2 Map
s
定义两个重载:
void setIntegerProperties(Map<String, Integer> properties)
void setStringProperties(Map<String, String> properties)
它们必须被称为不同的东西,因为你不能有两种具有相同擦除的方法。
我敢肯定,如果有任何语言会禁止一个值接受多种接受类型,那将是 Java。如果您真的需要这种功能,我建议您研究其他语言。 Python一定能做到
同时使用整数和字符串作为地图值的用例是什么?如果我们真的只处理整数和字符串,您将不得不:
- 定义一个可以容纳字符串或整数的包装器对象。不过我建议不要这样做,因为它看起来很像下面的其他解决方案。
- 选择 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
实现并覆盖 put
和 putAll
方法来验证数据:
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")));
因为只有 int
和 String
这两个 Value
工厂方法,所以不能将其他类型传递给映射。请注意,这也允许使用 int
作为参数类型,因此在这里可以将 byte
、short
等扩展为 int
。
我有一个方法应该只接受一个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
并在运行时验证 Integer
或 String
之外,最好的替代方法是什么?
void setProperties(Map<String, ?> properties)
此方法接受一组属性来配置底层服务实体。该实体仅支持 属性 类型 String
和 Integer
的值。比如一个属性maxLength=2
是有效的,defaultTimezone=UTC
也是有效的,但是allowDuplicate=false
是无效的。
由于 Integer
和 String
在 class 层次结构中最接近的共同祖先是 Object
你无法实现你想要做的 - 你可以帮助编译器缩小类型仅 Object
。
您可以
- 将您的值包装到 class 中,其中可以包含
Integer
或String
,或 扩展
Map
如@RC的回答,或在 class
中包装 2
Map
s
定义两个重载:
void setIntegerProperties(Map<String, Integer> properties)
void setStringProperties(Map<String, String> properties)
它们必须被称为不同的东西,因为你不能有两种具有相同擦除的方法。
我敢肯定,如果有任何语言会禁止一个值接受多种接受类型,那将是 Java。如果您真的需要这种功能,我建议您研究其他语言。 Python一定能做到
同时使用整数和字符串作为地图值的用例是什么?如果我们真的只处理整数和字符串,您将不得不:
- 定义一个可以容纳字符串或整数的包装器对象。不过我建议不要这样做,因为它看起来很像下面的其他解决方案。
- 选择 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
实现并覆盖 put
和 putAll
方法来验证数据:
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")));
因为只有 int
和 String
这两个 Value
工厂方法,所以不能将其他类型传递给映射。请注意,这也允许使用 int
作为参数类型,因此在这里可以将 byte
、short
等扩展为 int
。