Редизайн вокруг непроверенных кастовых предупреждений

У меня есть класс будет содержать несколько различных реализаций парсера для разных объектов. Хотя я могу хранить реализации парсера без каких-либо предупреждений, получение парсера с карты предупреждает о непроверенном исключении приведения. Ниже приведен упрощенный отрывок:

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

}
Другие вопросы по тегам