你如何做这项工作:
public class Frankenstein<T extends IHuman, IMonster>{
}
不做
public interface Weirdo extends Ihuman, IMonster{
}
修改
为什么这不起作用?
public <T> void mapThis(
Class<? extends MyClass<T>> key, Class<? extends T & IDisposable> value) {
}
我将编译器消息标记为Class<? extends T & IDisposable>
为错误。
答案 0 :(得分:99)
Reimeus已经指出,你在编辑中要求的是不可能的。我想稍微谈谈原因。
有人会认为您可以使用以下内容:
public <T, U extends T & IDisposable> void mapThis(
Class<? extends MyClass<T>> key,
Class<? extends U> value
) { ... }
事实上,当我第一次看到这篇文章时,这就是我的想法。但这实际上给出了编译器错误:
类型变量后面可能没有其他边界
为了帮助我解释原因,我想引用Victor Rudometov关于此错误的Oracle Blogs post:
这个事实并不总是很清楚,但确实如此。以下代码 不应该编译:
interface I {}
class TestBounds <U, T extends U & I> {
}
因为JLS第4章 类型,值和变量部分4.4类型变量状态:“ bound由 类型变量或类或接口类型组成 T后面可能还有其他接口类型I 1 ,...,I n 。“。所以一个 可以使用 T扩展U,T扩展SomeClass&amp;我,但不是 T扩展U&amp;我。 此规则适用于所有情况,包括类型变量和边界 方法和构造函数。
这一限制的原因在一篇密切相关的帖子中进行了探讨:Why can't I use a type argument in a type parameter with multiple bounds?
总而言之,施加限制是为了“排除某些尴尬的情况”(JLS §4.9)。
什么样的尴尬局面? An answer by Chris Povirk描述了一个:
[限制的原因是]指定非法类型的可能性。具体来说,使用不同的参数扩展通用接口两次。我无法想出一个非人为的例子,但是:
/** Contains a Comparator<String> that also implements the given type T. */ class StringComparatorHolder<T, C extends T & Comparator<String>> { private final C comparator; // ... } void foo(StringComparatorHolder<Comparator<Integer>, ?> holder) { ... }
现在
holder.comparator
是Comparator<Integer>
和Comparator<String>
。
克里斯也指向Sun bug 4899305,这是一个与此语言限制竞争的错误。它被关闭,因为不会修复以下评论:
如果类型变量后跟类型变量或者(可能是) 参数化的接口,可能会有更多的相互作用 递归类型变量,很难处理。事情 当一个绑定只是一个参数化类型时,它已经很复杂了 例如
<S,R extends Comparable<S>>
。因此,边界不会发生 改变现在。 javac和Eclipse都同意S&T
和S&Comparable<S>
是非法的。
所以这些是限制背后的原因。特别针对通用方法(你的问题关注的问题),我想进一步指出类型推理理论上会导致这种界限毫无意义。
如果我们重新检查上面假设签名中声明的类型参数:
<T, U extends T & IDisposable>
假设调用者未明确指定T
和U
,则可以将其简化为以下内容:
<T, U extends Object & IDisposable>
或者只是这个(微妙的差异,但那是another topic):
<T, U extends IDisposable>
这是因为T
没有任何边界,所以无论传递什么类型的参数,T
总是至少可以解析为Object
,所以然后可以U
。
让我们回过头来说T
是有限的:
<T extends Foo, U extends T & IDisposable>
这可以通过相同的方式减少(Foo
可以是类或接口):
<T extends Foo, U extends Foo & IDisposable>
基于这种推理,你试图实现的语法是无意义的,只要将调用者限制为更具体的参数。
在Java 8之前, 是您尝试执行的操作的用例。由于编译器如何推断泛型方法类型参数的限制,我上面的推理要走出窗口。采用以下通用方法:
class MyClass {
static <T> void foo(T t1, T t2) { }
}
这是一个常见的初学者错误,试图制作一个采用“相同类型”的两个参数的方法。当然,由于继承的工作方式,这是毫无意义的:
MyClass.foo("asdf", 42); // legal
此处,T
被推断为Object
- 这与先前关于简化mapThis
类型参数的推理相匹配。您必须手动指定类型参数才能实现预期的类型检查:
MyClass.<String>foo("asdf", 42); // compiler error
然而,这里是您的用例开始进入的地方,不同的是具有交错边界的多个类型参数:
class MyClass {
static <T, U extends T> void foo(T t, U u) { }
}
现在这个调用错误:
MyClass.foo("asdf", 42); // compiler error
表格已经转向 - 我们必须手动放松类型参数以使其编译:
MyClass.<Object, Object>foo("asdf", 42); // legal
这是因为编译器推断方法类型参数的方式有限。出于这个原因,你想要实现的目标实际上有一个限制调用者参数的应用程序。
但是,这个问题似乎已在Java 8中修复,MyClass.foo("asdf", 42)
现在编译时没有任何错误(感谢Regent指出这一点)。
答案 1 :(得分:3)
我只是觉得我会分享我在这些(非常罕见的)情况下使用的黑客行为:
/**
* This is a type-checking method, which gets around Java's inability
* to handle multiple bounds such as "V extends T & ContextAware<T>".
*
* @param value initial value, which should extends T
* @param contextAware initial value, which should extend ContextAware<T>
* @return a new proxy object
*/
public T blah(T value, ContextAware<T> contextAware) {
if (value != contextAware) {
throw new IllegalArgumentException("This method expects the same object for both parameters.");
}
return blah(value);
}
因此,通过为要尝试满足的每个边界要求相同的对象,您将获得编译时类型检查以及执行所有操作的单个对象。不可否认,为每个参数传递相同的对象有点愚蠢,但我在我的“内部”代码中非常安全和舒适地执行此操作。