java学习笔记(十)——泛型

10.1 简单泛型

使用泛型最重要的原因就是为了创造容器类。

有些情况下,我们希望容器能够同时持有多种类型的对象。通常我们只会使用容器来存储一种类型的对象。泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。

与其使用Object,更想暂时不指定类型,而且稍后再决定使用什么类型,要达到这个目的,需要使用类型参数,用尖括号括住,放在类名后面。然后在使用这个类的时候,再用实际的类型替换此类型参数。

10.1.1 一个元组类库

我们经常需要仅一次方法就能返回多个对象。但return语句只允许返回单个对象。因此,需要创建一个对象来持有要返回的多个对象。每次需要的时候,专门创建一个类来完成这样的工作。有了泛型就能一次性解决该问题,也能在编译期就能确保类型安全。

这个概念称为元组(tuple),它是将一组对象直接打包存储于其中的一个单一对象。这个容器对象运行读取其中元素,但是不允许向其中存放新的对象(也称为数据传送对象,或信使)。

通常,元组可以具有任意对象,同时元组中的对象可以是任意不同的类型。不过,我们希望能够为每一个对象指明其类型,并且从容器中读取出来,能够得到正确的类型。要处理不同长度的问题,我们需要创建多个不同的元组。

10.2 泛型接口

泛型也可以应用于接口。例如生成器(generator),这是一种专门负责创建对象的类。实际上,这是工厂方法设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。也就是生成器无需额外信息就知道如何创建新对象。

10.3 泛型方法

同样可以在类中包含参数化方法,而这个方法所在的类可以是泛型类,也可以不是泛型。也就是说,是否拥有泛型方法,与其所在的类是否是泛型没有关系。

泛型方法使得该方法能够独立于类而产生变化。无论何时,应尽量使用泛型方法。如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情更清楚明白。对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。

10.4 边界

边界使得你可以在用于泛型的参数类型上设置限制条件。尽管这使得你可以强制规定泛型可以应用的类型,但是其潜在的一个更重要的效果是你可以按自己的边界类型来调用方式。

因为擦除移除了类型信息,所以,可以用无界泛型参数调用的方法只是那些可以用Object调用的方法。但是,如果能够将这个参数限制为某个类型子集,那么就可以用这些类型子集来调用方法。为了执行这种限制,Java泛型重用了extends关键字。需要理解extends关键字在泛型边界上下文环境中和普通环境中所具有的意义是完全不同的。

10.5 问题

10.5.1 基本类型不能作为类型参数

Java泛型的限制之一是,不能将基本类型用作类型参数。因此,不能创建ArrayList<int>之类的东西。

解决方法是使用基本类型的包装类以及自动包装机制。如果创建一个ArrayList<Integer>,并将基本类型int应用于这个容器,那么你将发现自动包装机制将自动实现int到Integer的双向转换。

10.5.2 实现参数化接口

一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。

10.5.3 转型和警告

使用带有泛型类型参数的转型或instanceof不会有任何效果。

10.6 动态类型安全

因为可以向之前的代码传递泛型容器,所以旧式代码仍旧有可能破坏你的容器,java.util.Collections中有一组便利工具,可以解决在这种情况下的类型检查问题,它们是:静态方法checkedCollection(),checkedList(),checkedMap(),checkedSet(),checkedSortedMap()和checkedSortedSet()。这些方法每一个都会将你希望动态检查的容器当作第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。

受检查的容器在试图插入类型不正确的对象时抛出ClassCastException,这与泛型之前的(原生)容器形成了对比,对于后者来说,当你将对象从容器中取出时,才会通知你出现了问题。在后一种情况,就不知道问题在哪里,如果使用受检查的容器,就可以发现谁在试图插入不良对象。

10.7 异常

由于擦除的原因,将泛型应用于异常是非常受限的。catch语句不能捕获泛型类型的异常,因为在编译器和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自Throwable(这将进一步阻止你去定义不能捕获的泛型异常)。

10.8 混型

混型最基本的概念就是混合多个类的能力,以产生一个可以表示混型中所有类型的类。这往往是你最后的手段,它使组装多个类变得简单易行。

混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型上。混型有点像AOP,而AOP经常被建议用来解决混型问题。

10.8.1 C++中的混型

在C++中,使用多重继承的最大理由,就是为了使用混型。对应混型来说,更优雅的方式是使用参数化类型,因为混型就是继承自其类型参数的类。在C++中,可以很容易地创建混型,因为C++能够记住其模板参数的类型。

10.8.2 与接口混合

一种更常见的解决方案就是使用接口来产生混型效果。

10.8.3 使用装饰器模式

混型与装饰器设计模式关系很近。装饰器经常用于满足各种可能的组合,而直接子类化会产生过多的类,因此是不实际的。

装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。这使得对装饰器的使用时透明的,无论对象是否被装饰,你都拥有一个可以向对象发送的公共消息集。装饰类也可以添加新方法,但这是受限的。

装饰器是通过使用组合和形式化结构(可装饰物/装饰器层次结构)来实现的,而混型是基于继承的。因此可以将参数化类型的混型当作是一种泛型装饰器机制,这种机制不需要装饰器设计模式的继承结构。

10.8.4 与动态代理混合

可以使用动态代理来创建一种比装饰器更贴近混型模型的机制。通过使用动态代理,所产生的类的动态类型将会是已经混入的组合型。

10.9 潜在类型机制

当你在编写或使用只是持有对象的泛型时,这些代码将可以工作与任何类型(除了基本类型,尽管自动包装机制可以克服这一点)。或者说“持有器”泛型能够声明:“我不关心你是什么类型”。代码不关心它将要作用的类型,及可以真正应用于任何地方,并因此而“泛化”。

当泛型类型上执行操作(调用Object方法之前的操作)时,就会产生问题,因为擦除要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对泛化的一种明显的限制,因为必须限制泛型类型,使它们继承自特定的类,或者实现特定的接口。在某些情况下,最终可能会使用普通类或普通接口,因为限定边界的泛型可能会和指定类或接口没用任何区别。

泛型代码典型地将在泛型类型上调用少量方法,而具有潜在类型机制的语言只要求实现某个方法子集,而不是某个特定的类或接口,从而放松了这种限制(并且可以产生更加泛化的代码)。潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。由于不要求具体类型,因此代码就可以更加泛化。

潜在类型机制是一种代码组织和复用机制。有了它编写出的代码相对于没有它编写出的代码,能够更容易复用。代码组织和复用是所有编程的基本手段:编写一次,多次使用,并在一个位置保存代码。

两种支持潜在类型机制的语言实例是Python和C++。Python是动态类型语言(事实上所有类型检查都发生在运行时),而C++是静态语言(类型检查发生在编译期),因此潜在类型机制不要求静态或动态类型检查。






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值