Обобщения с необязательными множественными границами, например, List<? расширяет целочисленное или строковое значение>
У меня есть метод, который должен принимать только Map
чей ключ имеет тип String
и значение типа Integer
или же String
но не, скажем, Boolean
,
Например,
map.put("prop1", 1); // allowed
map.put("prop2", "value"); // allowed
map.put("prop3", true); // compile time error
Невозможно объявить карту, как показано ниже (для проверки времени компиляции).
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
является недействительным.
5 ответов
Вы не можете объявить переменную типа как один из двух типов. Но вы можете создать вспомогательный класс для инкапсуляции значений, не имеющих публичного конструктора, но фабричных методов для выделенных типов:
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")));
Так как есть только два Value
фабричные методы для int
а также String
ни один другой тип не может быть передан на карту. Обратите внимание, что это также позволяет использовать int
как тип параметра, так что расширение byte
, short
и т. д. int
здесь возможно
Другое решение будет на заказ 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");
}
}
}
NB: используйте Map
реализация, которая соответствует вашим потребностям в качестве базового класса
Поскольку Integer
а также String
ближайший общий предок в иерархии классов Object
вы не можете достичь того, что вы пытаетесь сделать - вы можете помочь компилятору сузить тип до Object
только.
Вы также можете
- оберните ваше значение в класс, который может содержать
Integer
или жеString
, или же простираться
Map
как в ответе @RC, илизавернуть 2
Map
с в классе
Определите две перегрузки:
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, чтобы поддерживать ощущение строгой типизации ваших значений.