1.泛型概述

1.1.为什么使用泛型

没有泛型,在编写代码时只能使用具体类型或Object类型,无法做到使用者想要使用什么类型就是类型。比如:创建一个方法,形参需要指定需要使用的数据类型,在创建方法之初就已经决定了该方法可以处理的数据类型,这大大限制了编程的灵活性。正因如此,才出现了在使用时才决定具体类型是什么的泛型编程。

1.2.泛型是什么

泛:广泛、普遍,非具体的东西,泛型就是定义之初用符号表示不具体的类型,在使用的时候才动态地指定具体的类型。更应该明白这种泛型编程设计思想,使用泛型带来的好处是代码更加简洁、更加灵活、使程序更加健壮(编译期没警告,运行期不会出现类强转异常--ClassCastException)。

2.泛型接口、类、方法

泛型允许在定义接口、类、方法时使用,将在声明变量、创建对象、调用方法时动态地指定。

2.1.泛型接口

定义泛型接口:比如集合中的List接口

  1. // 定义接口时指定泛型:E,E类型在接口中就可以作为类型使用
  2. public interface List<E> extends Collection<E>{
  3. ……
  4. boolean add(E e);
  5. Iterator<E> iterator();
  6. ……
  7. }
  8. // 定义接口时指定泛型:K 和 V,K和V类型在接口中就可以作为类型使用
  9. public interface Map<K,V>{
  10. ……
  11. Set<K> keySet();
  12. Collection<V> values();
  13. Set<Map.Entry<K, V>> entrySet();
  14. ……
  15. }

使用泛型接口:List接口的泛型类型E,在使用时指定为具体类型String

  1. public static void main(String[] args) {
  2. List<String> list = new ArrayList<>();// 指定泛型类型E=String
  3. list.add("我只认识字符串");//boolean add(E e); 等价于boolean add(String e);
  4. Iterator<String> iterator = list.iterator();//Iterator<E> iterator();
  5. while (iterator.hasNext()){
  6. String next = iterator.next();//不需要强转为String
  7. System.out.println(next);
  8. }
  9. }

关于泛型接口Map<K,V> 集合怎么用,就自行编写感受下。

2.2.泛型类

普通泛型类

定义泛型类

  1. public class DemoFx<D> {
  2. private D dd;
  3. public D getDd(){
  4. return this.dd;
  5. }
  6. public void setDd(D dd){
  7. this.dd = dd;
  8. }
  9. }

使用泛型类

  1. public static void main(String[] args) {
  2. DemoFx<String> stringDemoFx = new DemoFx<>();
  3. stringDemoFx.setDd("我是字符串类型");
  4. System.out.println(stringDemoFx.getDd());
  5. }

泛型类的继承与实现

定义泛型类:以ArrayList 类为例,继承泛型抽象类和实现泛型接口:

  1. public class ArrayList<E> extends AbstractList<E>
  2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
  3. ……
  4. public E get(int index) {
  5. rangeCheck(index);
  6. return elementData(index);
  7. }
  8. ……
  9. }

使用泛型类

  1. public static void main(String[] args) {
  2. List<String> list = new ArrayList<String>();// 指定泛型类型E=String
  3. list.add("我只认识字符串");
  4. String s = list.get(0);// 返回值为String
  5. }

2.3.泛型方法

定义泛型方法:还是ArrayList案例

  1. public class ArrayList<E> extends AbstractList<E>
  2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
  3. ……
  4. public <T> T[] toArray(T[] a) {
  5. if (a.length < size)
  6. // Make a new array of as runtime type, but my contents:
  7. return (T[]) Arrays.copyOf(elementData, size, a.getClass());
  8. System.arraycopy(elementData, 0, a, 0, size);
  9. if (a.length > size)
  10. a[size] = null;
  11. return a;
  12. }
  13. ……
  14. }

使用泛型方法:public <T> T[] toArray(T[] a)

  1. public static void main(String[] args) {
  2. List<String> list = new ArrayList<String>();
  3. list.add("s1");
  4. list.add("s2");
  5. list.add("sn");
  6. // public <T> T[] toArray(T[] a)
  7. String[] strings = list.toArray(new String[list.size()]);
  8. System.out.println(Arrays.asList(strings));
  9. }

3.类型通配符

3.1.使用类型通配符

通配符表示符号是问号<?>,它是未知类型,可以匹配任何类型,也称为无界通配符

对比”通配符“和”泛型“创建的方法

  1. // 通配符定义
  2. public void foreach(List<?> list){
  3. for (int i =0 ;i<list.size();i++) {
  4. Object o = list.get(i);
  5. System.out.println(o.toString());
  6. }
  7. }
  8. // 泛型定义
  9. public <T> void foreach2(List<T> list){
  10. for(T t : list){
  11. System.out.println(t.toString());
  12. }
  13. }
  14. // 使用
  15. public static void main(String[] args) {
  16. List<String> list = new ArrayList<String>();
  17. list.add("s1");
  18. list.add("s2");
  19. list.add("sn");
  20. Demo demo = new Demo();
  21. demo.foreach(list); // 通配符
  22. demo.foreach2(list); // 泛型
  23. }

通配符和泛型都可以实现相同的效果,并且泛型方法还可以使用本身定义的泛型类型,而通配符的”?“不可以当作数据类型来使用,所以通配符方法案例中只能用Object来接收list的元素,这也是通配符的缺点:无法确定未知类型是什么类型。

所以通配符的出现到底有什么用呢?

通配符为泛型的一种特例,无需定义既可在形参中使用的未知类型。

泛型和通配符的区别

  • Java编译器把泛型【T】推断成T类型,在代码块中允许出现 T类型变量;而把通配符【?】推断成未知类型,不存在 ?类型变量;
  • Class<T>需要依赖于T,需要在方法声明时指定<T>,而Class<?>则不需要;

这样可能更好理解泛型和通配符:泛型 强调的是类型,通配符 强调的是符号

Class<?> 表示任意类型,但又不等同于Class<Object>,前者在类型不匹配的情况下只能够插入null,但是后者可以插入Object或任何Object对象的子类。

例如:不能往List<?> list里添加任意类型的对象,除了null

3.2.类型上限

通配符上限:<? extends Demo> ,通配符【?】的上限是Demo类型,既是<? extends Demo> 的范围是Demo或其子类类型。

泛型上限:<T extends Demo> ,和通配符理解一样。类型上限如图

案例

创建三个类DemoFather、Demo、DemoChildren,关系如上图

  1. public class DemoTest {
  2. public static void main(String[] args) {
  3. List<DemoChildren> demoChildrens = new ArrayList<>();
  4. demoChildrens.add(new DemoChildren());
  5. demoChildrens.add(new DemoChildren());
  6. DemoTest test = new DemoTest();
  7. test.testDemo(demoChildrens); // 通配符
  8. test.testDemo2(demoChildrens);// 泛型
  9. }
  10. // 通配符上限:控制list 集合允许的类型范围为Demo或其子类
  11. public void testDemo(List<? extends Demo> list){
  12. // 若无上限,这里只能用Object类型代替Demo类型
  13. for (Demo demo : list){
  14. System.out.println(demo.toString());
  15. }
  16. }
  17. // 泛型上限:控制list 集合允许的类型范围为Demo或其子类
  18. public <T extends Demo> void testDemo2(List<T> list){
  19. for (T t : list){
  20. System.out.println(t.toString());
  21. }
  22. // or
  23. for(Demo demo:list){
  24. System.out.println(demo.toString());
  25. }
  26. }
  27. }

泛型的上限是在定义时确定上限<T extends Demo>;通配符直接在形参上确定上限<? extends Demo>。其实都很好理解,类型上限就是在一般写法的基础上加入范围“上限”,既是 extends xxx。

源码的一些例子

  1. // 接口泛型上限
  2. public interface ObservableArray<T extends ObservableArray<T>> extends Observable {……}
  3. // 抽象类泛型上限
  4. public abstract class ArrayListenerHelper<T extends ObservableArray<T>> extends ExpressionHelperBase {……}
  5. public abstract class CellBehaviorBase<T extends Cell> extends BehaviorBase<T> {……}
  6. // 方法泛型上限
  7. public static <T extends Number> ReadOnlyLongProperty readOnlyLongProperty(final ReadOnlyProperty<T> property) {……}
  8. // 通配符上限
  9. void putAll(Map<? extends K, ? extends V> m);

3.3.类型下限

通配符下限:<? super Demo> ,通配符【?】的下限是Demo类型,既是<? super Demo> 的范围是Demo的父类类型。

泛型下限:。主要是因为类型下限会令人困惑并且不是特别有用。为什么类型参数没有下限的一些解释:http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ107

  1. public static void main(String[] args) {
  2. Demo demo = new Demo();
  3. List<Demo> demos = new ArrayList<>();
  4. demos.add(demo);
  5. DemoTest test = new DemoTest();
  6. test.testSuper(demos);
  7. DemoFather demoFather = new DemoFather();
  8. List<DemoFather> demoFathers = new ArrayList<>();
  9. demoFathers.add(demoFather);
  10. DemoTest test2 = new DemoTest();
  11. test2.testSuper(demoFathers);
  12. }
  13. public void testSuper(List<? super Demo> list){
  14. // 虽然有下限,但无法直接使用Demo类型接收参数
  15. for (Object obj : list){
  16. System.out.println(obj.toString());
  17. }
  18. }

虽然有下限,但无法直接使用Demo类型接收参数。这就像“向上转型”和“向下转型”,向上转型是自动的,向下转型需要强转;类型上限可以使用最大类型(父类)接收比它小的类型(子类),类型下限不可以使用最小类型(子类)接受可能比它大的类型(父类)。

源码的一些例子

  1. public class ArrayList<E> extends AbstractList<E>
  2. implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  3. {
  4. ……
  5. public void sort(Comparator<? super E> c) {
  6. final int expectedModCount = modCount;
  7. Arrays.sort((E[]) elementData, 0, size, c);
  8. if (modCount != expectedModCount) {
  9. throw new ConcurrentModificationException();
  10. }
  11. modCount++;
  12. }
  13. }
  14. public class Arrays {
  15. public static <T> void sort(T[] a, int fromIndex, int toIndex,
  16. Comparator<? super T> c) {
  17. if (c == null) {
  18. sort(a, fromIndex, toIndex);
  19. } else {
  20. rangeCheck(a.length, fromIndex, toIndex);
  21. if (LegacyMergeSort.userRequested)
  22. legacyMergeSort(a, fromIndex, toIndex, c);
  23. else
  24. TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
  25. }
  26. }
  27. }

关于泛型字母E、K、V、T是什么?

  • E表示Element,
  • K表示Key,
  • V表示Value,
  • T表示Type,
  • N表示Number,
  • ? 表示 未知类型

除了【?】,其他泛型符号你写成字符串都可以,但要注意可读性,一般都是使用单个大写字母表示,不然代码反而不简洁、不易阅读。

4.泛型实现原理--类型擦除

把一个具有泛型信息的对象 赋给 另一个没有泛型信息的变量引用,类型信息都将被擦除

案例一:不指定泛型上限,类型擦除后为Object

  1. public static void main(String[] args) {
  2. // 定义集合泛型E = String
  3. List<String> stringArrayList = new ArrayList<>();
  4. stringArrayList.add("test1");
  5. // 获取到的类型为:String
  6. String s = stringArrayList.get(0);
  7. // 把带有泛型信息的stringArrayList 对象赋给不确定泛型的List
  8. List listObject = stringArrayList;
  9. // listObject 只知道get的类型为Object,而不是String
  10. Object obj = listObject.get(0);
  11. }

案例二:指定泛型上限,类型擦除后为上限的类型

  1. public class DemoFather {}
  2. public class Demo extends DemoFather{}
  3. public class DemoChildren<T extends DemoFather> {
  4. private T t;
  5. public T getT(){
  6. return this.t;
  7. }
  8. public void setT(T t){
  9. this.t= t;
  10. }
  11. }
  12. // 测试public class DemoTest {
  13. public static void main(String[] args) {
  14. //class DemoChildren<T extends DemoFather>,指定泛型T=Demo类型
  15. DemoChildren<Demo> demoChildren = new DemoChildren<Demo>();
  16. // 拿到的方法类型确实是T=Demo类型
  17. Demo demo = demoChildren.getT();
  18. // 把带有泛型信息的 demoChildren 对象赋给不确定泛型的demoChildren2
  19. DemoChildren demoChildren2 =demoChildren;
  20. // 再来获取方法的类型时,变为了上限的DemoFather类型
  21. DemoFather demoFather = demoChildren2.getT();
  22. }
  23. }

结论:

指定泛型上限时,类型擦除后为上限的类型;反之是Object类型,因为Java中所有类都默认继承了Object类。

所以案例二的泛型类在编译阶段是长这样的

  1. public class DemoChildren {
  2. private DemoFather t;
  3. public DemoFather getT(){
  4. return this.t;
  5. }
  6. public void setT(DemoFather t){
  7. this.t= t;
  8. }
  9. }
  10. // 原来的泛型类,对比一下
  11. public class DemoChildren<T extends DemoFather> {
  12. private T t;
  13. public T getT(){
  14. return this.t;
  15. }
  16. public void setT(T t){
  17. this.t= t;
  18. }
  19. }

Java往期文章

Java全栈学习路线、学习资源和面试题一条龙

我心里优秀架构师是怎样的?

免费下载经典编程书籍

Java泛型的那些事的更多相关文章

  1. Java泛型总结

    1. 什么是泛型?泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的 ...

  2. java泛型的讲解

    java泛型 什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指 ...

  3. Java泛型:类型擦除

    类型擦除 代码片段一 Class c1 = new ArrayList<Integer>().getClass(); Class c2 = new ArrayList<String& ...

  4. java 泛型 窜讲

    一.为什么使用泛型      复用性:泛型的本质就是参数化类型,因而使用编写的泛型代码可以被许多不同类型的对象所复用.      安全性:在对类型Object引用的参数操作时,往往需要进行显式的强制类 ...

  5. Java泛型知识点全方位总结

    前言 我一直认为泛型是编程语言设计中一个非常基本和重要的概念.Java中的泛型是什么?他们为什么在那里?他们是如何发展的?在学习基础知识时,对仿制药的透彻理解是非常重要的.因此,我阅读了<Jav ...

  6. Net is as typeof 运行运算符详解 net 自定义泛型那点事

    Net is as typeof 运行运算符详解   概述 在了解运行运算符的前提我们需要了解什么是RTTI ,在任何一门面向对象的语言中,都有RTTI这个概念(即 运行时). RTTI(Run-Ti ...

  7. Java基础学习总结(83)——Java泛型总结

    1. 什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型 ...

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

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

  9. JAVA泛型知识--> <? extends T>和<? super T>

    <? extends T> 和 <? super T> 是Java泛型中的“通配符(Wildcards)” 和 “边界(Bounds)”的概念 <? extends T& ...

随机推荐

  1. 知乎上一个关于Android面试的问题答案

    由于链接出错,这里附上原文链接:Touch Me 前段时间面试,自己以及小伙伴们简要的汇总的一些面试问题,可以对照的参考一下吧- 建议就是在面一家公司之前了解好这个公司的app是以什么为驱动的,例如电 ...

  2. day 11 算法的时间空间复杂度

    (1).有以下程序: 求输入的n值(除1和n)之外的所有因子之和. 分析:这里函数内的循环体i初值不能为零.%是表示"取余",0除以任何数都不会存在余数的,所有是余数为0. (2) ...

  3. 自从学会了VBA字典,VLOOKUP都不那么香了

    上篇博文中,小爬曾多次为VBA字典带货.鼓励多用字典,可以让我们的VBA脚本工具执行更快.今天小爬来细聊一下VBA字典的具体应用!如果你有一定VBA基础,那么看完你一定会对VBA字典有全新的认识:如果 ...

  4. 【一个idea】YesSql,一种在经典nosql数据库redis上实现SQL引擎的方案(我就要开历史的倒车)

    公众号链接 最高级的红酒,一定要掺上雪碧才好喝. 基于这样的品味,我设计出了一套在经典nosql数据库redis上实现SQL引擎的方法.既然redis号称nosql,而我偏要把SQL加到redis上, ...

  5. ros实例_百度语音+图灵

    1 百度语音模块 参考http://blog.csdn.net/u011118482/article/details/55001444 1.1 百度语音识别包 git clonehttps://git ...

  6. js整体

    1.引入 <script type="text/javascript"> 2.输出 使用 window.alert() 写入警告框  使用 document.write ...

  7. 不难懂-----git一套流程

    001.初始化仓库,创建git仓库 git init 002.配置个人信息 git config --global user.name <名字> --------->:配置用户名 g ...

  8. Java安全之C3P0利用与分析

    Java安全之C3P0利用与分析 目录 Java安全之C3P0利用与分析 写在前面 C3P0 Gadget http base C3P0.getObject() 序列化 反序列化 Class.forN ...

  9. STM32定时器触发ADC多通道连续采样,DMA缓存结果

    STM32的ADC使用非常灵活,采样触发方面:既支持软件触发,定时器或其他硬件电路自动触发,也支持转换完成后自动触发下一通道/轮转换.转换结果存储方面:既支持软件读取和转存,也支持DMA自动存储转换结 ...

  10. 社交网络分析的 R 基础:(三)向量、矩阵与列表

    在第二章介绍了 R 语言中的基本数据类型,本章会将其组装起来,构成特殊的数据结构,即向量.矩阵与列表.这些数据结构在社交网络分析中极其重要,本质上对图的分析,就是对邻接矩阵的分析,而矩阵又是由若干个向 ...