本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多(Java)码农和想成为(Java)码农的人。

目录

  1. 介绍
  2. 再谈Java中的类型
  3. 为什么需要泛型?
  4. Java中的泛型
  5. 泛型类型
  6. 泛型方法
  7. 总结

介绍

还记得我在这篇文章(我的Java Web之路32 - Spring MVC基于注解的控制器)中列举的Handler方法支持的众多返回值类型和注解吗?其中有不少是如下形式的:

  • HttpEntity, ResponseEntity
  • DeferredResult
  • Callable
  • ListenableFuture, java.util.concurrent.CompletionStage, java.util.concurrent.CompletableFuture

还有这篇文章(我的Java Web之路52 - Spring JDBC初步使用)提到 RowMapper 接口是一个泛型接口,使用时是这样的:

new RowMapper() { ... }

还有这篇文章(我的Java Web之路55 - ORM框架(MyBatis)初步使用)中我们定义HouseMapper接口时,其中一个方法的返回值使用了泛型接口List:

List selectAll();

还有这篇文章(我的Java Web之路58 - Spring整合ORM(MyBatis)2)也提到MapperFactoryBean实际上是一个泛型类,使用基于Java配置的方式如下:

@Beanpublic MapperFactoryBean houseMapper() throws Exception { ... }

这些类和接口使用时都在类名或接口名后面添加了一对尖括号括起其他类名的内容,这就是Java中的泛型,本篇文章就粗略介绍一下,让我们对它有个基础的认识,以后遇到它就能够理解了。

再谈Java中的类型

大家都知道,Java语言虽然是一种面向对象的编程语言,但同时也是一种强类型的编程语言,即任何一个变量都需要先声明它的类型之后才能使用它。关于类型和变量的一些知识,大家可以参考这篇文章和这篇文章。

总的来说,Java中的类型有两种,一种是基本类型(英文是primitive types),包括八个:byte、short、int、long、float、double、char、boolean 。

另一种是引用类型(英文是reference),因为引用类型是指向某一个对象/实例的,所以如果某个变量是引用类型的话,通常我们说该变量是某某类的引用,即该引用所指向的对象/实例的类型是某某类 / 接口。

所以,我们把基本类型和各种类 / 接口都统称为类型,即每一个类 / 接口就是一种类型,也就是说我们可以无限扩展类型,因为我们可以定义无限多个类 / 接口。

为什么需要泛型?

解释了Java中的类型之后,我们再来思考一下为什么需要泛型呢?或者说泛型解决了什么问题呢?

我们经常会遇到这种情况,一些代码逻辑(归为算法也未尝不可,就当是广义上的算法吧)实际上与它处理何种类型的数据是无关的。

举个例子,就拿上面我们经常用到的列表(List)这个类/接口所代表的算法来说,它就好像是现实中的火车一样(可能有些不太恰当),一节一节的,想装旅客就装旅客,想装某种货物就装某种货物,因此就有这样的方法:

E get(int n); //获取第N节车厢的东西,可能是旅客,也可能是其他某种货物E set(int n, E element); //将旅客或者其他某种货物装进第N节车厢

当然,列表(List)这个接口还有很多方法,这里只是拿出这两个来举例。可以看到,列表(List)这种算法(实际上是数据结构)独立于各种数据类型,即它可以容纳各种数据类型的数据。

反过来说,假如没有泛型的话,我们就需要为每一种类型设计一种List,比如:

interface 旅客List { 旅客 get(int n); 旅客 set(int n, 旅客 element);}interface 苹果List { 苹果 get(int n); 苹果 set(int n, 苹果 element);}//必要的话,还需要定义其他List

不知道大家有没有发现上述代码的最大问题是什么?

没错,就是重复啊!除了几个地方的数据类型不同,其他代码都是相同的。所以说,泛型本质上就是解决代码重复问题的。有了泛型,你就可以这样定义List:

interface List { T get(int n); T set(int n, T element);}

使用的时候,我们再指定是何种类型的List:

List list_旅客;List list_苹果;

或许你会想到,既然在Java语言中,一切都是Object类的子类(除了那八个基本类型外),我们可以用Object类来设计List啊:

interface List { Object get(int n); Object set(int n, Object element);}

使用的时候,将类型强制转换不就可以了吗:

List list_旅客;list_旅客.set(0, 旅客A);旅客 旅客A = (旅客)list_旅客.get(0);

不过,这样一来我们就需要使用强制类型转换,而强制类型转换是非常不安全的,比如,往往在某个地方将旅客塞进了某个List,而使用的时候却将它强制转换成苹果,这不就出错了吗。特别是Java编译器是不能发现此类错误的,只有在程序运行时才能发现(错误是越早发现越好)。

而使用泛型,我们在定义某个List变量时就可以指定该List是用来装何种类型的数据的,一来Java编译器可以发现此错误,因为它解析代码的时候就能够记住指定的类型啊;二来我们无需使用强制类型转换。

事实上,Java的泛型在底层就是使用Object来实现的,只不过是由Java编译器为我们进行强制类型转换。

综上所述,泛型有如下好处:

  • 消除重复(可以编写独立于类型的算法,即泛型算法);
  • 编译时就能进行类型检测;
  • 无需使用强制类型转换。

Java中的泛型

Java中的泛型分两种:

  • 泛型类型
  • 泛型方法

泛型对应的英文单词是 generic :

adj.一般的; 普通的; 通用的; 无厂家商标的; 无商标的;

n.同“a generic drug";

[例句]Parmesan is a generic term used to describe a family of hard Italian cheeses.

帕尔玛干酪是意大利硬奶酪的通称。

[其他]复数:generics

可以看到,泛型这个译法一方面与 generic 的本义(泛)是相符的,另一方面又兼顾了编程领域的含义(类型)。

泛型的本质是将类型变为一种参数,这就叫做类型的参数化(parameterized over types)。我们可以拿它与普通变量的参数化进行类比,这样有助于我们的理解和记忆:

要注意,形参的英文单词是 parameter;实参的英文单词是 argument 。

所以,类型形参就是 type parameter;类型实参就是 type argument 。

泛型类型

泛型类型又包括两种:

  • 泛型类
  • 泛型接口

泛型类的定义只需要在类名之后添加一对尖括号,然后在尖括号中声明若干个类型形参

new RowMapper() { ... }

泛型接口的定义也是类似:

List selectAll();

当然,尖括号中声明的类型形参就可以在该类或接口中使用了,不管是在属性中还是在方法中都可以使用,比如:

@Beanpublic MapperFactoryBean houseMapper() throws Exception { ... }

由于历史原因,泛型是在JDK的某个版本引入的,所以JDK中存在将原来不是泛型的类型,在后来的版本中改造成了泛型类型,而为了与之前的版本兼容,所以原来不是泛型的类型仍然可以使用,于是Java就引入原始类型(Raw Type)的概念。

举个例子,ArrayList原来不是泛型,后来被改造成了泛型:

public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable

所以,不使用泛型方式的ArrayList就叫原始类型,我们仍然可以定义原始类型的变量,甚至将泛型类型的对象赋值给该变量,当然,反过来也可以,如下:

ArrayList arrayList = new ArrayList();ArrayList listOfInt = list;

不过,这种代码极其不安全,大家最好尽量避免,一般IDE都会给出提示,Java编译器在编译时也会给出警告。

类型形参的命名也有一些约定俗成的规定,但你也可以不遵从:

E - Element:表示元素的类型,通常用在表示集合、容器等概念的泛型类 / 接口中。K - Key:表示键的类型,通常用在映射概念的泛型类 / 接口中。N - NumberT - Type:一般化的命名。V - Value:表示值的类型,通常用在映射概念的泛型类 / 接口中。S,U,V 等 - 一般化的命名,有多个类型形参时使用。

泛型方法

泛型方法是指在方法的定义中有自己的类型形参的声明,如果仅仅使用了泛型类型已经声明的形参,那就不算是泛型方法,举一个官方文档中的例子:

public class Util { public static  boolean compare(Pair p1, Pair p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); }}public class Pair { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } public K getKey() { return key; } public V getValue() { return value; }}

泛型方法的类型形参的声明是放在方法返回值的前面,也是使用尖括号将若干形参括起来的形式,如上面的 compare() 方法的定义。

实际调用方法时,可以在方法名前写上类型实参,如:

Pair p1 = new Pair<>(1, "apple");Pair p2 = new Pair<>(2, "pear");boolean same = Util.compare(p1, p2);

当然,前辈们已经把Java编译器实现的足够智能,就算你不写类型实参,它也能够从方法实参的类型中推断出来:

Pair p1 = new Pair<>(1, "apple");Pair p2 = new Pair<>(2, "pear");boolean same = Util.compare(p1, p2);

Java编译器的这个功能就叫做类型推断(type inference)。

总结

本篇文章介绍了Java泛型的基本知识,原理上也是很简单的,大家只要把它当做普通类/接口和普通方法即可,只不过是多了一些语法而已。我们只需要记住,泛型主要还是为了

  • 消除重复
  • 和实现类型安全。

当然,泛型还有很多内容,比如:

  • 类型形参可以设定一些边界,比如只允许某个类的子类或父类当做类型实参。
  • 泛型类/接口的继承有些不同,并不是说类型实参有父子关系,它们的泛型类就拥有父子关系,事实是它们没有任何关系。
  • 泛型中的通配符的使用。
  • 类型推断。
  • 类型擦除。
  • 一些最佳实践。
  • 等等。

这些内容以后慢慢介绍。

java定义list_我的Java Web之路59 - Java中的泛型相关推荐

  1. java定义一个eat方法_小黄鸭系列java基础知识 | java中的方法

    前言 今天我们要探讨的问题,是java基础语法的最后一个问题,也就是java中的方法,今天主要从以下几个方面来介绍: 方法是什么(定义) 方法的分类 方法的调用 应该说,学完今天的知识,你至少应该看懂 ...

  2. java 抛出异常_我的Java Web之路51 - Java异常基础

    本系列文章旨在记录和总结自己在Java Web开发之路上的知识点.经验.问题和思考,希望能帮助更多(Java)码农和想成为(Java)码农的人. 目录 介绍 异常的本质 Java异常的设计 Java异 ...

  3. java定义全局变量_矮油,你知道什么是 Java变量的作用域 嘛?

    变量的作用域规定了变量所能使用的范围,只有在作用域范围内变量才能被使用.根据变量声明地点的不同,变量的作用域也不同. 根据作用域的不同,一般将变量分为不同的类型:成员变量和局部变量.下面对这几种变量进 ...

  4. java定义属性时用this_(转载)深入Java关键字this的用法的总结

    合它的含义并不完全相同,使用不当还会出现错误, 本文对this的几种用法和出现的问题进行了分析详解. 关键词:类:对象:this:成员变量:方法:构造方法 中,Java语言提供了丰富的类(Class) ...

  5. java 定义变量时 赋值与不赋值_探究Java中基本类型和部分包装类在声明变量时不赋值的情况下java给他们的默认赋值...

    探究Java中基本类型和部分包装类在声明变量时不赋值的情况下java给他们的默认赋值 当基本数据类型作为普通变量(八大基本类型: byte,char,boolean,short,int,long,fl ...

  6. Eclipse 创建web项目后没有 Java EE 5 Library,没有web开发相关基础java包,myeclipse中有。...

    右键单击工程-build path-cfg build path-libraries-add library-Server Runtime前提是你已经配置了server,如果没有这个选项请确认你的ec ...

  7. JAVA定义一个多边形类_如何在每个数据类别中绘制多个多边形?

    一些代码在SIBRE的捆绑演示数据集上执行此操作 . 在这个例子中,我们尝试使用ggplot2创建后椭圆的多个样本的一些图 . library(SIBER) library(ggplot2) libr ...

  8. java光标位置无效_ResultSet.getString(1)引发java.sql.SQLException:当前光标位置的无效操作...

    当我运行以下servlet时: // package projectcodes; public void doPost(HttpServletRequest request,HttpServletRe ...

  9. 【程序员养成之路】Java基础篇 8-流进流出的IO流(二)

    以下内容若有误,欢迎私信我或在下方留言,谢谢^_− 目录 IO流(二) 1.特殊操作流 1.1 标准流 1.2 打印流 1.3 对象序列化流 1.4 Properties 拓展1:比较字节流和字节缓冲 ...

最新文章

  1. 【微信小程序企业级开发教程】事件相关详解
  2. 10月了,聊聊我今年参加秋招的真实感受
  3. python心得体会300字_有没有简单一点的 Python 小例子或小项目?
  4. Packet Tracer 5.0 建构 CCNA 实验攻略——路由器实现 Vlan 间通信
  5. gradle 上传jar包_Gradle学习记录014 关于依赖的声明
  6. 如何杀掉本地和远程NT系统进程
  7. Windows7 Xp Mode部署与讲解
  8. Android Picasso最详细的使用指南
  9. window.showModalDialog乱码(完美)解决方案
  10. C#在线打开编辑保存Excel文件[pageoffice]
  11. SQLSERVER中RANK OVER(PARTITION BY)的用法
  12. nginx开机启动脚本
  13. 从“黑掉Github”学Web安全开发
  14. 连表查询 mysql实例_mysql中各种常见join连表查询实例总结
  15. MSC-VO: 基于曼哈顿和结构约束的视觉里程计(CVPR 2021)
  16. 电影推荐——基于关联分析Apriori算法
  17. Android - 控件android:ems属性
  18. 趣味证书制作生成微信小程序源码
  19. 星星之火-26:3G CDMA系统中单用户的扩频原理
  20. 14个最常用的app测试工具推荐,拿走不谢!

热门文章

  1. android 酷炫倒计时,android 好用的倒计时
  2. linux忘记mysql密码_linux下忘记mysql root密码解决办法 | 系统运维
  3. linux 取消混杂模式,Linux下网卡混杂模式设置和取消
  4. 2018安徽省计算机一级试题答案,2018年计算机等一级考试试题100题及参考答案.docx...
  5. dep指定版本 go_Go 1.12 版本的新特性
  6. ASP.NET Core IdentityServer4 新手上路
  7. shell中的常用通配符,字符类
  8. 以太坊智能合约Hello World示例程序
  9. Win7\xp添加虚拟网Microsoft Loopback Adapter
  10. DEV控件中GridView中的复选框与CheckBox实现联动的全选功能