Общий параметр, если raw по умолчанию имеет значение Object - PullRequest
3 голосов
/ 20 июня 2019

Я работаю над проектом на основе Java 7. Я хотел ввести универсальный параметр для одного из классов, чтобы я мог медленно исключать приведение классов, необходимое для работы кода.

Давайте представим класс, похожий на тот, над которым я работаю:

public class A {
   public List<B> getB() { ... }
}

B имеет много дочерних классов, которые при использовании необходимо приводить, что, очевидно, не идеально. Модификация, которую я хотел бы сделать, выглядит так:

public class A<T extends B> {
    public List<T> getB() {...}
}

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

Я надеялся, что использование необработанного класса A сделает так, что getB() вернет тип B. Реальность такова, что если использовать raw, getB() вернет тип Object. Это какое-то поведение, связанное с Java 7, или я что-то упускаю? Есть ли способ заставить getB() возвращать тип B, когда A является необработанным? Похоже, я не сталкивался с этой проблемой в Java 8, хотя на самом деле я тоже не работал над проектом, который плохо структурирован.

Изменить: В комментариях меня попросили конкретный пример кода, где возникает проблема. Предполагая вышеупомянутые классы (отредактированные немного, чтобы лучше соответствовать моему фактическому сценарию):

A a = new A();
for(B b: a.getB()) { // Compiler error, because `getB()` returns a list of `Object`s instead of `B`s.
    ...
}

A<B> a = new A<B>();
for(B b: a.getB()) { // Everything is fine here obviously
    ...
}

Ответы [ 2 ]

1 голос
/ 10 июля 2019

Похоже, ваше намерение состоит в том, что, предполагая class C extends B, вы можете иметь A<C>, чей метод getB() возвращает List<C>. Но если вы позволите старому коду рассматривать метод того же объекта как возвращающий List<B>, он может вызвать для него getB().add(new B()), что подорвет весь тип безопасности.

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

Поскольку вы все равно должны прикоснуться к коду, чтобы воспользоваться новым универсальным типом, вы можете расширить класс, например,

public class A {
    public List<B> getB() { ... }
}

public class ExtendedA<T extends B> extends A {
    public List<B> getB() {
        return Collections.unmodifiableList(getT());
    }
    public List<T> getT() { ... }
}

Преимущество нового параметра типа T можно получить только для нового или обновленного кода, когда ExtendedA создается и последовательно проходит через весь путь кода. Но в любой момент, когда ExtendedA передается старому коду, он продолжает работать как прежде, если старый код подчиняется ограничениям, упомянутым выше. Код не должен пытаться добавить B экземпляров, если не знает фактический параметр типа. Но в этом решении ограничение применяется во время выполнения оболочкой unmodifiableList, поэтому безопасность универсального типа сохраняется, что является причиной того, что подпись метода оболочки допускает переход типа с List<T> на List<B>.

Лучшей стратегией миграции будет адаптация всех сайтов создания A s как можно скорее, так как тогда класс A может стать абстрактным, просто служа интерфейсом для старого кода, тогда как ExtendedA является актуальный тип безопасного API и реализация для всего нового и обновленного кода.

1 голос
/ 20 июня 2019

Проблема в том, что если вы используете необработанные типы, тип также удаляется из List, и вы получаете необработанный тип List из getB(). Поскольку цикл forEach не разрешает непроверенные преобразования типов, вы получаете ошибку компиляции. С оговоркой о том, что необработанных типов лучше избегать , простой обходной путь в вашем случае - использовать временную переменную для преобразования типов:

A a = new A();
List<B> list = a.getB();
for( B b : list ) {
    System.out.println(b);
}

Или эмулировать расширенный цикл for , и в этом случае непроверенное преобразование типов будет разрешено в заголовке цикла:

for (Iterator<B> i = a.getB().iterator(); i.hasNext();) {
    System.out.println(i.next());
}
...