Редизайн вокруг непроверенных кастовых предупреждений
У меня есть класс будет содержать несколько различных реализаций парсера для разных объектов. Хотя я могу хранить реализации парсера без каких-либо предупреждений, получение парсера с карты предупреждает о непроверенном исключении приведения. Ниже приведен упрощенный отрывок:
private Map<Class<?>, Parser<?>> parsers = new HashMap<>();
public <T> void addParser(Class<T> type, Parser<T> parser) {
parsers.put(type, parser);
}
private <T> Parser<T> parserFor(Class<T> type) {
// Compiler complains about unchecked cast below
return (Parser<T>) parsers.get(type);
}
Есть ли другой способ реализовать подобную логику, не вызывая непроверенное предупреждение о приведении?
4 ответа
Там нет никакого способа создать Map<Class<...>, Parser<...>>
где ...
-s может быть любым, но должно совпадать между ключом и его значением; так что вы не можете заставить компилятор выполнить проверку за вас, где Class<T>
гарантированно даст вам Parser<T>
, Тем не менее, ваш код сам по себе правильный; вы знаете, что ваше приведение правильное, даже если компилятор - нет.
Итак, когда вы знаете, что ваше приведение верное, но Java этого не знает, что вы можете сделать?
Лучший и самый безопасный подход - создать как можно меньшую часть вашего кода, которая отвечает за обработку перевода между проверенной и непроверенной логикой и за то, чтобы непроверенная логика не вызывала ошибок. Затем вы просто пометите этот код с соответствующим @SuppressWarnings
аннотаций. Например, вы можете получить что-то вроде этого:
public abstract class Parser<T> {
private final Class<T> mType;
protected Parser(final Class<T> type) {
this.mType = type;
}
public final Class<T> getType() {
return mType;
}
@SuppressWarnings("unchecked")
public final <U> Parser<U> castToParserOf(final Class<U> type) {
if (type == mType) {
return (Parser<U>) this;
} else {
throw new ClassCastException("... useful message ...");
}
}
}
Это позволит вам безопасно написать в вашем примере:
public <T> void addParser(final Parser<T> parser) {
parsers.put(parser.getType(), parser);
}
private <T> Parser<T> parserFor(final Class<T> type) {
return parsers.get(type).castToParserOf(type);
}
Рассмотрите возможность использования TypeToInstanceMap<Parser<?>>
из Google Guava. Это позволит вам делать такие вещи без каких-либо предупреждений или ошибок компилятора:
TypeToInstanceMap<Parser<?>> parsers;
parsers.put(new TypeToken<Parser<String>>(){},
makeStringParser());
Parser<Integer> intParser = parsers.get(new TypeToken<Parser<Integer>>(){});
По сути, это библиотека, которая делает что-то очень похожее на ответ @ ruakh.
Разработчик, который добавил <T>
в Class<T>
Нил Гафтер (Neil Gafter) обсудил фундаментальную проблему в своем блоге вскоре после выхода Java 5. Он звонит Class<T>
"токен типа" и говорит:
[Y] вы просто не можете сделать токен типа для универсального типа
... другими словами, вы не можете сделать Class<Parser<T>>
,
Так как ваша карта parsers
тип значения Parser<?>
и тип возврата вашего метода Parser<T>
очевидно, что забрасывать результат parsers.get(type)
в T
,
Одним из способов устранения ошибки компиляции является приведение типа к Parser<T>
:
private <T> Parser<T> parserFor(Class<T> type) {
return (Parser<T>)parsers.get(type);
}
Также вы можете изменить тип возвращаемого значения на Parser<?>
так как вы указали карту синтаксического анализатора как Map<Class<?>, Parser<?>>
, Это также очистит ошибку компиляции.
private <T> Parser<?> parserFor(Class<T> type) {
return parsers.get(type);
}
Или вы можете добавить параметр типа в свой класс упаковки.
public class YourClass<T> {
private Map<Class<T>, Parser<T>> parsers = new HashMap<>();
public void addParser(Class<T> type, Parser<T> parser) {
parsers.put(type, parser);
}
private Parser<T> parserFor(Class<T> type) {
return parsers.get(type);
}
}
Я не уверен, какой из них может быть правильно применен, однако, старайтесь не использовать приведение типов. Подумайте, почему мы используем дженерики.
Я получил это работать по-другому. Я сам экспериментирую с дженериками и был бы рад получить критику:)
Что я сделал, так это добавил интерфейс тегирования для Parseable объектов, а затем использовал его в качестве верхней границы Parser.
public interface IParseable {}
public class Parser<T extends IParseable> {
T paresableObj;
// do something with parseableObject here
}
И теперь Parser Factory не должен использовать подстановочные знаки и не использовать приведения.
public class ParserFactory {
private Map<Class<?>, Parser<? extends IParseable>> parsers = new HashMap<Class<?>, Parser<? extends IParseable>>();
public <T> void addParser(Class<T> type, Parser<? extends IParseable> parser) {
if(parserFor(type) == null){
parsers.put(type, parser);
}else{
//throw some excep
}
}
private <T> Parser<? extends IParseable> parserFor(Class<T> type) {
return parsers.get(type);
}
}