Java 泛型 - Make Generic 扩展 2 个接口
Posted
技术标签:
【中文标题】Java 泛型 - Make Generic 扩展 2 个接口【英文标题】:Java generics - Make Generic to extends 2 interfaces 【发布时间】:2012-10-17 14:36:02 【问题描述】:你是如何做到这一点的:
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>
标记为错误的编译器消息。
【问题讨论】:
Java Generics Wildcarding With Multiple Classes的可能重复 【参考方案1】: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 后面可能跟着更多的接口类型 I1 , ..., In."。所以一个 可以使用 T extends U, T extends SomeClass & I,但不能使用 T extends U & I。 此规则适用于所有情况,包括类型变量和边界 方法和构造函数。
在密切相关的帖子中探讨了这种限制的原因: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>
。
Chris 还指出了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 之前的附录:
在 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 指出这一点)。
【讨论】:
顺便说一下,MyClass.foo("asdf", 42)
在 Java 8 中不会给出带有<T, U extends T>
边界的编译错误。
@Regent 很高兴看到该问题已得到解决。更新我的答案,谢谢!
很好的答案,即使我自己不分享。在我看来,这是编译器的设计缺陷【参考方案2】:
我只是想分享一下我在这些(非常罕见的)情况下使用的技巧:
/**
* 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);
因此,通过为您试图满足的每个边界要求相同的对象,您可以获得编译时类型检查和完成所有操作的单个对象。诚然,为每个参数传递相同的对象有点愚蠢,但我在我的“内部”代码中非常安全和舒适地这样做。
【讨论】:
以上是关于Java 泛型 - Make Generic 扩展 2 个接口的主要内容,如果未能解决你的问题,请参考以下文章