JAVA泛型通配符的小小难点

本文深入探讨了Java泛型中通配符的应用与限制,通过实例解析了如何正确处理带有通配符参数的泛型方法,并揭示了通配符捕获的原理及常见错误。主要内容包括通配符的好处、如何利用通配符进行类型安全的代码编写、捕获助手的使用技巧以及避免常见的类型转换错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我已使用了一个通配符 — 接下来呢? 

清单 展示了一个简单的容器(container)类型 Box,它支持 put 和 get 操作。 Box 由类型参数 参数化,该参数表示 Box 内容的类型, Box<String> 只能包含 String 类型的元素。

清单 1. 简单的泛型 Box 类型

public interface Box<T> {

    public T get();

    public void put(T element);

}

通配符的一个好处是允许编写可以操作泛型类型变量的代码,并且不需要了解其具体类型。例如,假设有一个 Box<?> 类型的变量,比如清单 2 unbox() 方法中的 box 参数。unbox() 如何处理已传递的 box

清单 2. 带有通配符参数的 Unbox 方法

public void unbox(Box<?> box) {

    System.out.println(box.get());

}

事实证明 Unbox 方法能做许多工作:它能调用 get() 方法,并且能调用任何从 Object 继承而来的方法(比如 hashCode())。它惟一不能做的事是调用 put() 方法,这是因为在不知道该 Box 实例的类型参数 的情况下它不能检验这个操作的安全性。由于 box 是一个 Box<?> 而不是一个原始的 Box,编译器知道存在一些 充当 box 的类型参数,但由于不知道 具体是什么,您不能调用 put() 因为不能检验这么做不会违反 Box 的类型安全限制(实际上,您可以在一个特殊的情况下调用 put():当您传递 null 字母时。我们可能不知道 类型代表什么,但我们知道 null 字母对任何引用类型而言是一个空值)。

关于 box.get() 的返回类型,unbox() 了解哪些内容呢?它知道 box.get() 是某些未知 的 T,因此它可以推断出 get() 的返回类型是 的擦除(erasure),对于一个无上限的通配符就是 Object。因此清单 中的表达式 box.get() 具有 Object 类型。

通配符捕获

清单 展示了一些似乎应该 可以工作的代码,但实际上不能。它包含一个泛型 Box、提取它的值并试图将值放回同一个 Box

清单 3. 一旦将值从 box 中取出,则不能将其放回

public void rebox(Box<?> box) {

    box.put(box.get());

}

 

Rebox.java:8: put(capture#337 of ?) in Box<capture#337 of ?> cannot be applied

   to (java.lang.Object)

    box.put(box.get());

       ^

1 error

这个代码看起来应该可以工作,因为取出值的类型符合放回值的类型,然而,编译器生成(令人困惑的)关于 “capture#337 of ?” 与 Object 不兼容的错误消息。

capture#337 of ?” 表示什么?当编译器遇到一个在其类型中带有通配符的变量,比如 rebox() 的 box 参数,它认识到必然有一些 ,对这些 而言 box 是 Box<T>。它不知道 代表什么类型,但它可以为该类型创建一个占位符来指代 的类型。占位符被称为这个特殊通配符的捕获(capture。这种情况下,编译器将名称 “capture#337 of ?” 以 box 类型分配给通配符。每个变量声明中每出现一个通配符都将获得一个不同的捕获,因此在泛型声明 foo(Pair<?,?> x, Pair<?,?> y) 中,编译器将给每四个通配符的捕获分配一个不同的名称,因为任意未知的类型参数之间没有关系。

错误消息告诉我们不能调用 put(),因为它不能检验 put() 的实参类型与其形参类型是否兼容 — 因为形参的类型是未知的。在这种情况下,由于 实际表示 “?extends Object” ,编译器已经推断出 box.get() 的类型是 Object,而不是 “capture#337 of ?”。它不能静态地检验对由占位符 “capture#337 of ?” 所识别的类型而言 Object 是否是一个可接受的值。

捕获助手

虽然编译器似乎丢弃了一些有用的信息,我们可以使用一个技巧来使编译器重构这些信息,即对未知的通配符类型命名。清单 展示了 rebox() 的实现和一个实现这种技巧的泛型助手方法(helper):

清单 4. “捕获助手” 方法

public void rebox(Box<?> box) {

    reboxHelper(box);

}

 

private<V> void reboxHelper(Box<V> box) {

    box.put(box.get());

}

 

总结:

其实这一大段主要想要告诉我们, public T get();和 public void put(T element);中的T是不一样的,所以不可以向这样子调用:

public void rebox(Box<?> box) {

    box.put(box.get());//因为put中的T跟get中的T是不一样的。所以put不能保证get返回的类型是自己想要的类型,所以违反了泛型的类型检查~!

}

  

想要box.put(box.get());这样子调用,要先将2个泛型参数T都变成都一个泛型参数T,像这样子:

public void rebox(Box<?> box) {

    reboxHelper(box);

}

 

private<V> void reboxHelper(Box<V> box) {

    box.put(box.get());

}

 

   

 

   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值