3

我有一个关于 Java 泛型的问题。在下面的代码中,我们将接口 B 参数化为另一种必须实现接口 A 的类型。

这段代码是正确的。问题是:为什么它不适用于以下 list() 方法声明?

private <X extends A, Y extends B<X>> List<Y> list()

工作代码:

public interface A {
}
public interface B<T extends A> {
}
public class Test {

    private static class AA implements A {}
    private static class BB implements B<AA> {}

    private <R extends A, X extends R, Y extends B<X>> List<Y> list() {
        return null;
    }

    private void test() {
        List<BB> l = list();
    }
}

编辑:我已经修改了代码。现在我们已经通过它可以发出的声音对鸟进行了参数化。问题是为什么 useless_t 是必要的?

public class Test {
    public interface Sound {
    }
    public interface Bird<T extends Sound> {
    }

    private static class Quack implements Sound {}
    private static class Duck implements Bird<Quack> {}


    private <useless_t extends Sound, sound_t extends useless_t, bird_t extends Bird<sound_t>> List<bird_t> list() {
            return null;
    }

    private void test() {
            List<Duck> l = list();
    }
}
4

3 回答 3

3

我的 Eclipse IDE 不会按原样编译您的任何代码示例。但是当给出额外的类型提示时,它们会编译。在第二个示例中,无论有无 type parameter useless_t,以下行都不会为我编译:

List<Duck> l = list();

但以下确实为我编译:

List<Duck> l = this.<Sound, Quack, Duck> list();

排除因素后,以下内容也将useless_t编译:

List<Duck> l = this.<Quack, Duck> list();

所以这基本上是编译器没有正确获取类型参数的问题,你需要明确地给出类型。

更新:如果你真的遇到了一个添加useless_t了不同的程序,那么你处于不安全的地形,并且依赖于未指定的编译器行为。

您遇到了不同编译器行为不同的问题,即类型推断。JLS 并不完全清楚编译器必须在哪里推断类型,以及在哪里必须拒绝推断,因此这里有回旋余地。不同版本的 Eclipse 编译器和不同版本的 javac 在推断类型的位置不同。对于 javac,即使比较不同的 1.5.0_x 版本也是如此,并且 Eclipse 编译器通常可以推断出比 javac 更多的信息。

您应该只依赖所有常见编译器都成功的类型推断,否则会给出类型提示。有时,这就像引入临时变量一样容易,但有时(如您的示例中)您必须使用var.<Types>method()语法。

关于评论: 如果我想让 Duck.getSound() 方法返回 Quack,而不是使用泛型返回 Sound,该怎么办?

假设 Bird 接口有以下方法:

public interface Bird<T extends Sound> {
    T getSound();
}

然后你可以像这样实现它:

private static class Duck implements Bird<Quack> {
    public Quack getSound() { return new Quack(); }
}

这是泛型的一个用例——允许实现指定具体类型,这样即使超类也可以使用该类型。(在不知道 T 的具体类型的情况下,Bird 接口可以有一个setSound(T),或者用 T 做其他事情。)

如果调用者只知道实例是 type Bird<? extends Sound>,他将不得不像这样调用 getSound :

Sound birdSound = bird.getSound();

如果调用者知道Quack,他可以进行instanceof测试。但是如果调用者知道这只鸟真的是 a Bird<Quack>,甚至那是 a Duck,那么他可以写这个并且它可以根据需要编译:

Quack birdSound = bird.getSound();

但要注意:在接口或超类中生成过多的类型会带来系统过于复杂的风险。正如 Slanec 所写,重新思考你的真实设计,看看是否真的需要有这么多泛型。

我曾经走得太远,最终得到了一个接口层次结构和两个实现层次结构,基于这样的接口:

interface Tree<N extends Node<N>,
               T extends Tree<N, T>> { ... }

interface SearchableTree<N extends SearchableNode<N>,
                         S extends Searcher<N>,
                         T extends SearchableTree<N, S, T>>
    extends Tree<N, T> { ... }

我不建议效仿那个例子。;-)

于 2012-06-09T08:26:23.057 回答
1

我想说:AA 实现了 A,通过定义 List<AA> l = list() 你希望它扩展 B<X> 而它没有。无论如何,您会看到编写这样的代码很容易混淆。这太复杂了。

于 2012-06-09T08:02:38.360 回答
0

您对 Java 泛型有一点误解。要记住的事情,这是一个微妙的事情,List<Y>不是关于列表的内容,而是对列表本身的修改。

让我们稍微推断一下;说我有interface Animal和。(随着我们的进展,我将发明更多的类和接口。)现在,如果我声明一个将动物返回为的方法,则以下代码没有任何问题:interface Dog extends Animalinterface Cat extends AnimalList<Animal> createList()

List<Animal> litter = createList();
Cat tabby = new Tabby();
litter.add(tabby);
Dog poodle = new Poodle();
litter.add(poodle);

那是因为狗是动物,猫是动物;add on 类型的方法签名List<Animal>add(Animal);正如预期的那样,我们可以调用add任何有效的 Animal 实例。但是类型参数 onList并没有修改或限制列表的内容,它修改了列表本身的类型;“猫名单”不是“动物名单”,“狗名单”也不是。即使该createLitter()方法实际上返回的 anew ArrayList<Animal>()只包含 的实例Parrot,上面的代码也没有问题。但是,您不能做的是“缩小”列表的类型。例如,这是一个编译错误:

List<Bird> birds = createList(); // does not compile

想象一下,如果它被允许,并createList返回一个包含我们虎斑猫的“动物列表”;以下将导致类转换异常:

Bird leaderOfTheFlock = birds.get(0);

您也不能“扩大”列表的类型。想象一下,如果有可能:

List<Object> things = createList(); // does not compile

也不允许这样做的原因是代码现在可以添加一个new Integer(0)-things因为 anInteger是一个Object. 显然这也不是我们想要的,出于同样的原因——“动物列表”不是“对象列表”。类型参数“Animal”List<Animal>修改了列表本身的类型,我们正在讨论两种不同类型的列表。这将我们引向这一点的第一个结果——泛型类型不遵循继承(is-a)层次结构。

如果不知道更多你想做什么,就很难从这里开始并保持相关性。我并不是要严厉,但看起来你开始在你的代码中使用泛型,看看是否有什么能起作用。我在泛型上苦苦挣扎多年。即使在浏览了一个解释这个微妙点的博客之后,我也不得不重新创建上述的相当多的变体来强化课程,寻找各种方法,如果我违反规则,我最终会出现类转换异常。您的问题的解决方案可能是代​​码的其他部分没有很好地定义您尝试引入的严格类型系统,您看到的泛型问题只是其中的一个症状。尽量减少泛型,更多地依赖组合和继承。我仍然偶尔会通过跳出通用的深端来击中自己的脚。尝试记住泛型的重点不是消除强制转换,而是使编译器可以使用类型信息,以帮助验证代码如何处理类型的正确性,这也很有帮助。或者换句话说,它将运行时错误(类转换)转换为源代码/编译时错误,因此请务必牢记编译时所拥有的类型信息之间的区别(即使使用泛型也是有限的) ) 以及您在运行时拥有的类型信息(即实例的完整类型信息)。

于 2013-08-22T07:26:45.203 回答