2015-02-10 5 views
3

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

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

Есть еще один способ реализовать подобную логику, не вызывая неконтролируемое предупреждение произнесения?

+0

Нет никакого способа сделать это без неконтролируемого приведения. Это нормально, пока вы не занимаетесь смешным делом. – Radiodef

ответ

1

Невозможно создать 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); 
} 
+0

Из-за любопытства, как подавление необработанного предупреждения должно быть лучше/безопаснее, чем подавление непроверенного предупреждения? – fayerth

+1

@fayerth: Это не так. Оба одинаково хороши - важная часть состоит в том, что вы просто максимально локализуете код, не проверенный по типу, чтобы вы могли быть уверены в его правильности. Мой пример закончил с '@SuppressWarnings (« raw »)', потому что вы не можете напрямую использовать его из 'Parser ' '' Parser '- это не компиляция * предупреждение *, а компиляция * * * поэтому мне нужен был промежуточный состав, а '(Parser)' был самым простым. (В противном случае мне нужно было бы написать * два * приведения, например, что-то вроде '(Parser ) (Parser ) this'.) – ruakh

+0

@fayerth: На самом деле, извините, коррекция: я только что протестировал в Eclipse и кастинг из' Parser '' 'Parser ' действительно работал нормально. Просто предупреждение без предупреждения. Поэтому я изменю свой ответ на это. – ruakh

1

Поскольку ваша карта 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); 
    } 
} 

Я не уверен, какой из них можно применить правильно, однако старайтесь не использовать литье типа. Подумайте, почему мы используем generic.

+1

Об ошибке компиляции, это была опечатка с моей стороны; Я соответствующим образом отредактировал вопрос. Для вашего предложения по возврату 'Parser ', я пробовал это и изначально думал, что это сработает. Тем не менее, я тогда понял, что вместо этого я получаю предупреждение без предупреждения, когда я использую 'parserFor()', что по существу означает, что предупреждение теперь находится совсем в другом месте. В идеале для самого класса не требуется параметр типа для выполнения его работы, поскольку он просто хочет получить Parser для определенного типа и использовать его. Будет ли еще какой-то способ избежать предупреждения? – fayerth

+0

Возвращая тип подстановочного знака, это тоже неограниченное, это плохая идея ИМО. 'Parser ' должно быть хорошо. –

1

Рассмотрите возможность использования TypeToInstanceMap<Parser<?>> у Google Guava. Это позволит вам делать такие вещи без каких-либо предупреждений компилятора или ошибок бы то ни было:

TypeToInstanceMap<Parser<?>> parsers; 

parsers.put(new TypeToken<Parser<String>>(){}, 
      makeStringParser()); 

Parser<Integer> intParser = parsers.get(new TypeToken<Parser<Integer>>(){}); 

Это, по существу, библиотека, которая делает что-то очень похожее на @ruakh's answer под капотом.

Разработчик, который добавил <T> в Class<T>, Neil Gafter, discussed the fundamental issue in his blog вскоре после того, как была выпущена Java 5. Он называет Class<T> «тип маркера», и говорит:

[Y] Ou просто не может сделать маркер типа для универсального типа

... Другими словами, вы можете» t сделать Class<Parser<T>>.

+0

Эта ссылка, которую вы поделили, безусловно, хорошо читается и немного помогла уточнить эту тему. Поскольку я ищу решение, которое не требует сторонней библиотеки, я решил принять ответ @ ruakh. Сказав это, я думаю, что это определенно очень хорошая альтернатива. – fayerth

0

я получил эту работу по-другому. Я сам экспериментирую с дженериками и буду рад получить критику :)

Что я сделал, это добавить интерфейс тегов для объектов Parseable, а затем использовать его как верхнюю границу в Parser.

public interface IParseable {} 

public class Parser<T extends IParseable> { 
    T paresableObj; 
    // do something with parseableObject here 
} 

И теперь фабрике Parser не нужно использовать подстановочные знаки и использовать отливки.

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

} 
+0

Это не помогает, потому что вы вынуждаете клиентов работать с 'IParseable' вместо подстановочного знака, который по умолчанию был бы' Object'. Однако может оказаться невозможным, чтобы все, с чем должен работать парсер, будет реализовывать этот интерфейс. Так что это в основном просто дополнительная нагрузка. – SpaceTrucker

+0

@SpaceTrucker - спасибо, ты прав. Он делает это. Я думал, что это больше внутренняя библиотека, и в этом случае не слишком сложно реализовать класс с тегом интерфейса. Также я думал, что это будет высокий порядок для анализа любого объекта, а объект, который является Parseable, должен иметь определенную характеристику.Не защищая ответ, просто объясняя мою мысль. Еще раз спасибо за ваши отзывы. – ramp