递归通用用法

时间:2009-10-13 22:38:36

标签: java generics

编辑:“ 我收到了'erickson'的一个非常恰当的答案,但是有一个问题(向上转换?)在我的原始示例中未明确涵盖,并且是他的回答没有解决。我已经扩展了这个例子来解决另一个问题,我已经把它包含在这篇文章的最后。谢谢你的帮助。

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

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

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

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

这些是我班级的定义:

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

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

Class Enum<E extends Enum<E>>

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

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

  1. addNewNeighbors1a 使用原始类型节点,但至少它可以工作。
  2. addNewNeighbors1b 使用Node类型,但根本不编译(错误包含在代码中)。
  3. addNewNeighbors1c 使用一个更复杂的Node参数,我希望它可以工作,但是它不能编译(错误包含在代码中)。
  4. addNewNeighbors3 使用Node的复杂参数,但它不会再次编译,即使node和newNode的参数相同。
  5. 在综合方面,我的问题是如何对这些自身参数化的泛型类型进行翻译?

    我很高兴能获得有关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<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
    

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

    在第二和第三种情况下,我假设我不知道城市类型。第二种情况有效,但我使用的是原始类型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(或节点)?
    3. 感谢您的帮助。

4 个答案:

答案 0 :(得分:2)

您可以制作通用方法以及通用类型。使用这些,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);

答案 1 :(得分:2)

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

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

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

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

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

答案 2 :(得分:1)

对于你的第二部分,通配符再次成为一个问题。 ConcreteNode可能属于City没有扩展的某种类型 - 例如,ConcreteNode<Suburb>没有被City扩展 - 尽管事实上你知道它是什么,编译器却没有。

答案 3 :(得分:0)

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

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

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

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