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. Spark 概念学习系列之Spark存储管理机制

    Spark存储管理机制 概要 01 存储管理概述 02 RDD持久化 03 Shuffle数据存储 04 广播变量与累加器 01 存储管理概述 思考: RDD,我们可以直接使用而无须关心它的实现细节, ...

  2. Thinkphp5创建控制器

    今天我们就来创建一个控制器: <?php namespace app\index\controller; use think\Controller; class Test extends Con ...

  3. [Angular] Style HTML elements in Angular using ngStyle

    We will learn how to make use of the ngStyle directive to directly add multiple style attributes to ...

  4. 微信支付v2开发(3) JS API支付

    本文介绍如何使用JS API支付接口完成微信支付. 一.JS API支付接口(getBrandWCPayRequest) 微信JS API只能在微信内置浏览器中使用,其他浏览器调用无效.微信提供get ...

  5. hadoop组件及其作用

    1.hadoop有三个主要的核心组件:HDFS(分布式文件存储).MAPREDUCE(分布式的计算).YARN(资源调度),现在云计算包括大数据和虚拟化进行支撑. 在HADOOP(hdfs.MAPRE ...

  6. synchronized和ReentrantLock区别

    一.什么是sychronized sychronized是java中最基本同步互斥的手段,可以修饰代码块,方法,类. 在修饰代码块的时候需要一个reference对象作为锁的对象. 在修饰方法的时候默 ...

  7. CSS伪元素与伪类的区别

    伪类和伪元素介绍 伪类:伪类选择元素基于的是当前元素处于的状态,或者说元素当前所具有的特性,而不是元素的id.class.属性等静态的标志.由于状态是动态变化的,所以一个元素达到一个特定状态时,它可能 ...

  8. jquery-validate使用.md

    html <form id="s_form" class="form-horizontal" action="http://www.baidu. ...

  9. 关于DMA

    用串口在dma中发东西的时候,,, 要判断DMA里是不是由东西,是不是在占用 当多个外设再用DMA的时候,,,要查看DMA有没有占用 一包数没发完,不要再传另一包

  10. 在此页上的 ActiveX 控件和本页上的其它部份的交互可能不安全

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息http://xqy266.blogbus.com/logs/66258230.html 在EOS6的项目中,如果采用VC++开发的Active ...