Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

Edited:" I received a very pertinent answer from 'erickson', but there is a side problem (up-casting?) that was not explicitly covered in my original example and is not solved with his answer. I've extended the example to cover this other problem, and I've included it at the end of this post. Thanks for your help.

I'm currently facing a problem with Java generics that is related to something that has been called the "Curiously Recurring Generic Pattern". I thought I had found the solution after reading the answer from Jon Skeet to this question "java enum definition". Nevertheless, I found myself with different problems when I tried to apply it in my code.

I've come up with a 'small' example where the problem I'm facing appears. I hope it will be clear enough to illustrate my questions.

Description of the example: I want to build a graph where node types can vary. I've defined an abstract class Node, which defines some basic methods, and a concrete class that implements those methods, namely ConcreteNode. I've also created a specialization of ConcreteNode called City.

In a given graph, an important requirement is that all the elements should be made of the same types or subtypes of it, i.e. a graph of ConcreteNode can have only ConcreteNodes or Cities.

These are the definitions of my classes:

abstract class Node<T extends Node<T>>
class ConcreteNode<T extends ConcreteNode<T>> extends Node<T>
class City extends ConcreteNode<City>

These definitions make use the 'Recurring Generic Pattern' also found in the definition of the Enum class:

Class Enum<E extends Enum<E>>

Questions: I'm having problem using these classes. I don't have problems if I have to stay at the City level in the hierarchy, i.e. connecting City to City, but I'm having huge problems when trying to access other classes.

In the following code, my problems can be seen in the signature of the methods of GraphUtil:

  1. addNewNeighbors1a uses the raw type Node, but at least it works.
  2. addNewNeighbors1b uses the type Node, but it doesn't compile at all (the error is included the code).
  3. addNewNeighbors1c uses a more complex parameter for Node, that I expected to work, but it doesn't compile (the error is included the code).
  4. addNewNeighbors3 uses complex parameters for Node, but it doesn't compile again, even though the parameters are the same for node and newNode.

In synthesis, my question is how to upcast these generic types that are parametrized on themselves?.

I will be really glad to get help with the best signature for the methods of GraphUtil, assuming that these methods are going to be located in a library that doesn't know anything about City or even ConcreteNode.

Thank you all.

Here's the full code of the example

package test.city;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class TestCity {
    abstract class Node<T extends Node<T>> {
    public abstract void addNeighbor(T n);
    public abstract void addNeighbors(Collection<? extends T> nodes);
    public abstract Collection<T> neighbors();
    }

    class ConcreteNode<T extends ConcreteNode<T>> extends Node<T> {
    protected Collection<T> _neighbors = new ArrayList<T>();

    @Override
    public void addNeighbor(T n) {
        _neighbors.add(n);
    }

    @Override
    public void addNeighbors(Collection<? extends T> nodes) {
        _neighbors.addAll(nodes);
    }

    @Override
    public Collection<T> neighbors() {
        return _neighbors;
    }
    }

    class City extends ConcreteNode<City> {
    protected String _name;

    public City(String name) {
        _name = name;
    }

    @Override
    public String toString() {
        return _name;
    }
    }

    public TestCity() {
    City nyc = new City("NYC");
    nyc.addNeighbor(new City("Boston"));
    nyc.addNeighbor(new City("Wash"));

    GraphUtil.print("Printing cities", nyc.neighbors());

    GraphUtil.printNeighbors1(nyc);
    GraphUtil.printNeighbors2(nyc);
    GraphUtil.printNeighbors3(nyc);
    GraphUtil.printNeighbors4(nyc);
    GraphUtil.addNewNeighbors1a(nyc, new City("Miami"));
    GraphUtil.addNewNeighbors2(nyc, new City("NewOr"));
    GraphUtil.addNewNeighbors3(nyc, new City("Dallas"));
    }

    static class GraphUtil {
    static void printNeighbors1(Node<?> node) {
        print("Nodes", node.neighbors());
    }

    static void printNeighbors2(ConcreteNode<?> node) {
        print("Concrete nodes", node.neighbors());
    }

    static void printNeighbors3(Node<? extends Node<?>> node) {
        print("Nodes2", node.neighbors());
    }

    static void printNeighbors4(ConcreteNode<? extends ConcreteNode<?>> node) {
        print("Concrete nodes2", node.neighbors());
    }

    static void addNewNeighbors1a(Node node, City newNode) {
        node.addNeighbor(newNode);
        print("Add city to node", node.neighbors());
    }

    static void addNewNeighbors1b(Node<?> node, City newNode) {
        // node.addNeighbor( newNode ); <---- DOES NOT COMPILE!!!
        // The method addNeighbor(capture#8-of ?) in the type
        // TestCity.Node<capture#8-of ?>
        // is not applicable for the arguments (TestCity.City)
    }

    static void addNewNeighbors1c(Node<? extends Node<?>> node, City newNode) {
        // node.addNeighbor( newNode ); <---- DOES NOT COMPILE!!!
        // The method addNeighbor(capture#9-of ? extends TestCity.Node<?>)
        // in the type
        // TestCity.Node<capture#9-of ? extends TestCity.Node<?>> is not
        // applicable for the arguments (TestCity.City)

    }

    static void addNewNeighbors2(Node node, ConcreteNode newNode) {
        node.addNeighbor(newNode);
        print("Add concrete node to node", node.neighbors());
    }

    static void addNewNeighbors3(Node<? extends Node<?>> node,
        Node<? extends Node<?>> newNode) {
        // node.addNeighbor( newNode ); <---- DOES NOT COMPILE!!!
        // The method addNeighbor(capture#8-of ? extends TestCity.Node<?>)
        // in the type
        // TestCity.Node<capture#8-of ? extends TestCity.Node<?>> is not
        // applicable for the arguments
        // (TestCity.Node<capture#10-of ? extends TestCity.Node<?>>)
    }

    static void print(String msg, Collection<?> col) {
        System.out.println(msg + ": " + Arrays.toString(col.toArray()));
    }
    }

    public static void main(String[] args) {
    new TestCity();
    }

}

The output of running this code is the following (no surprises at all):

Printing cities: [Boston, Wash]
Nodes: [Boston, Wash]
Concrete nodes: [Boston, Wash]
Nodes2: [Boston, Wash]
Concrete nodes2: [Boston, Wash]
Add city to node: [Boston, Wash, Miami]
Add concrete node to node: [Boston, Wash, Miami, NewOr]

Second part of the problem

There is a related problem that I had not included in the original example because I thought the solution was also going to apply.

I've now added the following method to GraphUtil:

static <T extends Node<T>> T getSomeNeighbor(T node) {
    return node.neighbors().iterator().next();
}

And from my main class I'm trying the following:

City someCity = GraphUtil.getSomeNeighbor(nyc); 
someCity.addNeighbor(new City("London")); // OK

ConcreteNode someCN1 = GraphUtil.getSomeNeighbor(nyc); 
someCN1.addNeighbor(new City("Paris")); // OK, but raw

ConcreteNode<?> someCN2 = GraphUtil.getSomeNeighbor(nyc); 
someCN2.addNeighbor(new City("Berlin")); // Does not compile

ConcreteNode<?> nc = new City("");
nc.addNeighbor(new City("Bern")); // Does not compile

The first case works, because I know the concrete type that is returned, and it is coherent with the type provided in the parameter.

In the second and third cases I'm assuming I don't know the type City. The second case works, but I'm using the raw type ConcreteNode.

In the third case, there is a compilation error in the second line: "The method addNeighbor(capture#3-of ?) in the type TestCity.ConcreteNode is not applicable for the arguments (TestCity.City)."

In the example I'm using 'new City("-")' as a parameter because I don't know how to up-cast them. In the fourth case I tried to up-cast City to ConcreteNode, but it failed. The current compiler error is the following: "The method addNeighbor(capture#4-of ?) in the type TestCity.ConcreteNode is not applicable for the arguments (TestCity.City)"

Questions:

  1. How can I fix cases 2 and 3 without knowing the type City?
  2. How can up-cast City to ConcreteNode (or to Node)?

Thanks for your help.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
91 views
Welcome To Ask or Share your Answers For Others

1 Answer

You can make generic methods as well as generic types. Using these, the problem methods in GraphUtils can be fixed like this:

static <T extends Node<T>> void addNewNeighbors1a(T node, T newNode)
{
  node.addNeighbor(newNode);
  print("Add city to node", node.neighbors());
}

static <T extends Node<T>> void addNewNeighbors2(T node, T newNode)
{
  node.addNeighbor(newNode);
  print("Add concrete node to node", node.neighbors());
}

Hey, wait a second… those are the same methods!

It turns out that, since they only depend on the interface of Node, you only need one of them to handle any Node implementation.

Down the road, you might find it necessary to change the Node interface like this:

public abstract <S extends T> void addNeighbor(S n);

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...