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

2022-08-31 20:08:04

你如何做到这一点:

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>


答案 1

Reimeus已经指出,您在编辑中要求的内容是不可能的。我只想稍微扩展一下为什么。

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

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

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

类型变量后面不能跟有其他边界

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

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

interface I {}

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

}

因为 JLS 第 4 章类型、值和变量第 4.4 节类型变量指出:“绑定由类型变量或类或接口类型 T 组成,后面可能跟有进一步的接口类型 I1、 ...、 In.”。因此,可以使用T扩展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) { ... }

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

Chris还指出了Sun bug 4899305,这是一个挑战这种语言限制的错误。它被关闭为“不会修复”,并带有以下注释:

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

因此,这些是限制背后的原因。专门解决泛型方法(你的问题涉及),我想进一步指出,类型推断在理论上会导致这样的边界毫无意义。

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

<T, U extends T & IDisposable>

假设调用方没有显式指定 和 ,这可以简化为以下内容:TU

<T, U extends Object & IDisposable>

或者只是这个(微妙的区别,但这是另一个话题):

<T, U extends IDisposable>

这是因为没有任何界限,所以无论传入哪种类型的参数,至少可以解析,因此可以。TTObjectU

让我们回过头来说是有界的: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

这里推断为 - 这与早期关于简化类型参数的推理相吻合。您必须手动指定类型参数才能实现预期的类型检查:TObjectmapThis

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中得到了修复,现在编译没有任何错误(感谢Regent指出了这一点)。MyClass.foo("asdf", 42)


答案 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);
    }

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