Java – The correct way to fix capture transforms in Java

The correct way to fix capture transforms in Java… here is a solution to the problem.

The correct way to fix capture transforms in Java

There is a hierarchical model that uses inheritance and generics in collections, and I have a capture conversion > with Related compilation error:

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

The only way I was able to overcome this error was through the following conversion method:

<pre class=”lang-java prettyprint-override”>private static <T extends PublicNodeModel> T convert(PublicNodeModel node) {
return (T) node;
}

This conversion seems silly to me, so my question is is some “right” way to handle this conversion in Java?


The model and code that causes this error is as follows (full runnable example)。

Simplification of the model (e.g. no getters/setters, etc.):

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

class TreeModel extends PublicTreeModel {}

class PublicNodeModel {}

class NodeModel extends PublicNodeModel {}

Usage that causes compilation errors:

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

Use the above fix (convert() method:

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

Solution

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

You shouldn’t be able to put anything into that list except the literal null. This type means “it’s some subclass of PublicNodeModel, I just don’t know which one”: because the compiler doesn’t know which subclass is allowed, it prevents you from adding any elements. You should only use the elements in this list, not provide them to them.


In this particular case, you’re really just swapping elements in the list, so there’s a simple solution:

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

That is, even if you don’t know the exact expected subtype of the element in treeModel.getNodes(), you know that the element you’re trying to put back has been taken out of list; So it’s type-safe (or, at least, no less type-safe than the previous type).


In a more general case, there are two other solutions: First, remove the wildcard so that the list accepts any subclasses of PublicNodeModel:

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

If you really want the element that depends on nodes to be a specific NodeModel, this may be Not Acceptable.

The second method is to add a type parameter to the class that is used to save the element type in the list:

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

In the context of the code you display with compilation errors, the second method might become:

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

Related Problems and Solutions