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):约束和局限性



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

Java泛型解析(02):通配符限定的更多相关文章

  1. Java泛型解析(03):虚拟机运行泛型代码

    Java泛型解析(03):虚拟机运行泛型代码      Java虚拟机是不存在泛型类型对象的,全部的对象都属于普通类,甚至在泛型实现的早起版本号中,可以将使用泛型的程序编译为在1.0虚拟机上可以执行的 ...

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

    Java泛型解析(04):约束和局限性           前两节.认识和学习了泛型的限定以及通配符.刚開始学习的人可能须要一些时间去体会到泛型程序设计的优点和力量,特别是想成为库程序猿的同学就须要下 ...

  3. Java泛型解析(01):认识泛型

    Java泛型解析(01):认识泛型 What      Java从1.0版本号到如今的8.中间Java5中发生了一个非常重要的变化,那就是泛型机制的引入.Java5引入了泛型,主要还是为了满足在199 ...

  4. Java泛型中的通配符T,E,K,V

    Java泛型中的通配符T,E,K,V 1.泛型的好处 2.泛型中的通配符 2.1 T,E,K,V,? 2.2 ?无界通配符 2.3 上界通配符 < ? extends E> 2.4 下界通 ...

  5. Java泛型中的通配符

    Java泛型中的通配符可以直接定义泛型类型的参数.而不用把该函数定义成泛型函数. public class GenericsTest { public static void main(String[ ...

  6. 关于JAVA泛型中的通配符类型

    之前对JAVA一知半解时就拿起weiss的数据结构开始看,大部分数据结构实现都是采取通配符的思想,好处不言而喻. 首先建立两个类employee和manager,继承关系如下.其次Pair类是一个简单 ...

  7. Java泛型二:通配符的使用

    原文地址http://blog.csdn.net/lonelyroamer/article/details/7927212 通配符有三种: 1.无限定通配符   形式<?> 2.上边界限定 ...

  8. JAVA 泛型中的通配符 T,E,K,V,?

    前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据 ...

  9. 【转】聊一聊-JAVA 泛型中的通配符 T,E,K,V,?

    原文:https://juejin.im/post/5d5789d26fb9a06ad0056bd9 前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型 ...

随机推荐

  1. vue.js的基础与语法

    Vue的实例 创建第一个实例: {{}} 被称之为插值表达式.可以用来进行文本插值. <!DOCTYPE html> <html lang="en"> &l ...

  2. css实现简单的告警提示动画效果

    需求:css实现简单的告警提示动画效果,当接收到实时信息的时候,页面弹出告警信息的动画效果 <!DOCTYPE html> <html lang="en"> ...

  3. SafeSEH原理及绕过技术浅析

    SafeSEH原理及绕过技术浅析 作者:magictong 时间:2012年3月16日星期五 摘要:主要介绍SafeSEH的基本原理和SafeSEH的绕过技术,重点在原理介绍. 关键词:SafeSEH ...

  4. stm32单片机的封装

    接着去查看VREF...

  5. Flask项目之手机端租房网站的实战开发(一)

    说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家! 一丶项目介绍 产品:关于手机移动端的租房网站 角色:在这个产品中用户包括房东与房客 功能:房东可以在这个平台发布自己的房屋,房客可 ...

  6. jmeter--使用badboy录制脚本

    JMeter录制脚本有多种方法,其中最常见的方法是用第三方工具badboy录制,另外还有JMeter自身设置(Http代理服务器+IE浏览器设置)来录制脚本,但这种方法录制出来的脚本比较多且比较乱,个 ...

  7. 微服务实战(四):服务发现的可行方案以及实践案例 - DockOne.io

    原文:微服务实战(四):服务发现的可行方案以及实践案例 - DockOne.io 这是关于使用微服务架构创建应用系列的第四篇文章.第一篇介绍了微服务架构的模式,讨论了使用微服务架构的优缺点.第二和第三 ...

  8. Spring+Struts2+Hibernate的整合

    这篇主要采用Maven搭建Spring+Struts2+Hibernate的整合项目,复习一下SSH框架,虽然spring提供自己的MVC框架, 但是Spring也提供和其他框架的无缝整合,采用组件形 ...

  9. [D3] Basic Interactivity with D3 v4

    Data visualizations are a lot more interesting when they’re interactive. Whether it’s clicks, roll o ...

  10. netty检测系统工具PlatformDependent

    1. 检测jdk版本 @SuppressWarnings("LoopStatementThatDoesntLoop") private static int javaVersion ...