Java Generics - 需要解释

时间:2012-06-09 07:41:56

标签: java generics

我对Java Generics有疑问。在下面的代码中,我们有接口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();
    }
}

3 个答案:

答案 0 :(得分:3)

我的Eclipse IDE不会按原样编译任何代码示例。但是当给出额外的类型提示时,它们会编译。在第二个示例中,无论是否有类型参数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会产生影响,那么你处于不安全的地形,并依赖于未指定的编译器行为。

您遇到了一个问题,其中不同的编译器行为不同,即Type Inference。 JLS并不完全清楚编译器必须推断类型的位置,以及它必须拒绝推断的位置,因此这里有摆动空间。不同版本的Eclipse编译器和不同版本的javac在推断类型的位置上有所不同。对于javac,即使在比较不同的1.5.0_x版本时也是如此,Eclipse编译器通常可以推断出比javac更多。

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

关于评论: 如果我希望方法Duck.getSound()返回Quack,而不是声音使用泛型?

假设Bird接口具有以下方法:

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

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

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

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

如果调用者只知道某个实例的类型为Bird<? extends Sound>,则他必须像这样调用getSound:

Sound birdSound = bird.getSound();

如果来电者知道Quack,他可以执行instanceof测试。但是,如果来电者知道这只鸟真的是Bird<Quack>,或者甚至是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> { ... }

我不建议遵循这个例子。 ; - )

答案 1 :(得分:1)

我说:AA通过定义List&lt; AA&gt;来实现A. l = list()您希望它扩展B&lt; X&gt;它没有。无论如何,你会看到编写这样的代码会让你感到困惑。这太复杂了。

答案 2 :(得分:0)

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

让我们推断一下;说我有interface Animalinterface Dog extends Animal以及interface Cat extends Animal。 (我将继续发明更多类和接口。)现在,如果我声明一个将动物返回List<Animal> createList()的方法,那么以下代码没有错:

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

那是因为狗是动物,猫是动物;添加类型List<Animal>的方法签名是add(Animal);我们可以按照预期使用任何有效的Animal实例调用add。但是List上的类型参数不会修改或限制列表的内容,它会修改列表本身的类型;和一个猫的名单&#34;不是动物和动物名单,也不是狗的名单和#34;。即使createLitter()方法实际返回仅包含new ArrayList<Animal>()实例的Parrot,上述代码也可以。然而,你不能做的是“缩小”&#39;列表的类型。例如,这是一个编译错误:

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

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

Bird leaderOfTheFlock = birds.get(0);

你也不能扩大&#39;列表的类型。想象一下,如果可能的话:

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

不允许这样做的原因是代码现在可以向new Integer(0)添加things - 因为IntegerObject。很明显,这也不是我们想要的,并且出于同样的原因 - 一个动物名单&#34;不是&#34;对象列表&#34;。类型参数&#34; Animal&#34; on List<Animal>修改了列表本身的类型,我们讨论的是两种不同类型的列表。这引出了我们这一点的第一个结果 - 泛型类型不遵循继承(is-a)层次结构。

如果不了解您想要做的更多事情,很难从这里开始并保持相关性。我并不是说要苛刻,但看起来你开始在你的代码中抛出泛型来看看是否有效。我和Generics一起奋斗多年。即使经过一个解释了这个微妙点的博客之后我也不得不重新创建上述的一些变化来强化课程,寻找各种方法,如果我违反了规则,我最终会遇到一个类别转换异常。可能你的问题的解决方案是代码的其他部分没有很好地定义你想要引入的严格类型系统,你看到的泛型问题只是一个症状。尝试减少泛型并更多地依赖于组合和继承。我仍然偶尔通过一般的深层射击自己。尝试记住泛型不是为了消除强制转换,而是为编译器提供类型信息,以帮助验证代码处理类型的正确性,这也很有帮助!或者换句话说,它将运行时错误(类转换)转换为源/编译时错误,因此请务必记住编译时所具有的类型信息之间的区别(即使对于泛型,也是有限的) )以及您在运行时获得的类型信息(这是实例的完整类型信息)。