【问题标题】:Recursive Generic Usage递归通用用法
【发布时间】:2009-10-13 22:38:35
【问题描述】:

已编辑:" 我从 'erickson' 那里收到了一个非常中肯的回答,但有一个附带问题(向上转换?)在我的原始示例中没有明确涵盖,并且是他的回答没有解决。我已经扩展了这个例子来解决这个其他问题,我已经把它放在了这篇文章的末尾。谢谢你的帮助。

我目前正面临一个与称为"Curiously Recurring Generic Pattern" 的东西相关的Java 泛型问题。在阅读 Jon Skeet 对这个问题"java enum definition" 的回答后,我以为我找到了解决方案。然而,当我尝试在我的代码中应用它时,我发现自己遇到了不同的问题。

我想出了一个我面临的问题出现的“小”示例。我希望它足以说明我的问题。

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

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

这些是我的类的定义:

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

这些定义使用了 Enum 类定义中的“循环通用模式”:

Class Enum<E extends Enum<E>>

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

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

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

综合起来,我的问题是如何向上转换这些自身参数化的泛型类型?

如果这些方法将位于对 City 甚至 ConcreteNode 一无所知的库中,我将非常高兴获得有关 GraphUtil 方法的最佳签名的帮助。

谢谢大家。

这是示例的完整代码

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

}

运行这段代码的输出如下(一点也不意外):

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

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

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

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

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

问题:

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

感谢您的帮助。

【问题讨论】:

  • 抱歉,您更新后的问题中的示例过于人为,无法帮助我想象您在哪里遇到了真正的问题。在所有示例中,您确实知道addNeighbor 的参数类型(始终为City)。您能否展示您真正尝试编写的方法——也就是说,没有静态声明参数类型的方法?您可能还需要展示如何调用此方法。

标签: java generics


【解决方案1】:

您可以制作泛型方法以及泛型类型。使用这些,GraphUtils 中的问题方法可以这样修复:

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

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

事实证明,由于它们只依赖于Node的接口,你只需要其中一个就可以处理任何Node的实现。

以后,您可能会发现有必要像这样更改Node 界面:

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

【讨论】:

  • 您好,感谢您的回答:非常清楚,它解决了我的部分问题。但是,您的答案未涵盖它的另一个方面。我已经更新了原始帖子以包括另一面。如果你能帮助我,那就太好了。非常感谢。
【解决方案2】:

对于第 1 部分,我将制作方法签名

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

这样,node 不必与 newNode 的类型相同。

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

ConcreteNode<? super City> nc = new City("");
nc.addNeighbor(new City("Bern"));

【讨论】:

  • 第二部分没有用,因为我需要知道 City 才能编写 ConcreteNode,我假设我不是。
【解决方案3】:

对于您的第二部分,通配符又是一个问题。 ConcreteNode 可能是 City 没有扩展的某种类型 - 例如,ConcreteNode&lt;Suburb&gt; 没有被 City 扩展 - 尽管你知道它是什么,但编译器并不知道。

【讨论】:

  • 你是对的......这意味着我的图表可以由不同种类的具体节点组成,不是吗?如果我知道情况并非如此,那么我唯一的选择是使用原始类型,还是使用强制转换?
  • 我不会说这是唯一的选择,但你目前的设计让你有点吃不消,是的。
【解决方案4】:

第一,静态方法中的通配符允许添加任何类型的节点,而编译器知道这是不允许的。假设您正在绘制蛋白质图(例如,我这样做)并且您有一个带有实例的 Protein extends Node&lt;Protein&gt; 类。你的通配符方法,如果它们起作用,将允许我添加所述蛋白质。

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

public <U extends T> void add(U other)

T 由类参数定义,并且该方法允许添加 T 的任何子类,如本地参数 U 所定义的那样。这应该在一个方向上解决您的问题,尽管您仅限于将子类添加到超类.

【讨论】:

  • 感谢您的回答。蛋白质图确实是相同的另一个应用。正如埃里克森所提到的,添加类型 U 来添加对于支持未来的类型很有用。但是,我最初的问题是使用 GraphUtil 的方法,并且按照他的建议解决了这些问题。现在我在原帖中添加了问题的另一部分。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-03
  • 2011-05-02
  • 2012-10-10
  • 1970-01-01
  • 2023-04-10
  • 2016-12-24
相关资源
最近更新 更多