Java Generics - это безопасное приведение без проверки? - PullRequest
5 голосов
/ 27 января 2011

У меня есть (еще один) непроверенный вопрос.Я на 90% уверен, что это безопасно, но я хочу убедиться (я оправдываю использование @SupressWarnings другому разработчику, который просматривает код)

Следующий шаблон был установленнаш фреймворк:

abstract class Writer<T> {
    Class<T> valueType;

    Writer(Class<T> valueType) {
        this.valueType = valueType;
    }
}
class Cat { }

class CatWriter extends Writer<Cat> {
    CatWriter() {
        super(Cat.class);
    }
}

Я также использую подкласс Writer для написания класса, который использует обобщения:

class Color {}
class Green extends Color {}
class Brown extends Color {}

Мой класс писателя выглядит так:

abstract class Bird<C extends Color> {}
class Parrot extends Bird<Green>{}
class Ostrich extends Bird<Brown>{}

class BirdWriter<C extends Color> extends Writer<Bird<C>> {
    BirdWriter(Bird<C> bird) {
        super((Class<Bird<C>>)bird.getClass());
    }
}

Я мог бы использовать необработанные типы в писателе, но это дает гораздо больше предупреждений.Вместо этого я включаю дженерики в класс Writer.Это хорошо везде, но конструктор.Я вынужден привести bird.getClass() (который является объектом класса, который не имеет общей подписи) к объекту класса с общей подписью. Это выдает неконтролируемое приведение предупреждение при приведении, , но я считаю, что можно безопасно преобразовать результат в Class<Bird<C>>, потому что bird, который передается в параметр, гарантированнобыть Bird<C>.

Тестирование подтверждает мою теорию, но я хочу убедиться, что мое мышление верно. Есть ли способ, которым этот код небезопасен?


Обновление

Спасибо за ваши ответы.Из-за ответов я понял, что в моей структуре была слабость, и пересмотрел ее.

По существу Cat использует простой Writer, который знает, что всегда пишет Cat.В моем случае, у меня есть тип «SmartAnimal», который может быть записан динамическим Writer, так что мне не нужно создавать Writer для каждого животного.

class SmartAnimal {}
class Dog extends SmartAnimal {}
class Horse extends SmartAnimal {}
class SuperHorse extends Horse {}

class DynamicWriter<A extends SmartAnimal> extends Writer<A> {
    DynamicWriter(A smartAnimal) {
        super((Class<A>)smartAnimal.getClass());
    }
}

СноваУ меня такое же предупреждение, но это кажется более безопасным.

Это лучше и безопаснее?

Ответы [ 3 ]

4 голосов
/ 27 января 2011

Вы не совсем правы. Правильное, безопасное, но все еще не проверенное заклинание - на Class<? extends Bird<C>>, поскольку птица, которую вы передаете, может, например, быть Совой, которая вернет Class<Owl>, который может быть назначен на Class<? extends Bird<C>>. Питер Лори прав, потому что это не проверено, но безопасно.

Обновление:

Нет, по той же причине все еще небезопасно. Вы все еще можете сделать что-то вроде new DynamicWriter<Horse>(new SuperHorse());, которое будет выполнять непроверенное приведение от Class<SuperHorse> до Class<Horse>. Чтобы быть в безопасности, вы должны привести к Class<? extends A>.

3 голосов
/ 27 января 2011

Это, безусловно, неправильно, по причине, указанной ILMTitan в его ответе . Но я дам вам причину, по которой это также может быть не очень безопасно. Представьте, что у вас есть метод verifyType(), который гарантирует, что все, что было передано для записи, имеет правильный тип:

public void write(T t) {
   if (!verifyType(t)) explode();
   //...do write
}

public boolean verifyType(T t) {
   return valueType.isInstance(t);
}

Этот метод, который вы ожидаете, никогда не потерпит неудачу, верно? В конце концов, вы просто гарантируете, что t является T (поскольку у вас есть Class<T>), и мы уже знаем, что является a T, потому что write() принимает T? Правильно? Неверно! Вы на самом деле не проверяете, является ли t T, но потенциально какой-то произвольный подтип T.

Изучите, что произойдет, если вы объявите Writer<Bird<Green>> и создадите его с помощью Parrot<Green>, для этого вызова будет законным :

Writer<Bird<Green>> writer;
writer.write(new SomeOtherGreenBird());

И снова вы не ожидаете, что это потерпит неудачу. Однако ваш тип значения предназначен только для Parrot, поэтому проверка типа не будет выполнена. Все зависит от того, что вы делаете в своем классе писателей, но будьте предупреждены: вы здесь шагаете в опасную воду. Объявление Class<? extends T> в вашем писателе не позволит вам совершить такую ​​ошибку.

1 голос
/ 27 января 2011

IMHO, это безопасно. Проблема в том, что getClass () возвращает класс объекта во время выполнения, но компилятор не понимает, что делает getClass (), и нет синтаксиса, который делает что-то вроде

Class<this> getClass();

возможно, потому что это единственный пример того, когда он будет использоваться.

...