Java泛型 - 使Generic扩展2个接口

Ily*_*man 43 java generics interface class

你是如何做这项工作的:

public class Frankenstein<T extends IHuman, IMonster>{
}
Run Code Online (Sandbox Code Playgroud)

没有制作

public interface Weirdo extends Ihuman, IMonster{
}
Run Code Online (Sandbox Code Playgroud)

编辑

为什么这不起作用?

public <T> void mapThis(
        Class<? extends MyClass<T>> key, Class<? extends T & IDisposable> value) {

}
Run Code Online (Sandbox Code Playgroud)

我将编译器消息标记Class<? extends T & IDisposable>为错误.

Pau*_*ora 104

Reimeus已经指出你在编辑中要求的东西是不可能的.我想稍微扩展一下原因.

有人会认为你可以使用以下内容:

public <T, U extends T & IDisposable> void mapThis(
        Class<? extends MyClass<T>> key,
        Class<? extends U> value
) { ... }
Run Code Online (Sandbox Code Playgroud)

事实上,当我第一次看到这篇文章时,这就是我的想法.但这实际上给出了编译器错误:

类型变量可能不会跟随其他边界

为了帮助我解释原因,我想引用Victor Rudometov关于此错误的Oracle博客文章:

这个事实并不总是很清楚,但确实如此.以下代码不应编译:

interface I {}

class TestBounds <U, T extends U & I> {

}

因为JLS第4章类型,值和变量部分4.4类型变量指出:" 结合的由任一类型变量,或一个类或接口类型T可能随后进一步接口类型的I 1,...,I Ñ. " .因此,可以使用T extends U,T扩展SomeClass&I,但不是T扩展U&I.此规则适用于所有情况,包括方法和构造函数中的类型变量和边界.

在一个密切相关的帖子中探讨了这种限制的原因:为什么我不能在具有多个边界的类型参数中使用类型参数?

总而言之,施加限制是为了"排除某些尴尬的情况"(JLS§4.9).

什么样的尴尬局面?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) { ... }
Run Code Online (Sandbox Code Playgroud)

现在holder.comparator是一个Comparator<Integer>和一个Comparator<String>.

克里斯还指出了Sun bug 4899305,这是一个针对这种语言限制的bug.它被关闭,因为不会修复以下评论:

如果类型变量后跟类型变量或(可能是参数化的)接口,则可能会有更多相互递归的类型变量,这些变量很难处理.当绑定只是参数化类型时,事情已经很复杂,例如<S,R extends Comparable<S>>.因此,现在的界限不会改变.javac和Eclipse的同意,S&T并且 S&Comparable<S>是非法的.

所以这些是限制背后的原因.特别针对通用方法(你的问题关注的问题),我想进一步指出类型推理理论上会导致这种界限毫无意义.

如果我们重新检查上面假设签名中声明的类型参数:

<T, U extends T & IDisposable>
Run Code Online (Sandbox Code Playgroud)

假设呼叫者未明确地指定T并且U,这可以减少到以下内容:

<T, U extends Object & IDisposable>
Run Code Online (Sandbox Code Playgroud)

或者只是这个(微妙的差异,但那是另一个话题):

<T, U extends IDisposable>
Run Code Online (Sandbox Code Playgroud)

这是因为T没有任何边界,所以无论传入什么类型的参数,T总是可以解决Object至少,然后可以U.

让我们回去说说T是有限的:

<T extends Foo, U extends T & IDisposable>
Run Code Online (Sandbox Code Playgroud)

这可以以相同的方式减少(Foo可以是类或接口):

<T extends Foo, U extends Foo & IDisposable>
Run Code Online (Sandbox Code Playgroud)

基于这种推理,只要将调用者限制为更具体的参数,您尝试实现的语法就毫无意义.

Java 8之前的附录:

在此之前的Java 8,一个用例你想做什么.由于编译器如何推断泛型方法类型参数的限制,我上面的推理要走出窗口.采用以下通用方法:

class MyClass {
    static <T> void foo(T t1, T t2) { }
}
Run Code Online (Sandbox Code Playgroud)

这是一个常见的初学者错误,试图制作一个采用"相同类型"的两个参数的方法.当然,由于继承的工作方式,这是毫无意义的:

MyClass.foo("asdf", 42); // legal
Run Code Online (Sandbox Code Playgroud)

在这里,T推断为Object- 这与先前关于简化mapThis类型参数的推理相匹配.您必须手动指定类型参数才能实现预期的类型检查:

MyClass.<String>foo("asdf", 42); // compiler error
Run Code Online (Sandbox Code Playgroud)

但是,这就是你的用例开始出现的地方,不同的是带有交错边界的多个类型参数:

class MyClass {
    static <T, U extends T> void foo(T t, U u) { }
}
Run Code Online (Sandbox Code Playgroud)

现在这个调用错误:

MyClass.foo("asdf", 42); // compiler error
Run Code Online (Sandbox Code Playgroud)

表已经转向 - 我们必须手动放松类型参数以使其编译:

MyClass.<Object, Object>foo("asdf", 42); // legal
Run Code Online (Sandbox Code Playgroud)

发生这种情况是因为编译器推断方法类型参数的方式有限.出于这个原因,你想要实现的目标实际上是有一个限制调用者参数的应用程序.

但是,这个问题似乎已在Java 8中修复,MyClass.foo("asdf", 42)现在编译时没有任何错误(感谢Regent指出这一点).


Ben*_*Ben 5

我只是想分享我在这些(非常罕见)情况下使用的技巧:

    /**
     * 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);
    }
Run Code Online (Sandbox Code Playgroud)

因此,通过为您试图满足的每个边界要求相同的对象,您可以获得编译时类型检查和执行所有操作的单个对象。诚然,为每个参数传递相同的对象有点愚蠢,但我在“内部”代码中非常安全和舒适地执行此操作。