Правильный способ исправить захват захвата в Java - PullRequest
3 голосов
/ 07 февраля 2020

Имея иерархическую модель, которая использует наследование и обобщения в коллекции, у меня есть ошибка компиляции, связанная с преобразованием захвата :

CaptureConversion.java:16: error: incompatible types: NodeModel cannot be converted to CAP#1
    treeModel.getNodes().set(0, primaryNode);
                                ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends PublicNodeModel from capture of ? extends PublicNodeModel

Единственный способ, которым я смог обойти эту ошибку было с помощью следующего метода конвертации:

private static <T extends PublicNodeModel> T convert(PublicNodeModel node) {
    return (T) node;
}

Это преобразование выглядит для меня довольно глупо, поэтому мой вопрос , если есть какой-то "правильный" способ, как справиться с такого рода конверсией в Java ?


Ниже приведены модель и код, вызывающий эту ошибку (полный пример выполнимый на GitHub).

Упрощенная модель (например, нет получателей) / setters et c.):

class PublicTreeModel {
    List<? extends PublicNodeModel> nodes = new ArrayList<>();
}

class TreeModel extends PublicTreeModel {}

class PublicNodeModel {}

class NodeModel extends PublicNodeModel {}

Использование, вызывающее ошибку компиляции:

TreeModel treeModel = Fixture.createModel();
int index = Fixture.indexOfPrimaryNode(treeModel.getNodes());
NodeModel primaryNode = (NodeModel) treeModel.getNodes().get(index);

// following two lines won't compile
treeModel.getNodes().set(index, treeModel.getNodes().get(0));
treeModel.getNodes().set(0, primaryNode);

Использование с вышеупомянутым исправлением (метод convert()):

// this will compile & run
treeModel.getNodes().set(index, convert(treeModel.getNodes().get(0)));
treeModel.getNodes().set(0, convert(primaryNode));

Ответы [ 2 ]

4 голосов
/ 07 февраля 2020
class PublicTreeModel {
    List<? extends PublicNodeModel> nodes = new ArrayList<>();
}

Вы не должны иметь возможность помещать в этот список что-либо, кроме буквального null. Этот тип говорит: «это некоторый подкласс PublicNodeModel, я просто не знаю, какой»: поскольку компилятор не знает, какой подкласс разрешить, он не позволяет добавлять какие-либо элементы. Вы должны использовать только элементы из этого списка, а не предоставлять ему элементы.


В этом конкретном случае c вы фактически просто меняете элементы в списке, поэтому есть простое решение:

TreeModel treeModel = Fixture.createModel();
int index = Fixture.indexOfPrimaryNode(treeModel.getNodes());
Collections.swap(treeModel.getNodes(), 0, index);

То есть: даже если вы не знаете точный ожидаемый подтип элементов в treeModel.getNodes(), вы знаете, что элемент, который вы пытаетесь вставить обратно, был удален из списка; поэтому он безопасен от типа (или, по крайней мере, не менее безопасен от типа, чем он уже был).


В более общем случае есть два других решения: во-первых, удалите подстановочный знак, так что что список принимает любой подкласс PublicNodeModel:

class PublicTreeModel {
    List<PublicNodeModel> nodes = new ArrayList<>();
}

Это может быть неприемлемо, если вы действительно хотите полагаться на элементы nodes, которые являются конкретно NodeModel s.

Второй подход заключается в добавлении параметра типа в класс для типа элементов, которые будут храниться в списке:

class PublicTreeModel<T extends PublicNodeModel> {
    List<T> nodes = new ArrayList<>();
}

В контексте кода, который вы show, в котором есть ошибка компиляции, второй подход может выглядеть так:

class TreeModel extends PublicTreeModel<NodeModel> {}

TreeModel treeModel = Fixture.createModel();
int index = Fixture.indexOfPrimaryNode(treeModel.getNodes());

NodeModel primaryNode = treeModel.getNodes().get(index);

treeModel.getNodes().set(index, treeModel.getNodes().get(0));
treeModel.getNodes().set(0, primaryNode);
0 голосов
/ 07 февраля 2020

Если вы хотите форсировать ситуацию, одна идея состоит в том, чтобы сделать приведение:

 ( (List<PublicNodeModel>)treeModel.getNodes()).set(0, primaryNode);

по цене предупреждения unchecked or unsafe operations, но это, конечно, не рекомендуется. @ Ответ Энди Тернера - путь к go.

...