该楼层疑似违规已被系统折叠 隐藏此楼查看此楼

泛型是Java SE 5.0中引入的一项特征,自从这项语言特征出现多年来,我相信,几乎所有的Java程序员不仅听说过,而且使用过它。关于Java泛型的教程,免费的,不免费的,有很多。

Java泛型由来的动机

理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:

1 List box = ...;

2 Apple apple = box.get(0);

上面的代码自身已表达的很清楚:box是一个装有Apple对象的List。get方法返回一个Apple对象实例,这个过程不需要进行类型转换。没有泛型,上面的代码需要写成这样:

1 List box = ...;

2 Apple apple = (Apple) box.get(0);

很明显,泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换操作:编译器保证了这些类型转换的绝对无误。

相对于依赖程序员来记住对象类型、执行类型转换——这会导致程序运行时的失败,很难调试和解决,而编译器能够帮助程序员在编译时强制进行大量的类型检查,发现其中的错误。

泛型的构成

由泛型的构成引出了一个类型变量的概念。根据Java语言规范,类型变量是一种没有限制的标志符,产生于以下几种情况:

泛型类声明

泛型接口声明

泛型方法声明

泛型构造器(constructor)声明

泛型类和接口

如果一个类或接口上有一个或多个类型变量,那它就是泛型。类型变量由尖括号界定,放在类或接口名的后面:

1 public interface List extends Collection {

2 ...

3 }

简单的说,类型变量扮演的角色就如同一个参数,它提供给编译器用来类型检查的信息。

Java类库里的很多类,例如整个Collection框架都做了泛型化的修改。例如,我们在上面的第一段代码里用到的List接口就是一个泛型类。在那段代码里,box是一个List对象,它是一个带有一个Apple类型变量的List接口的类实现的实例。编译器使用这个类型变量参数在get方法被调用、返回一个Apple对象时自动对其进行类型转换。

实际上,这新出现的泛型标记,或者说这个List接口里的get方法是这样的:

1 T get(int index);

get方法实际返回的是一个类型为T的对象,T是在List声明中的类型变量。

泛型方法和构造器(Constructor)

非常的相似,如果方法和构造器上声明了一个或多个类型变量,它们也可以泛型化。

1 public static T getFirst(List list)

这个方法将会接受一个List类型的参数,返回一个T类型的对象。

例子

你既可以使用Java类库里提供的泛型类,也可以使用自己的泛型类。

类型安全的写入数据…

下面的这段代码是个例子,我们创建了一个List实例,然后装入一些数据:

1 List str = new ArrayList();

2 str.add("Hello ");

3 str.add("World.");

如果我们试图在List装入另外一种对象,编译器就会提示错误:

1 str.add(1); // 不能编译

类型安全的读取数据…

当我们在使用List对象时,它总能保证我们得到的是一个String对象:

1 String myString = str.get(0);

遍历

类库中的很多类,诸如Iterator,功能都有所增强,被泛型化。List接口里的iterator()方法现在返回的是Iterator,由它的T next()方法返回的对象不需要再进行类型转换,你直接得到正确的类型。

1 for (Iterator iter = str.iterator(); iter.hasNext();) {

2 String s = iter.next();

3 System.out.print(s);

4 }

使用foreach

“for each”语法同样受益于泛型。前面的代码可以写出这样:

1 for (String s: str) {

2 System.out.print(s);

3 }

这样既容易阅读也容易维护。

自动封装(Autoboxing)和自动拆封(Autounboxing)

在使用Java泛型时,autoboxing/autounboxing这两个特征会被自动的用到,就像下面的这段代码:

1 List ints = new ArrayList();

2 ints.add(0);

3 ints.add(1);

4

5 int sum = 0;

6 for (int i : ints) {

7 sum += i;

8 }

然而,你要明白的一点是,封装和解封会带来性能上的损失,所有,通用要谨慎的使用。

子类型

在Java中,跟其它具有面向对象类型的语言一样,类型的层级可以被设计成这样:

在Java中,类型T的子类型既可以是类型T的一个扩展,也可以是类型T的一个直接或非直接实现(如果T是一个接口的话)。因为“成为某类型的子类型”是一个具有传递性质的关系,如果类型A是B的一个子类型,B是C的子类型,那么A也是C的子类型。在上面的图中:

FujiApple(富士苹果)是Apple的子类型

Apple是Fruit(水果)的子类型

FujiApple(富士苹果)是Fruit(水果)的子类型

所有Java类型都是Object类型的子类型。

B类型的任何一个子类型A都可以被赋给一个类型B的声明:

1 Apple a = ...;

2 Fruit f = a;

泛型类型的子类型

如果一个Apple对象的实例可以被赋给一个Fruit对象的声明,就像上面看到的,那么,List 和 a List之间又是个什么关系呢?更通用些,如果类型A是类型B的子类型,那C 和 C之间是什么关系?

答案会出乎你的意料:没有任何关系。用更通俗的话,泛型类型跟其是否子类型没有任何关系。

这意味着下面的这段代码是无效的:

1 List apples = ...;

2 List fruits = apples;

下面的同样也不允许:

1 List apples;

2 List fruits = ...;

3 apples = fruits;

为什么?一个苹果是一个水果,为什么一箱苹果不能是一箱水果?

在某些事情上,这种说法可以成立,但在类型(类)封装的状态和操作上不成立。如果把一箱苹果当成一箱水果会发生什么情况?

1 List apples = ...;

2 List fruits = apples;

3 fruits.add(new Strawberry());

如果可以这样的话,我们就可以在list里装入各种不同的水果子类型,这是绝对不允许的。

另外一种方式会让你有更直观的理解:一箱水果不是一箱苹果,因为它有可能是一箱另外一种水果,比如草莓(子类型)。

这是一个需要注意的问题吗?

应该不是个大问题。而程序员对此感到意外的最大原因是数组和泛型类型上用法的不一致。对于泛型类型,它们和类型的子类型之间是没什么关系的。而对于数组,它们和子类型是相关的:如果类型A是类型B的子类型,那么A[]是B[]的子类型:

1 Apple[] apples = ...;

2 Fruit[] fruits = apples;

可是稍等一下!如果我们把前面的那个议论中暴露出的问题放在这里,我们仍然能够在一个apple类型的数组中加入strawberrie(草莓)对象:

1 Apple[] apples = new Apple[1];

2 Fruit[] fruits = apples;

3 fruits[0] = new Strawberry();

这样写真的可以编译,但是在运行时抛出ArrayStoreException异常。因为数组的这特点,在存储数据的操作上,Java运行时需要检查类型的兼容性。这种检查,很显然,会带来一定的性能问题,你需要明白这一点。

重申一下,泛型使用起来更安全,能“纠正”Java数组中这种类型上的缺陷。

现在估计你会感到很奇怪,为什么在数组上会有这种类型和子类型的关系,我来给你一个《Java Generics and Collections》这本书上给出的答案:如果它们不相关,你就没有办法把一个未知类型的对象数组传入一个方法里(不经过每次都封装成Object[]),就像下面的:

1 void sort(Object[] o);

泛型出现后,数组的这个个性已经不再有使用上的必要了(下面一部分我们会谈到这个),实际上是应该避免使用。

通配符

在本文的前面的部分里已经说过了泛型类型的子类型的不相关性。但有些时候,我们希望能够像使用普通类型那样使用泛型类型:

向上造型一个泛型对象的引用

向下造型一个泛型对象的引用

向上造型一个泛型对象的引用

例如,假设我们有很多箱子,每个箱子里都装有不同的水果,我们需要找到一种方法能够通用的处理任何一箱水果。更通俗的说法,A是B的子类型,我们需要找到一种方法能够将C类型的实例赋给一个C类型的声明。

为了完成这种操作,我们需要使用带有通配符的扩展声明,就像下面的例子里那样:

1 List apples = new ArrayList();

2 List extends Fruit> fruits = apples;

“? extends”是泛型类型的子类型相关性成为现实:Apple是Fruit的子类型,List 是 List extends Fruit> 的子类型。

向下造型一个泛型对象的引用

现在我来介绍另外一种通配符:? super。如果类型B是类型A的超类型(父类型),那么C 是 C super A> 的子类型:

1 List fruits = new ArrayList();

2 List super Apple> = fruits;

为什么使用通配符标记能行得通?

原理现在已经很明白:我们如何利用这种新的语法结构?

? extends

让我们重新看看这第二部分使用的一个例子,其中谈到了Java数组的子类型相关性:

1 Apple[] apples = new Apple[1];

2 Fruit[] fruits = apples;

3 fruits[0] = new Strawberry();

就像我们看到的,当你往一个声明为Fruit数组的Apple对象数组里加入Strawberry对象后,代码可以编译,但在运行时抛出异常。

现在我们可以使用通配符把相关的代码转换成泛型:因为Apple是Fruit的一个子类,我们使用? extends 通配符,这样就能将一个List对象的定义赋到一个List extends Fruit>的声明上:

1 List apples = new ArrayList();

2 List extends Fruit> fruits = apples;

3 fruits.add(new Strawberry());

这次,代码就编译不过去了!Java编译器会阻止你往一个Fruit list里加入strawberry。在编译时我们就能检测到错误,在运行时就不需要进行检查来确保往列表里加入不兼容的类型了。即使你往list里加入Fruit对象也不行:

1 fruits.add(new Fruit());

你没有办法做到这些。事实上你不能够往一个使用了? extends的数据结构里写入任何的值。

原因非常的简单,你可以这样想:这个? extends T 通配符告诉编译器我们在处理一个类型T的子类型,但我们不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,我们就不允许往里面加入任何这种类型的数据。另一方面,因为我们知道,不论它是什么类型,它总是类型T的子类型,当我们在读取数据时,能确保得到的数据是一个T类型的实例:

1 Fruit get = fruits.get(0);

? super

使用 ? super 通配符一般是什么情况?让我们先看看这个:

1 List fruits = new ArrayList();

2 List super Apple> = fruits;

我们看到fruits指向的是一个装有Apple的某种超类(supertype)的List。同样的,我们不知道究竟是什么超类,但我们知道Apple和任何Apple的子类都跟它的类型兼容。既然这个未知的类型即是Apple,也是GreenApple的超类,我们就可以写入:

1 fruits.add(new Apple());

2 fruits.add(new GreenApple());

如果我们想往里面加入Apple的超类,编译器就会警告你:

1 fruits.add(new Fruit());

2 fruits.add(new Object());

因为我们不知道它是怎样的超类,所有这样的实例就不允许加入。

从这种形式的类型里获取数据又是怎么样的呢?结果表明,你只能取出Object实例:因为我们不知道超类究竟是什么,编译器唯一能保证的只是它是个Object,因为Object是任何Java类型的超类。

存取原则和PECS法则

总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:

如果你想从一个数据类型里获取数据,使用 ? extends 通配符

如果你想把对象写入一个数据结构里,使用 ? super 通配符

如果你既想存,又想取,那就别用通配符。

众智动力java_Java泛型简明教程相关推荐

  1. java web怎么实现跳转,成都汇智动力-JAVAweb路径跳转实现教程

    原标题:成都汇智动力-JAVAweb路径跳转实现教程 web.xml中"/"代表项目根目录: web.xml ... ServletOne /servlet/ServletOne ...

  2. php程序设计简明教程

    序 编写一本有关PHP的简明教程,对PHP选修课程而言,是很必要的. PHP语言是开放源代码语言,由PHP开发小组及全世界的PHP爱好者时刻进行着维护和更新,不断增强其功能,所以在网络上不断地会涌现大 ...

  3. F#简明教程二:F#类型系统和类型推断机制

    [51CTO独家特稿]在上一篇教程<F#与函数式编程概述>中我们了解到F#和函数式编程的一些特点,更多关于F#语言和函数式编程的介绍可以参考51CTO之前对微软MVP赵颉老师的专访< ...

  4. 【二】gym初次入门一学就会---代码详细解析简明教程----平衡杆案例

    相关文章: [一]gym环境安装以及安装遇到的错误解决 [二]gym初次入门一学就会-简明教程 [三]gym简单画图 [四]gym搭建自己的环境,全网最详细版本,3分钟你就学会了! [五]gym搭建自 ...

  5. java集合到线程的考试_成都汇智动力-Java SE考试编程题总结

    原标题:成都汇智动力-Java SE考试编程题总结 线程和进程的区别: (1)进程是运行中的程序,拥有自己独立的内存空间和资源; (2)一个进程可以有一个或多个线程组成,且至少有一个线程称为主线程; ...

  6. java面试题成都_成都汇智动力-java面试——多线程面试题

    原标题:成都汇智动力-java面试--多线程面试题 1.多线程有什么用?发挥多核CPU的优势 防止阻塞 便于建模 2.创建线程的方式继承Thread类 实现Runnable接口 至于哪个好,不用说肯定 ...

  7. python web和java web区别_成都汇智动力-谈谈个人认为的JavaWeb开发与PythonWeb开发的区别...

    原标题:成都汇智动力-谈谈个人认为的JavaWeb开发与PythonWeb开发的区别 今天这篇文章谈一谈Java Web开发和Python Web开发的区别.在这里我并不是鼓励大家从Java Web转 ...

  8. DuiLib入门简明教程

           Duilib 是一款强大的界面开发工具,可以将用户界面和处理逻辑彻底分离,极大地提高用户界面的开发效率. 国内首个开源 的directui 界面库,开放,共享,惠众,共赢,遵循bsd协议 ...

  9. 传智播客mysql视频_传智播客mysql高清视频教程(41集)

    本套课程为传智播客mysql高清视频教程,全套课程有41讲,是mysql入门的优质教材,随着mysql不断发展,现在使用mysql+php做网站已成为主流,如果你想学习动态网页设计,那么建议你选择ph ...

  10. 华为Ascend众智计划项目--3DMPPE_ROOTNET--Pytorch模型迁移至NPU(二)

    系列文章目录 项目信息.本地GPU单卡复现: 华为Ascend众智计划项目–3DMPPE_ROOTNET–Pytorch模型迁移至NPU(一) 模型迁移--本地代码添加: 华为Ascend众智计划项目 ...

最新文章

  1. java用毫秒数做日期计算的一个踩坑记录
  2. 可能是第二好的 Spring OAuth 2.0 文章,艿艿端午在家写了 3 天~
  3. HTML列表标签,讲的明明白白!
  4. [PHP]json_encode增加options参数后支持中文
  5. 防止重复提交表单的两种方法
  6. 计算机关闭节能模式,bios怎么关闭cpu节能模式_bios节能模式怎么设置
  7. Linux tcp数据分节接收,TCP的建立和终止 图解
  8. 华硕触控板无法在Win11中使用的解决办法
  9. python项目方案书模板格式_项目策划书格式范文
  10. mongovue 导入mysql_使用mongovue把sqlserver数据导入mongodb的步骤
  11. 区块链技术视频网站EthCast.com上线
  12. 全新一代智慧园区数字孪生解决方案,为园区运营商和集成商赋能
  13. 什么是Windows安全模式?Windows安全模式详解
  14. Java发送mail报错“java.util.ServiceConfigurationError: com.sun.mail.imap.IMAPProvider not a subtype”
  15. java通过txt读取迷宫地图_java寻找迷宫路径的简单实现示例
  16. (转)iPhone +ipad尺寸规范(界面 图标)
  17. 更换cpu后 unraid 无法启动web,提示PTE Read access is not set
  18. 【CTF WriteUp】2023数字中国创新大赛网络数据安全赛道决赛WP(1)
  19. word press html,wordpress广告插件24款 管理网站广告代码很方便
  20. Hadoop项目结构及其主要作用

热门文章

  1. springboot生成包含特定数字_关于Spring Boot 这可能是全网最好的知识点总结
  2. 如何C语言编程二维数组五位学生总分,C语言编程题(有关二维数组的循环的)...
  3. Oracle RAC tns 00505,Alert Log Errors: 12170 TNS-12535/TNS-00505: Operation Timed Out
  4. python支持双向索引_python3 deque 双向队列创建与使用方法分析
  5. linux命令行经典教程,linux常用命令的经典使用
  6. linux筛选方式,使用grep实现精确过滤的五种方法
  7. 痛与快乐有一个代码是什么_痛与快乐有一个代码是什么_痛苦与快乐
  8. windows利用iis配置反向代理实现ECS内网互通oss
  9. 《从程序员到项目经理》学习笔记
  10. 面试 多线程 MFC CSDN