Java泛型通配符与类型化泛型的使用

5
我正在阅读Java教程中的以下部分:http://docs.oracle.com/javase/tutorial/java/generics/capture.html 文章一开始就指出,由于捕获类型无法转换为Object类型,所以以下代码会产生错误,因此set方法无法确认Object是capture#1类型。
import java.util.List;

public class WildcardError {

    void foo(List<?> i) {
        i.set(0, i.get(0));
    }
}

我在一定程度上理解这个推理。i.get返回一个Object,编译器无法确定对象是否属于capture#1类型,因此无法以类型安全的方式将其与第二个参数匹配。

然后建议使用以下代码使此方法工作:

public class WildcardFixed {

    void foo(List<?> i) {
        fooHelper(i);
    }


    // Helper method created so that the wildcard can be captured
    // through type inference.
    private <T> void fooHelper(List<T> l) {
        l.set(0, l.get(0));
    }

}

我有点理解这段代码为什么能够正常工作。在这段代码中,l.get 被保证是类型为 T 的,所以它可以被当做 T 类型的参数来传递。
但是我不理解的是,为什么不能直接使用这样的方法,而非使用 helper:
class GenericsTest {
    static <K> void bar(List<K> l) {
        l.set(0, l.get(l.size() - 1));
    }

    public static void main(String[] args) {
        List<Integer> lst = Arrays.asList(1, 2, 3, 4);
        bar(lst);
        System.out.println(lst); // [4, 3, 2, 4]
    }
}

例如,如果您要使用类型推断,为什么不直接使用显式类型泛型而不是通配符和辅助函数?在这种情况下使用通配符有什么优势?在哪些情况下您会更喜欢使用通配符而不是类型化的泛型?


根据您的理解,static <K> void 是什么意思? - Mike 'Pomax' Kamermans
@Mike 对我来说,这意味着K是一个类型参数,仅适用于该方法的范围。Java使用类型推断来确定运行时应该是什么K。在所示的示例中,我传递了List<Integer>的实例,因此编译器推断K为Integer。K可以是任何引用类型,并且引用类型从Object扩展,因此在这种情况下,我认为K只是显式编写<?>的另一种方式。 - Shashank
@Mike 我不知道...它似乎与普通的void方法相同。我不知道K与返回有任何关系。我只是在使用这里的语法:http://docs.oracle.com/javase/tutorial/java/generics/methods.html - Shashank
我不明白你的问题。你的 fooHelperbar 使用相同的类型。索引只是不同的(但 static 与实例不同,但这也无关紧要)。 - Sotirios Delimanolis
问题实际上是为什么您要创建一个使用 <?> 的方法,而不是使用像 <K> 这样的类型化泛型。在我看来,它们都具有相同的类型安全级别,并且都可以处理所有引用类型,但使用类型化泛型使您能够做更多的事情而不会使编译器抱怨。 - Shashank
显示剩余2条评论
2个回答

1
请注意,void foo(List<?> i)void <K> foo(List<K> i) 两者接受的参数集合完全相同--假设您在泛型方法案例中没有明确指定 K,可以传递到一个函数签名的任何参数都可以传递到另一个函数签名,并反之亦然。因此,对于“外部代码”,这两个签名都是同样有用的。
鉴于它们是等效的,具有较少类型参数的那个更简单,应始终优先选择。当向外部代码提供公共API时,应仅以最简形式呈现,仅具有使其安全所必需的最小类型信息。类型 List<?> 足以表示它可以接受任何类型的 List,因此我们应该使用它。
我们恰好在内部使用类型参数 K 来解决一些泛型问题,但这只是一个不幸的内部实现细节,外部代码无需关心。因此,在创建公共API时,我们应该隐藏这种丑陋并将其包装在更漂亮的函数签名中。
除了抽象目的,您希望特定签名的另一个原因是,如果它覆盖具有该签名的超类方法。在覆盖时无法添加类型参数。

0

我认为教程中提到的例子是指处理需要接受未绑定通配符<?>参数的情况。这可能发生在与遗留代码交互或扩展具有此通配符类型的现有类时。

在大多数情况下,当您需要灵活性但又不想牺牲类型检查时,您会使用像<T extends U><? extends T>这样的东西。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接