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.