专栏文章导航

Java泛型入门篇: 泛型类、泛型接口以及泛型方法
Java泛型进阶篇: 无界通配符、上界通配符以及下界通配符
Java泛型原理篇: 类型擦除以及桥接方法


文章目录

  • 前言
  • 1. 什么是泛型
  • 2. 泛型的使用
    • 泛型类
      • 使用方法
      • 泛型类继承
    • 泛型接口
      • 使用方法
      • 泛型接口实现类
    • 泛型方法
      • 使用方法
      • 静态泛型方法
      • 所属类为泛型类/泛型接口

前言

    我们在平时的开发当中基本上无时无刻都在使用泛型,尤其是涉及到集合、多态或自定义类的场景,可以说是泛型是一个十分重要的特性。但包括我在内的大多数人,对泛型的掌握不够深刻,大多数情况下只在使用List、Map等集合的时候才会使用到,其他情况下基本不用或者说不会用,在编码以及设计的时候也就无法做到得心应手,所以是时候来梳理一下有关泛型的知识了。

1. 什么是泛型

    以经典的集合为例,在引进泛型之前,我们定义的集合对象中可以存放任意类型的数据,即Object。而在获取元素的时候,大多数时间都需要我们强制转换该对象至某一数据类型,后续才可以调用其相关方法或得到某些属性值,也就是需要明确的知道每一个元素的类型,此时非常容易引发ClassCastException。可怕的是,在编译期间并没有任何迹象表现出代码有问题,只有当运行时程序执行至此并且触发某种逻辑时才会引起异常,如:

List list = new ArrayList();
list.add("1");
list.add(2);
list.add(Collections.emptyList());

    可以看到,list中能存放任意类型,即Object类型或其子类类型。而当我们取元素时,一般需要进行强制类型转换:

String str;
for (Object obj : list) {str = (String) obj;System.out.println(str.length);
}

    在我们写完此段程序时,IDE并没有提示有任何问题,但是当程序执行时就会抛出ClassCastException,因为list中的第2、3个元素并不是String类型。
    既然是转型错误,那么我们使用instance of关键字进行所属类型判断应该就不会有问题了,改进后的代码如下:

for (Object obj : list) {if (obj instanceof String) {System.out.println((String) obj);} else if (obj instanceof Integer) {System.out.println((Integer) obj);} else if (obj instanceof List) {System.out.println((List) obj);}
}

    改进后的代码执行后确实没有再触发异常了,但问题是如果我们添加新的数据类型(如添加一个浮点数),那么程序就要加一种对应的类型判断。在日常开发中,程序都是有可能对外提供的,我们也不知道调用者到底会传递什么类型,那么要检查多少种类型才能让程序健壮呢,10种,100种还是1000种?增加这么多判断的同时,代码也就失去了可读性和维护性。甚至调用者会传递自定义的类型,这个时候就更加不能采取这种手段解决问题了。

    从Java5以后,我们可以使用新特性泛型(Generic)来解决这一问题,它提供了编译期的类型安全检查机制,即在编译期间就可以判断类型是否匹配,无需等到运行时,并且在获取元素时也无需手动进行强制转换。

  可以把泛型理解成一个标签或标记。假如有一个箱子,我们能够向箱子中放入任意东西,比如球鞋、手机、衣服等,没有任何限制。在从箱子里取出东西时需要辨别具体是什么东西,才能够使用这件物品,如果提前预判是什么物品就有可能会出差错。泛型就类似一个便利贴,贴在箱子上,上边写着"球鞋",那么我们就知道这个箱子是专门放球鞋的,无法放入其他物品,从箱子里取出的时候,也不用去辨别,因为里边的东西就是球鞋,拿出来直接穿就行。(有一种情况除外,在后续的文章中会介绍)

2. 泛型的使用

  泛型的本质就是参数化类型,即所有的操作类型被指定为一个参数,可在整个类或方法中进行传递。

泛型类

  见名知意,泛型类就是将泛型标识定义在类上,他与普通类的创建类似,只不过多了<>来存放泛型标识。

使用方法

public class 类名 <泛型标识,泛型标识2,...> {private 泛型标识 变量名;
}

  泛型标识可以为任意字符串,一般只用一个大写的字母。平时常见的有E, T, K, V,当然也可以定义为A,B,C,D等,泛型标识的数量也可以是任意个。
  在泛型类中定义的泛型可以理解为类型的形式参数,可以用来做成员变量,也可以作为方法的入参或方法的返回值类型,如:

public class TestGeneric<T> {// 做成员变量private T t;// 做方法入参public TestGeneric(T t) {this.t = t;}// 做方法返回值类型public T getT() {return t;}
}

  创建泛型类对象与创建普通对象相似,但多了<>来声明实际的泛型类型,可以理解为实参,即从当前创建的对象中的成员变量、方法或返回值类型由T转换为了所传递的类型。

类名<数据类型> 对象名 = new 类名<数据类型>();

  比如经常使用的ListArrayList

List<String> list = new ArrayList<String>();

  上述写法比较啰嗦,前后共定义了两次泛型的实际数据类型。在Java7之后,后边的泛型类型可以不用写,程序会自动识别,后边就变成了一个空的<>

List<String> list = new ArrayList<>();

  如果不指定泛型类型时,会默认为Object
  在泛型介绍中提到,泛型的两个好处,一是可以在编译期校验数据类型,二是获取时可以不用强制类型转换,我们平时在使用的时候也确实如此。

List<String> list = new ArrayList<>();
list.add("1");
// 编译错误,只能存储String类型
list.add(2);
// 编译错误,只能存储String类型
list.add(Collections.emptyList());// 无需强制转型,直接就可以获取String类型
String str = list.get(0);

泛型类继承

  • 子类是泛型类
      当子类也是泛型类,并且也想指定父类的泛型类型时,两个类的泛型类型必须一致。
public class TestList<T> extends ArrayList<T> {}class Test {public static void main(String[] args) {List<String> list = new TestList<>();list.add("1");}
}

  如果泛型类型不一致则会编译错误

// Cannot resolve symbol 'E'
public class TestList<T> extends ArrayList<E> {}

  分析:上文中提出泛型实质上为参数化类型,泛型T可以在实例化的时候指定,如TestList<String> list = new TestList<>();,也就是泛型类型T的实际类型为String,父类中的泛型类型同样为T,也为String类型。但如果不一致的话,如上例中,父类中的泛型类型E无法通过参数化进行传递,也就不能确定实际的类型,编译也就不通过了。

  • 子类不是泛型类
      当子类不是泛型类,但又想指定父类泛型类形时,此时需要明确的指出父类中泛型的类型。
public class TestList2 extends ArrayList<Integer> {}class Test2 {public static void main(String[] args) {TestList2 list = new TestList2();list.add(1234);}
}

  分析:由于子类不是泛型类,自然也就无法将泛型类型传递给父类,所以需要在定义类时指定具体的泛型类型,否则就会编译错误。如上例所示,将ArrayList的泛型类型指定为Integer,那么使用TestList2时,就可以调用ArrayList<Integer>的相关方法了。


泛型接口

使用方法

  泛型接口的定义与泛型类完全一致

public interface 接口名 <泛型标识1, 泛型标识2...> {}

  最典型的例子就是熟知的框架Mybatis-PlusMapper以及IBaseService接口,下图截取自官网Gitee

泛型接口实现类

  • 实现类是泛型类
      同泛型类继承,如果实现类是泛型类,并且也想指定父接口的泛型类型,那么两个泛型类型必须一致。
public class TestList3<E> implements List<E> {@Overridepublic int size() {return 0;}...

  如果泛型类型不一致则会编译错误

// Cannot resolve symbol 'E'
public class TestList3<T> implements List<E> {}
  • 实现类不是泛型类

  同泛型类继承,如果实现类不是泛型类,又想指定父接口的泛型类形时,那么需要明确的指出具体的类型。

public class TestList4 implements List<Integer> {@Overridepublic int size() {return 0;}...
}class Test4 {public static void main(String[] args) {TestList4 list = new TestList4();list.add(1234);}
}

泛型方法

  泛型方法就是在定义方法时额外定义泛型标识,在使用方法时传递该泛型类型。需要注意的是,并不是带有泛型标识的方法就是泛型方法。如:

public class Box<T> {private List<T> list = new ArrayList<>();public void put(T t) {list.add(t);}public T get(int index) {if (index >= list.size()) {throw new IllegalArgumentException("Illegal index");}return list.get(index);}
}

  其中,虽然put()以及get()方法的参数或返回值类型都包含了泛型标识T,但是这两个方法并不是泛型方法,而是使用了泛型类Box中定义的泛型类型的普通方法,T是通过泛型类定义的泛型标识T传递而来的,而不是在方法中定义的。

  泛型方法是不依托于泛型类或泛型接口的,即普通类中也可以定义泛型方法。

使用方法

  在返回值类型前使用<>来定义该方法的泛型标识。

public  <泛型标识1, 泛型标识2...> 返回值类型 方法名(参数1...) {...
}
public class TestUtil {public <T> T getFirst(List<T> list) {if (CollectionUtils.isEmpty(list)) {return null;}return list.get(0);}public static void main(String[] args) {TestUtil testUtil = new TestUtil();List<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);Integer num = testUtil.getFirst(list);// 结果为1System.out.println(num);List<String> list2 = new ArrayList<>();list2.add("a");list2.add("b");list2.add("c");String letter = testUtil.getFirst(list2);// 结果为aSystem.out.println(letter);}
}

  上例中,TestUtil并不是泛型类,但是getFirst()却是泛型方法,它使用了<T>作为泛型标识,在参数中以及返回值进行传递。

静态泛型方法

  如果是想定义静态泛型方法,需在泛型标识前加static,上述方法可以改为

public static <T> T getFirst(List<T> list) {if (CollectionUtils.isEmpty(list)) {return null;}return list.get(0);
}

所属类为泛型类/泛型接口

  当出现这种情况时,可以同时使用两者的泛型标识。

public class Test<T> {public <E> void test(T t, E e) {// Do Something...}
}

  当泛型方法存在于一个泛型类或泛型接口,并且两者的泛型标识一致时,此时该方法中的泛型标识为方法中定义的泛型标识,而不是泛型类/泛型接口中定义的。

public class TestGeneric<T> {public <T> T test(T t) {return t;} public static void Main(String[] args) {TestGeneric<String> testGeneric = new TestGeneric<>();// 实参与返回值类型都不是创建泛型类对象时定义的String,而是IntegerInteger number = testGeneric.get(1);}
}

Java泛型入门篇: 泛型类、泛型接口以及泛型方法相关推荐

  1. Java泛型进阶篇: 无界通配符、上界通配符以及下界通配符

    专栏文章导航 Java泛型入门篇: 泛型类.泛型接口以及泛型方法 Java泛型进阶篇: 无界通配符.上界通配符以及下界通配符 Java泛型原理篇: 类型擦除以及桥接方法 文章目录 前言 1. 无界通配 ...

  2. JAVA中整型常量的长度,Java基础入门篇(三)——Java常量、变量,

    Java基础入门篇(三)--Java常量.变量, 一.Java常量 (一)什么是常量 常量指的是在程序中固定不变的值,是不能改变的数据.在Java中,常量包括整型常量.浮点型常量.布尔常量.字符常量等 ...

  3. Java爬虫入门篇---Jsoup工具

    Java爬虫入门篇---Jsoup工具 前言 准备工作 获取文本数据 获取页面中所有的图片 前言 pythoy的scrapy框架是大名鼎鼎,Jsoup则为Java框架的爬虫 准备工作 1.下载jsou ...

  4. java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一,大厂 HR 如何面试

    写在最前面,我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家.扫码加微信好友进[程序员面试学习交流群],免费领取.也欢迎各位一起在群里探讨技术. 对j ...

  5. java 泛型详解-绝对是对泛型方法讲解

    Reference:  http://blog.csdn.net/s10461/article/details/53941091 1. 概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模 ...

  6. java 泛型的接口_Java 泛型接口

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 Java教程 - 如何使用Java泛型接口 在Java中,我们创建泛型接口. 语法 这是一个泛型接口的泛型语法: interface interface- ...

  7. javascript java html_JS入门篇(二):在html中如何使用Javascript

    原标题:JS入门篇(二):在html中如何使用Javascript (1)java的使用 HTML 中的脚本必须位于 <> 与 > 标签之间.脚本可被放置在 HTML 页面的 和 部 ...

  8. 穿越 java | 快速入门篇 - 第1节 计算机基础知识

    主题:计算机基础知识 开发环境 更多干货 定义 作用 组成元件 CPU 内存 cpu里的高速缓存 BIOS软件(基础输入输出系统) CMOS芯片 机械硬盘 组成 数据存取过程 文件编码 ASCII G ...

  9. Java框架入门篇——Spring

    文章目录 前言 1.Spring是什么? 2.Spring 的优点? 3.Spring的IoC理解 4.Spring的AOP理解 5.BeanFactory和ApplicationContext有什么 ...

  10. 什么是泛型?- 泛型入门篇

    目录 1.什么是泛型? 2.泛型是怎么编译的 泛型的编译机制:擦除机制 1.什么是泛型? 泛型其实就是将类型作为参数传递,泛型允许程序员在编写代码时使用一些以后才指定的类型 ,在实例化该类时将想要的类 ...

最新文章

  1. python电子时钟包装盒_python 电子时钟
  2. 如何在自己的信息管理系统里集成第三方权限控制组件 - 设计一个漂亮的WEB界面...
  3. OpenCV Fast角点检测
  4. 【云隐】STM32F103C8T6实现串口IAP方式升级固件
  5. 从0到1入门Serverless
  6. atheros蓝牙设备驱动 小米_小米Air 13笔记本黑苹果WiFi蓝牙硬件改装方案二
  7. Android Studio下载
  8. python2使用pandas处理excel数据
  9. MySQL 安装(msi/zip方式安装)
  10. HTML创建几个边框,使用HTML5创建多个边框
  11. linux 设置tomcat快捷启动方式
  12. ZooKeeper 数据模型 Znode 结构特性详解
  13. mysql数据库异地备份
  14. 高速PCB设计EMI之九大规则
  15. Python编程基础的应用
  16. Session的钝化和活化(序列化和反序列化)
  17. 全志A10/RK2918等七款平板芯片横向PK
  18. python(decorator)
  19. 低代码的价值,短期被高估,长期被低估
  20. 基于python的火车票售票系统/基于django火车票务网站/火车购票系统

热门文章

  1. 全网最新抖音视频去水印解析PHP网页源码
  2. solr自定义分词器
  3. 云队友丨知乎10万赞回答:真正厉害的人,是怎么分析问题的?
  4. 怎么在国内创建谷歌账号_如何在Google相册中创建和共享协作相册
  5. ppt里面如何插入表格
  6. 【3款文献阅读的插件】
  7. Swift3.0 中实现发短信功能
  8. 【必看文件含发帖规范】2020年黑马程序员社区总版规发布!
  9. Python接口自动化
  10. 大数据云计算实习报告