Java泛型解析(02):通配符限定

     考虑一个这种场景。计算数组中的最大元素。
[code01]

    public class ArrayUtil {
public static <T> T max(T[] array) {
if (array == null || 0 == array.length) { return null ;}
T max = array[0];
for (int i = 1; i < array.length; i++) {
if (max.compareTo(array[i]) < 0) {max = array[i];}
}
return max;
}
}
     细致看看code01里面的代码(代码不完整),使用类型參数T定义一个max局部变量,这几意味着这个max能够是随意的类型。那么max.compareTo(array[i]) 方法的调用的前提是T所属的类中有compareTo方法,怎么能做到这一点呢?别着急,让我们来看看怎样给类型參数进行限定,如今来对code01中的代码进行完好。

[code02]

    public class ArrayUtil {
public static <T extends Comparable<T>> T max(T[] array) {
if (array == null || 0 == array.length) { return null ;}
T max = array[0];
for (int i = 1; i < array.length; i++) {
if (max.compareTo(array[i]) < 0) {max = array[i];}
}
return max;
}
}
     注意看,我们定义类型參数的变化:<T extends Comparable<T>>,这里将T类型限定在Comparable及其全部的子类。是不是非常好奇Comparable明明是一个interfacce,依据所学知识推断,实现interface用的keyword是implements,为什么呢?

     <T extends Bounding Type>,表示T类型应该是绑定类型及其子类型(subType),T和绑定类型能够是类或者接口,使用extendskeyword由于它更接近于子类的概念,另外Java设计者并不打算为Java加入新的keyword如:sub
     假设给T限定多个类型,则须要使用符号"&",如以下格式
[code03]

     <T extends Runnable & Serializable>
     细心的读者可能会发现。这里限定的都是interface。假设限定为class是不是也这么自由的呢?先不急着回答这个问题。我们知道Java中能够实现多个接口,而继承仅仅能是单继承,可想而知。当我们给T限定类型的时候,限定为某个class的时候是有限制的,看看以下几组泛型限定的代码
[code04]

     <T extends Runnable & Serializable & ArrayList> // 错误
<T extends Runnable & ArrayList & Serializable> // 错误
<T extends ArrayList & LinkedList & Serializable> // 错误
<T extends ArrayList & Runnable& Serializable> // 正确
     不难看出,假设要限定T为class的时候,就有一个非常严格的规则,这个class仅仅能放在第一个。最多仅仅能有一个class。事实上非常easy理解,这样一来。就行严格控制T类型是单继承的,遵循Java的规范。
小结:
     1.类型限定仅仅能限定某个类型及其子类。使用keywordextends。
     2.多个类型參数用","隔开。如:<K, V>,多个限定类型用"&"隔开。如: <T extends Runnable & Serializable>
     3.限定interface的时候。对interface的个数和顺序无严格要求,限定class时。则须要将class类型置于第一个,且最多仅仅能存在一个class类型。
钻牛角尖:
     问:类型限定中能够通过 extends 来限定子类型,能否够通过类似superkeyword来限定超类型呢? 
    答:哈哈。问的好。接下来一一揭晓。
    比較遗憾的是,类似<T extends Runnable & Serializable>这种泛型限定子类的语法。来限定超类是没有成为Java中的一个语法规范的,比如:
[code05]

     <T super File & Runnable> // 错误
     code03中的类型參数的定义是错误的。至少眼下Java中没有这种规范来支撑这种语法,怎样解释这个问题,笔者得花一番心思了...
不得不请教面向对象先生了:
     1.面向接口(抽象)编程,而非面向实现编程。这个设计原则告诉我们。方法调用通过高层的抽象类或者接口来进行。详细调用的方法体就是我们实际执行时期传递的详细实现类的实例。这也是多态的一种体现。

我们实现自己的泛型是提供后期应用程序猿使用的。限定一个子类,这就须要我们通过子类来调用方法,而调用的方法体则是这个类的超类的实例。继承结构越往上就可能是abstract的。或者是interface。抽象类和接口是无法实例化对象。这样的反设计让调用面临失败。一旦限定的这个类就是抽象的或者是接口,必然会造成这个泛型类或泛型方法无法使用,导致设计失败。举个样例:

[code06]

     public static <T super Runnable> void test(T runner) {
runner.run();
}
     这个T类型限定为Runnable的超类。这个Runnable是一个接口。无法实例化对象,方法參数runner就是一个不存在的实例,所以这是一个失败的设计。并且这样的语法也无法通过编译器。

     面向对象先生的解释对刚開始学习的人可能有点晦涩难懂。没关系。这里仅仅要知道Java是不能支持这样的泛型限定的。

不管从设计角度,还是从后期扩展的角度。都是说只是去的。

     可是不能这样定义泛型。并不代表Java泛型中就没有 super keyword了,接下来说说泛型中的通配符类型。有了前面的基础。这里恐怕不是问题了。
通配符类型
     通配符类型,相比于固定的泛型类型,它是一个巧妙的解决方式。如:
[code07]

    Couple<? extends Employee>
     表示Couple的泛型类型是Employee或者其子类,Couple<Manager>满足。而Couple<File>不满足了。通配符用 "?" 表示。
     我们来打印一下夫妇类中的wife:
[code08]

     public static void printWife(Couple<Employee> couple) {
Employee wife = couple.getWife();
System. out.println(wife);
}
     code08中的方法參数仅仅能将Employee组成的夫妇传入。貌似经理的如Couple<Manager>就不能了,这有点不合适了,搞得好像Manager还不能结婚了。

所以要想让Manager也能结婚并打印其wife,须要更改我们的设计了:

[code09]

     public static void printWife(Couple<? extends Employee> couple) {
Employee wife = couple.getWife();
System. out .println(wife);
}
     通配符的子类型限定的语法与文章一開始介绍的类型限定有点相似,可是这里有些细节的秘密。
[code10]

     public static <T extends Comparable<T>> T max(T[] array) {...}
public static void printWife(Couple<? extends Employee> couple) {...}
     前者T是提前定义的类型參数,T能够作为一个详细的类型来定义方法的參数类型,局部变量等,T的作用域是整个方法(方法返回值。參数,方法体中局部变量),这样的设计是为了给使用者带来方便,将參数类型的指定权有限制地交给了使用者。

而后者中不存在类型參数的定义,max方法參数的类型是预先定义好的Couple类型,使用者无法在使用的时候指定其它类型,但能够有限制地指定Couple中的泛型參数的类型,

     ?

extends Employee 自身不能单独使用,能够理解为仅仅能寄生在其它泛型类中,作为泛型类一个详细的类型參数,通经常使用于定义阶段,如以下:

[code11]

     public static ? extends Employee printWife() {...} // 错误
public static void printWife(? extends Empoyee employee) {...} // 错误
     使用通配符来定义方法返回值和方法的參数类型在Java中是不同意的!
     弄清楚了前面类型限定和通配符的差别以后。再引入通配符的超类型限定就不是那么难以理解了。
通配符的超类型限定:
     和前面子类型的限定一样,用"?"表示通配符,它的存在必须存在泛型类的类型參数中,如:
[code12]

     Couple<? super Manager>
     格式跟通配符限定子类型一样。用了keywordsuper,可是这两种方式的通配符存在一个隐蔽的差别,让我们来揭晓吧,先看看以下代码:
[code13]、

     Couple<Manager> couple = new Couple<Manager>(new Manager(),new Manager());
Couple<? extends Employee> couple2 = couple;
Employee wife = couple2.getWife();
// couple2.setWife(new Manager()); // 此处编译错误
     Couple<? extends Employee>定义了couple2后能够将getWife和setWife想象成例如以下:
[code14]

     ?

extends Employee getWife() {...}
void setWife(? extends Employee) {...}
     getWife是能够通过的,由于将一个返回值的引用赋给超类Employee是全然能够的,而setWife方法接受的是一个Employee的子类。详细是什么子类,编译器并不知道,拒绝传递不论什么特定的类型。所以couple2.setWife(new Manager())是不能被调用的。

所以通配符的子类限定适用于读取。

     在来看看通配符的超类型限定,即Couple<? super Manager>。getWife和setWife能够想象成:
[code15]

     ? super Manager getWife() {...}
void setWife(? super Manager) {...}
     getWife方法的返回值是Manager的超类型,而Manger的超类型是得不到保证的,虚拟器会将它会给Object,而setWife方法是须要的是Manager的超类型,所以传入随意Manager都是同意的,所以通配符的超类型限定适用于写入。

无限定通配符
     无限定通配符去除了超类型和子类型的规则。只用一个"?

"表示,而且也只能用于指定泛型类的类型參数中。如Couple<?>的形式,此时getWife和setWife方法如:

[code16]

     ?

getWife() {...}
void setWife(?) {...}
     getWife返回值直接付给了Object,而setWife方法是不同意调用的。那么既然这么脆弱,牛逼的Java设计者为什么还要引入这样的通配符呢?在一些简单的操作中,五限定通配符还是实用武之地的。比方:
[code17]

      public static boolean isCoupleComplete(Couple<?

> couple) {
return couple.getWife() != null && couple.getHusband() != null;
}
     这种方法体中,getWife和getHusband返回值都是Object类型的,此时我们仅仅须要推断是否为null就可以,而不须要知道详细的类型是什么,这就发挥了无限定通配符的作用了。发动脑经想一想。这种方法用文章開始所提到类型限定能否够取代呢?自我思考中...
[code18]

     public static <T> boolean isCoupleComplete(Couple<T> couple) {
return couple.getWife() != null && couple.getHusband() != null ;
}
public static <T extends Employee> boolean isCoupleComplete(Couple<T> couple) {
return couple.getWife() != null && couple.getHusband() != null ;
}
     到这里,爱思考的读者可能在思考一个问题,通配符代表了泛型类中的參数类型,在方法体中,怎么去捕获这个參数类型呢?

这里考虑三种通配符的捕获

     1.Couple<?

extends Employee> couple:getWife返回Employee

     2.Couple<?

super Manager> couple:无法捕获,getWife返回Object

     3.Couple<?

> couple:无法捕获,getWife返回Object

     悲催了。仅仅有第一种能捕获,怎么办呢?别着急,看看以下的小魔术:
[code19]

    public static void print(Couple<?> couple) {
printHelp(couple);
}
public static <T> void printHelp(Couple<T> couple) {
T husband = couple.getHusband();
T wife = couple.getWife();
couple.setHusband(wife);
couple.setWife(husband);
System. out.println(husband);
System. out.println(wife);
}
     当须要捕获通配符的时候,能够借助前面所学的类型參数进行辅助。事实上这是一个多余的动作。基本上用不到这么麻烦。这么做是为了把通配符和泛型限定联系起来,巩固一下之前的学习。
总结:
     1.泛型參数的限定,使用extendskeyword,限定多个类型时用"&"隔开。如:<T extends Runnable& Serializable> 
     2.泛型參数限定中,假设限定的类型是class而不是interface,则class必须放在限定类表中的第一个,且最多仅仅能存在一个class。如:<T extends ArrayList & Runnable& Serializable> 
     3.通配符仅仅能用在泛型类的泛型參数中,不能单独使用。

如Couple<?

>、Couple<? extends Employee> 、Couple<? super Manager>

     4.通配符的超类型限定适用于写入,通配符的子类型限定适用于读取,无限定通配符适用于一些非null推断等简单操作。

     5.通配符的捕获能够借助泛型类型限定来辅助。
     这一节内容比較多,须要花点时间好好消化,体会总结中的5点。下一节,说一个深刻点的话题,虚拟机中的针对泛型代码的擦除。

Java泛型解析(04):约束和局限性



=====【感谢亲阅读寻常心的文章,亲若认为此文有帮助,顶一顶亲的支持将给我前进的动力】=====

最新文章

  1. Unable to download data from http://ruby.taobao.org/ &amp; don&#39;t have write permissions for the /Library/Ruby/Gems/2.0.0 directory.
  2. nginx_https
  3. 利用css中的background-position定位图片
  4. 利用 lucene.net 实现高效率的 WildcardQuery ,记一次类似百度搜索下拉关键字联想功能的实现。
  5. js动态创建的元素绑定事件
  6. Pinterest 架构:两年内月 PV 从零到百亿【翻译】
  7. url的编码问题
  8. 使用MyEclipse Swing/Matisse
  9. 奇葩问题:spring+mybaits项目突然出现其中一些Mapper类找不到
  10. HTML5 canvas 绘制精美的图形
  11. VC 编程ANSI环境下读写Unicode文件
  12. huffman编码【代码】
  13. 打字机效果-so easy
  14. Jstree 使用CheckBox插件 选中父节点时被禁用的子节点也会选中问题
  15. traefik 结合 docker-compose 的快速安装及使用
  16. [paper reading] C-MIL: Continuation Multiple Instance Learning for Weakly Supervised Object Detection CVPR2019
  17. CSS染色图标(图片)
  18. 【】tensorflow学习笔记
  19. mothur trim.seqs 去除PCR引物
  20. table maker&#39;s delimma

热门文章

  1. vue使用jsonp
  2. Cronolog 分割 Tomcat8 Catalina.out日志 (转)
  3. 浅谈Normalize.css
  4. Android Set与List之间转化
  5. maven 配置Project Facets时further configuration available不出来问题
  6. python3中numpy函数的argsort()
  7. 6.2、Android硬件访问服务编写系统代码
  8. sql海量数据优化
  9. POJ 1887 Testing the CATCHER(LIS的反面 最大递减子序列)
  10. tc