从泛型的使用情况看出你对语言的理解程度(3)

本文是泛型学习的最后一篇。介绍了类型参数和通配符的对偶性,指出无界通配符方式更适合作为公共 API。还以集合为例,说明泛型正常使用会限制容器类型参数数量,可将类型参数放键上绕过限制,如用 Class 对象作类型安全异构容器的键。

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

上一篇推文中我们学会了什么是泛型单例工厂,并且了解了它的实现原理:从泛型的使用情况看出你对语言的理解程度(2)。今天我们继续最后一篇关于泛型的学习。

Part1

类型参数和通配符之间存在对偶性,可以使用其中一种方法声明许多方法。例如,下面是静态方法的两种可能声明,用于交换列表中的两个索引项。第一个使用无界类型参数,第二个使用无界通配符:

public static <E> void swap(List<E> list, int i, int j);public static void swap(List<?> list, int i, int j);

额,可能有些人还不知道对偶性是什么意思,在这说一下:对偶性即导致相同的结果,而表面上不同的理论之间的对应。也就是说上面的两行代码,实现的方式不同但是效果是一样的。

那么这两个方式在作为公共API实现的时候哪个更好呢?答案是用无界通配符的第二种方式。

传入一个列表(任意列表),该方法交换索引元素。不需要担心类型参数。通常,如果类型参数在方法声明中只出现一次,则用通配符替换它。如果它是一个无界类型参数,用一个无界通配符替换它; 如果它是有界类型参数,则用有界通配符替换它。

例如,我们swap方法的时候,第一种方式必须使用类型参数限制list,否则会报一个Unchecked assignment的警告:

public static void main(String[] args) {    List<Integer> list = new ArrayList<>(); //必须添加类型参数限制    swap(list, 1, 2);}

而第二种则不需要此种限制,且不会有编译警告:

public static void main(String[] args) { List list = new ArrayList();    swap(list, 1, 2);}

很明显,第二种更适合作为公用API来使用。

Part2

集合是泛型的常见应用之一,如 Set<E> 和 Map<K,V>,以及单元素容器,如 ThreadLocal<T> 和 AtomicReference<T>。在所有这些应用中,都是参数化的容器。这将每个容器的类型参数限制为固定数量。通常这正是你想要的。Set 只有一个类型参数,表示其元素类型;Map 有两个,表示键和值的类型;如此等等。

然而,有时你需要更大的灵活性。例如,一个数据库行可以有任意多列,能够以类型安全的方式访问所有这些列是很好的。幸运的是,有一种简单的方法可以达到这种效果。其思想是参数化键而不是容器。然后向容器提供参数化键以插入或检索值。泛型类型系统用于确保值的类型与键一致。

作为这种方法的一个简单示例,考虑一个 Favorites 类,它允许客户端存储和检索任意多种类型的 Favorites 实例。Class 类的对象将扮演参数化键的角色。这样做的原因是 Class 类是泛型的。Class 对象的类型不仅仅是 Class,而是 Class<T>。例如,String.class 的类型为 Class<String>、Integer.class 的类型为 Class<Integer>。在传递编译时和运行时类型信息的方法之间传递类 Class 对象时,它被称为类型标记。

avorites 类的 API 很简单。它看起来就像一个简单的 Map,只不过键是参数化的,而不是整个 Map。客户端在设置和获取 Favorites 实例时显示一个 Class 对象。以下是 API:

public class Favorites {  private Map<Class<?>, Object> favorites = new HashMap<>();    public <T> void putFavorite(Class<T> type, T instance) {    favorites.put(Objects.requireNonNull(type), instance);  }    public <T> T getFavorite(Class<T> type) {    return type.cast(favorites.get(type));  }}

 

下面是一个示例程序,它演示了 Favorites 类、存储、检索和打印 Favorites 字符串、整数和 Class 实例:​​​​​​​

public static void main(String[] args) {  Favorites f = new Favorites();  f.putFavorite(String.class, "Java");  f.putFavorite(Integer.class, 0xcafebabe);  f.putFavorite(Class.class, Favorites.class);  String favoriteString = f.getFavorite(String.class);  int favoriteInteger = f.getFavorite(Integer.class);  Class<?> favoriteClass = f.getFavorite(Class.class);  System.out.printf("%s %x %s%n", favoriteString,favoriteInteger, favoriteClass.getName());}

Favorites 的实例是类型安全的:当你向它请求一个 String 类型时,它永远不会返回一个 Integer 类型。它也是异构的:与普通 Map 不同,所有键都是不同类型的。因此,我们将 Favorites 称为一个类型安全异构容器。

这里发生了一些微妙的事情。每个 Favorites 实例都由一个名为 favorites 的私有 Map<Class<?>, Object> 支持。你可能认为由于通配符类型是无界的,所以无法将任何内容放入此映射中,但事实恰恰相反。需要注意的是,通配符类型是嵌套的:通配符类型不是 Map 的类型,而是键的类型。这意味着每个键都可以有不同的参数化类型:一个可以是 Class<String>,下一个是 Class<Integer>,等等。这就是异构的原理。

总之,以集合的 API 为例的泛型在正常使用时将每个容器的类型参数限制为固定数量。你可以通过将类型参数放置在键上而不是容器上来绕过这个限制。你可以使用 Class 对象作为此类类型安全异构容器的键。以这种方式使用的 Class 对象称为类型标记。还可以使用自定义键类型。例如,可以使用 DatabaseRow 类型表示数据库行(容器),并使用泛型类型 Column<T> 作为它的键。

关注公众号获取更多内容,有问题也可在公众号提问哦:

 

强哥叨逼叨

叨逼叨编程、互联网的见解和新鲜事

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值