递归通用用法

编辑:“ 我从’erickson’那里得到了一个非常恰当的答案,但是有一个问题(向上?),这个问题没有在我的原始例子中明确涵盖,并且没有用他的答案解决。我已经将示例扩展到涵盖了这个问题,我已经把它包含在这篇文章的末尾。谢谢你的帮助。

我目前正面临Javagenerics的问题,这个问题与被称为“奇怪的重复通用模式”的东西有关 。 我认为在阅读Jon Skeet对这个问题“java enum definition”的回答后,我找到了解决方案。 然而,当我尝试在我的代码中应用它时,我发现自己遇到了不同的问题。

我想出了一个“小”的例子,我面临的问题出现了。 我希望能够清楚地说明我的问题。

示例说明:我想构建一个节点类型可能不同的图形。 我已经定义了一个抽象类Node ,它定义了一些基本方法,以及一个实现这些方法的具体类,即ConcreteNode 。 我还创建了一个名为City的ConcreteNode专门化。

在给定的图中,一个重要的要求是所有元素应该由它的相同类型或子类型组成,即ConcreteNode的图形只能有ConcreteNodes Cities。

这些是我的类的定义:

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

这些定义使用了Enum类定义中的“Recurring Generic Pattern”:

 Class Enum<E extends Enum> 

问题:我在使用这些课时遇到了问题。 如果我必须留在层次结构中的城市级别,即将城市连接到城市,我没有问题,但是在尝试访问其他课程时我遇到了很大的问题。

在下面的代码中,我可以在GraphUtil的方法签名中看到我的问题:

  1. addNewNeighbors1a使用原始类型Node,但至少它可以工作。
  2. addNewNeighbors1b使用Node类型,但它根本不编译(错误包含在代码中)。
  3. addNewNeighbors1c使用一个更复杂的Node参数,我希望它能工作,但它不能编译(错误包含在代码中)。
  4. addNewNeighbors3使用Node的复杂参数,但它不会再次编译,即使node和newNode的参数相同。

在综合中,我的问题是如何对这些自身参数化的generics类型进行升级?

我很高兴能获得有关GraphUtil方法的最佳签名的帮助,假设这些方法将位于一个对City甚至ConcreteNode一无所知的库中。

谢谢你们。

这是示例的完整代码

 package test.city; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; public class TestCity { abstract class Node<T extends Node> { public abstract void addNeighbor(T n); public abstract void addNeighbors(Collection nodes); public abstract Collection neighbors(); } class ConcreteNode<T extends ConcreteNode> extends Node { protected Collection _neighbors = new ArrayList(); @Override public void addNeighbor(T n) { _neighbors.add(n); } @Override public void addNeighbors(Collection nodes) { _neighbors.addAll(nodes); } @Override public Collection neighbors() { return _neighbors; } } class City extends ConcreteNode { 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 // 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(); } } 

运行此代码的输出如下(完全没有惊喜):

 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] 

第二部分问题

有一个相关的问题,我没有包含在原始示例中,因为我认为该解决方案也适用。

我现在已经向GraphUtil添加了以下方法:

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

从我的主要课程我尝试以下内容:

 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 

第一种情况有效,因为我知道返回的具体类型,并且它与参数中提供的类型一致。

在第二和第三种情况下,我假设我不知道城市类型。 第二种情况有效,但我使用的是原始类型ConcreteNode。

在第三种情况下,第二行中存在编译错误: TestCity.ConcreteNode类型中的方法addNeighbor(capture#3-of?)不适用于参数(TestCity.City)。“

在示例中,我使用’new City(“ – ”)’作为参数,因为我不知道如何向上转换它们。 在第四种情况下,我试图将City向上转换为ConcreteNode,但它失败了。 当前编译器错误如下: “TestCity.ConcreteNode类型中的方法addNeighbor(capture#4-of?)不适用于参数(TestCity.City)”

问题:

  1. 如何在不知道City类型的情况下修复案例2和3?
  2. 如何将City转换为ConcreteNode(或Node)?

谢谢你的帮助。

您可以创建generics方法以及generics类型。 使用这些, GraphUtils的问题方法可以这样修复:

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

嘿,等一下……那些方法都是一样的!

事实certificate,由于它们仅依赖于Node的接口,因此您只需要其中一个来处理任何Node实现。

在路上,您可能会发现有必要更改Node接口,如下所示:

 public abstract  void addNeighbor(S n); 

对于第1部分,我会使方法签名

 static > void addNewNeighbors(Node node, T newNode) { node.addNeighbor(newNode); } 

这样,节点不必具体与newNode类型相同。

对于第2部分,我会做类似的事情。

 ConcreteNode nc = new City(""); nc.addNeighbor(new City("Bern")); 

对于你的第二部分,通配符也是一个问题。 ConcreteNode可能属于City没有扩展的某种类型 – 例如, ConcreteNode不是由City扩展的 – 尽管事实上你知道它是什么,编译器却没有。

一,静态方法中的通配符允许添加任何类型的节点,编译器知道这是不允许的。 假设你正在做蛋白质图(例如,我做),你有一个带有实例的Protein extends Node类。 你的通配符方法,如果他们工作,将允许我添加所述蛋白质。

至于解决问题,请尝试按以下方式定义添加方法:

 public  void add(U other) 

T由class参数定义,并且该方法允许添加由本地参数U定义的T的任何子类。这应该在一个方向上处理您的问题,尽管您仅限于向超类添加子类。