Rekurencyjne użycie ogólne

Edytowane: „ Otrzymałem bardzo trafną odpowiedź od „erickson”, ale istnieje problem uboczny (odlewanie?), Który nie został wyraźnie omówiony w moim oryginalnym przykładzie i nie został rozwiązany wraz z jego odpowiedzią. Rozszerzyłem przykład, aby objąć ten inny problem, i umieściłem go na końcu tego posta. Dzięki za pomoc.

Obecnie boryka się z problemem generics Java, który jest związany z czymś, co zostało nazwane„Ciekawie powtarzający się ogólny wzór”. Myślałem, że znalazłem rozwiązanie po przeczytaniu odpowiedzi Jona Skeeta na to pytanie„definicja java enum”. Niemniej jednak znalazłem się z różnymi problemami, gdy próbowałem zastosować go w moim kodzie.

Wymyśliłem „mały” przykład, w którym pojawia się problem, przed którym stoję. Mam nadzieję, że będzie wystarczająco jasne, aby zilustrować moje pytania.

Opis przykładu: Chcę zbudować wykres, w którym typy węzłów mogą się różnić. Zdefiniowałem klasę abstrakcyjnąWęzeł, który definiuje niektóre podstawowe metody i konkretną klasę, która implementuje te metody, a mianowicieConcreteNode. Stworzyłem także specjalizację ConcreteNode o nazwieMiasto.

W danym wykresie ważnym wymaganiem jest, aby wszystkie elementy były wykonane z tych samych typów lub podtypów, tj. Wykres ConcreteNode może mieć tylko ConcreteNodeslub Miasta.

Oto definicje moich zajęć:

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

Te definicje wykorzystują „Powtarzający się wzór ogólny”, który można znaleźć również w definicji klasy Enum:

Class Enum<E extends Enum<E>>

Pytania: Mam problem z używaniem tych klas. Nie mam problemów, jeśli muszę pozostać na poziomie miasta w hierarchii, tzn. Połączyć miasto z miastem, ale mamolbrzymi problemy podczas próby uzyskania dostępu do innych klas.

W poniższym kodzie moje problemy można zauważyć w podpisie metod GraphUtil:

addNewighbors1a używa węzła typu raw, ale przynajmniej działa.addNewighbors1b używa typu Węzeł, ale w ogóle się nie kompiluje (błąd jest zawarty w kodzie).addNewighbors1c używa bardziej złożonego parametru dla węzła, który powinien działać, ale nie kompiluje się (błąd jest zawarty w kodzie).addNewighbors3 używa złożonych parametrów dla węzła, ale nie kompiluje się ponownie, nawet jeśli parametry są takie same dla węzła i nowego węzła.

Podsumowując, moje pytanie brzmijak rozwinąć te ogólne typy, które są sparametryzowane na siebie?.

Będę bardzo zadowolony, że otrzymam pomoc z najlepszym podpisem dla metod GraphUtil, zakładając, że te metody będą zlokalizowane w bibliotece, która nie wie nic o City lub nawet ConcreteNode.

Dziękuję wam wszystkim.

Oto pełny kod przykładu

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

}

Wynik działania tego kodu jest następujący (bez żadnych niespodzianek):

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]

Druga część problemu

Istnieje pokrewny problem, którego nie uwzględniłem w oryginalnym przykładzie, ponieważ myślałem, że rozwiązanie będzie miało również zastosowanie.

Dodałem następującą metodę do GraphUtil:

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

Z mojej głównej klasy próbuję:

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

Pierwszy przypadek działa, ponieważ znam konkretny typ, który jest zwracany i jest spójny z typem podanym w parametrze.

W drugim i trzecim przypadku zakładam, że nie znam typu Miasto. Drugi przypadek działa, ale używam surowego typu ConcreteNode.

W trzecim przypadku występuje błąd kompilacji w drugim wierszu:„Metoda addNeighbor (przechwytywanie # 3-of?) W typie TestCity.ConcreteNode nie ma zastosowania dla argumentów (TestCity.City).”

W przykładzie używam „nowego miasta („ - ”)” jako parametru, ponieważ nie wiem, jak je ulepszyć. W czwartym przypadku próbowałem przenieść miasto na ConcreteNode, ale nie powiodło się. Aktualny błąd kompilatora jest następujący:„Metoda addNeighbor (przechwytywanie # 4-of?) W typie TestCity.ConcreteNode nie ma zastosowania dla argumentów (TestCity.City)”

Pytania:

Jak mogę naprawić przypadki 2 i 3 bez znajomości typu Miasto?W jaki sposób można przerzucić miasto na ConcreteNode (lub na węzeł)?

Dzięki za pomoc.

questionAnswers(4)

yourAnswerToTheQuestion