[英]Java Generics - explanation needed
我有一个关于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();
}
}
我的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>
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> { ... }
我不建议遵循这个例子。 ;-)
我会说:AA通过定义List <AA> l = list()来实现A,你希望它扩展B <X>,而不是。 无论如何,你会看到编写这样的代码会让你感到困惑。 这太复杂了。
您对Java Generics略有误解。 要记住的事情,这是一个微妙的事情, List<Y>
不是关于列表的内容,而是列表本身的修改。
让我们推断一下; 说我有interface Animal
和interface 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
上的type参数不会修改或限制列表的内容,它会修改列表本身的类型; 并且“猫列表”不是“动物列表”,也不是“狗列表”。 即使createLitter()
方法实际返回一个只包含Parrot
实例的new ArrayList<Animal>()
,上面的代码也没问题。 然而,你不能做的是“缩小”列表的类型。 例如,这是一个编译错误:
List<Bird> birds = createList(); // does not compile
想象一下,如果它被允许,并且createList
返回了一个包含我们虎斑的“动物列表”; 以下将导致类强制转换异常:
Bird leaderOfTheFlock = birds.get(0);
你也不能'扩大'列表的类型。 想象一下,如果可能的话:
List<Object> things = createList(); // does not compile
不允许这样做的原因是代码现在可以向things
添加new Integer(0)
- 因为Integer
是一个Object
。 显然,这不是我们想要的,并且出于同样的原因 - “动物列表”不是“对象列表”。 List<Animal>
上的类型参数“Animal”修改了列表本身的类型,我们讨论的是两种不同类型的列表。 这引出了我们这一点的第一个结果 - 泛型类型不遵循继承(is-a)层次结构。
如果不了解你想做什么,就很难从这里开始并保持相关性。 我并不是说要严厉,但看起来你开始在你的代码中抛出泛型,看看是否有效。 我和Generics一起奋斗多年。 即使经过一个解释了这个微妙点的博客之后我也不得不重新创建上述的一些变化来强化课程,寻找各种方法,如果我违反了规则,我最终会遇到一个类别转换异常。 可能你的问题的解决方案是代码的其他部分没有很好地定义你想要引入的严格类型系统,你看到的泛型问题只是一个症状。 尝试减少泛型并更多地依赖于组合和继承。 我仍然偶尔通过一般的深层射击自己。 尝试记住泛型不是为了消除强制转换,而是为编译器提供类型信息,以帮助验证代码处理类型的正确性,这也很有帮助; 或者换句话说,它将运行时错误(类转换)转换为源/编译时错误,因此请务必记住编译时所具有的类型信息之间的区别(即使对于泛型,也是有限的) )以及运行时的类型信息(实例的完整类型信息)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.