Обобщения Java - возвращение подтипа объявленного типа из метода - PullRequest
6 голосов
/ 22 октября 2008

Мой класс реализует метод суперкласса, который возвращает List<JComponent>. Возвращаемый список доступен только для чтения:

public abstract class SuperClass {
    public abstract List<JComponent> getComponents();
}

В моем классе я хочу вернуть поле, которое объявлено как List - то есть подсписок:

public class SubClass extends SuperClass {
    private List<JButton> buttons;
    public List<JComponent> getComponents() {
        return buttons;
    }
}

Это приводит к ошибке компилятора, так как List<JButton> не является подтипом List<JComponent>.

Я могу понять, почему он не компилируется, так как нельзя допускать добавление JTextField в список JButtons.

Однако, поскольку список только для чтения, «концептуально» это должно быть разрешено. Но, разумеется, компилятор не знает, что он доступен только для чтения.

Есть ли способ достичь того, чего я хочу достичь, не изменяя объявление метода в суперклассе и объявление поля в подклассе?

Спасибо, Calum

Ответы [ 8 ]

9 голосов
/ 22 октября 2008

Объявить getComponents() как:

public List<? extends JComponent> getComponents()
6 голосов
/ 22 октября 2008

Я думаю, что нашел ответ, который я ищу ...

return Collections.<JComponent>unmodifiableList(buttons);

Я ранее пробовал:

return Collections.unmodifiableList(buttons);

но это было связано с типом List<JButton>.

Установка в явном параметре типа позволяет List<JButton> трактоваться как List<JComponent>, что разрешено unmodifiableList().

Теперь я счастлив ;-), и я кое-что узнал благодаря обсуждению.

Calum

4 голосов
/ 22 октября 2008

Не думаю, что вы можете делать то, что хотите, не изменяя ни сигнатуру метода суперкласса, ни объявление списка подклассов. Суперкласс жестко определяет тип возвращаемого значения для JComponent. Нет никакого способа сделать ваш тип возврата чем-либо кроме JComponent.

Если бы это было только для чтения, я бы сделал что-то вроде:

public class SubClass extends SuperClass {
    private List<JComponent> buttons = new ArrayList<JComponent>();
    public void addButton(JButton button) {
        buttons.add(button);
    }
    public List<JComponent> getComponents() {
        return Collections.unmodifiableList(buttons);
    }
}

Если вы можете изменить суперкласс, вы можете попробовать что-то вроде:

public abstract class SuperClass<E extends JComponent> {
    public abstract List<E> getComponents();
}

public class SubClass extends SuperClass<JButton> {
    private List<JButton> buttons = new ArrayList<JButton>();
    public List<JButton> getComponents() {
        return Collections.unmodifiableList(buttons);
    }
}
1 голос
/ 19 марта 2009

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

0 голосов
/ 22 октября 2008

Весь смысл обобщений заключается в обеспечении безопасности типов во время компиляции для таких вещей, как «тип» коллекции. К сожалению для вас, List<JButton> не является подтипом List<JComponent>. Причина этого проста и заключается в следующем:

List<JComponent> jc;
List<JButton> jb = new ArrayList<JButton>();
//if List<JButton> was a sublcass of List<JComponent> then this is a valid assignment
jc = jb;
jc.add(new JCheckBox()); //valid, as jc accepts any JComponent

JButton b = jb.get(0); //this will throw a ClassCastException, rendering Generic type-safety pointless

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

Я думаю, что это хорошее общее правило - быть очень осторожным при разработке универсального класса. Вам действительно нужны дженерики? Как это привыкнет? Некоторые общие проекты заканчиваются ужасными структурами, и пользователям приходится возвращаться к версии raw . Механизм фильтрации JTable является отличным примером: когда вы хотите реализовать цепочку фильтров (что, конечно, будет!), Все это рушится. Дело в том; в этом случае генерики были добавлены по цене и практически без выгоды.

0 голосов
/ 22 октября 2008

Вы можете выполнить приведение с @SuppressWarnings. Я считаю, что это будет уместно в этом случае, просто убедитесь, что вы указали причину в комментарии.

Либо выполните следующее:

public List<JComponent> getComponents()
{
  return new ArrayList<JComponent>( buttons );
}

Да, я знаю, что это делает копию, и список уже доступен только для чтения. Но пока профилировщик не скажет вам иначе, я бы предположил, что штраф небольшой.

@ Calum: Я согласен с тем, что использование? -Выражений в типах возвращаемых данных является неправильной формой, поскольку вызывающий код не может сделать это, например:

List<JComponent> list = obj.getComponents();
0 голосов
/ 22 октября 2008

Хм, не уверен

Обычный способ настроить это - выглядеть как супер

public abstract class SuperClass {
    public abstract List<? extends JComponent> getComponents();
}

Не уверен, как это сделать, не меняя это.

0 голосов
/ 22 октября 2008

Для этого вам нужно расширить дженерики. :)

public abstract class SuperClass {
    public abstract List<? extends JComponent> getComponents();
}


public class SubClass extends SuperClass {
    private List<JButton> buttons;
    public List<? extends JComponent> getComponents() {
        return buttons;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...