通俗易懂讲泛型

由于博客园的 markdown 语法有点坑,格式如果阅读中遇到问题,可以异步本人语雀文档查看:https://www.yuque.com/docs/share/57b89afd-91d8-4e64-82a0-243c74304004?# 《泛型》

泛型是什么

传统编程大多数都是面向对象类型编程,比如方法参数传入一个指定的类型,这类代码比较难复用,通常新增一个类型时就得增或者改代码。当然除了面对特定的类型编程还有面向基类和接口编程的多态编程,这类的代码会通用一些,方法入参传入一个基类或者接口,那么该方法就能适用于基类的所有派生类和接口的实现者,新增派生类也无需更改代码,但是这样就增加了代码的耦合度,必须继承指定基类或者接口才行。泛型就更加通用了,泛型实现了参数化类型,这样我们编写的通用方法就可以适用于多种类型,而不是一个具体的接口或者类。

个人理解泛型的主要作用是为了复用组件代码,当然在泛型出现之前也是可以编写通用的组件代码的,但是这样有点不安全。

泛型出来之前,集合内存储的元素是 Object 类型的,Object 是所有类的基类,因此,可以往集合内部添加任意类型,如上例子就往 listOld 集合内添加了:字符串,整形,对象,这三种类型。这样会导致一种问题,我们无法得知集合内的元素究竟是什么类型的,只能知道他们都是 Object 的子类。这样在使用的时候就得进行强制类型转化。使用的不当的话很容易报 “ClassCastException” 异常。

java 5 泛型出来之前,集合的使用方法:

public class Main {
    public static void main(String[] args) {
        List listOld = new ArrayList();
        listOld.add("string");
        listOld.add(123);
        listOld.add(new Main());

        for (Object o : listOld) {
            if (o instanceof String) {
                String str = (String) o;
                System.out.println("this is string type");
            }
            if (o instanceof Integer) {
                Integer i = (Integer) o;
                System.out.println("this is Integer type");
            }
            if (o instanceof Main) {
                Main m = (Main) o;
                System.out.println("this is Object");
            }
        }
    }
}

输出:

this is string type

this is Integer type

this is Object

泛型出来之后,我们就可以为集合表明一个确定的类型,这样就可以往集合内添加该类型或者该类型的子类。如果添加的类型不正确,那么编译期就会报错。
通过在集合引入泛型,那么编译器就会在编译器进行类型校验,如果往一个指定了类型的集合内部添加了错误类型,编译器就会报错。
而在使用元素的时候,也会自动的将集合内的元素转化为指定类型,这种转化是安全的,因为编译器确保了只能往集合内添加指定类型的元素。

java 5 泛型出来之后,集合的使用方法:

public class Main {
    public static void main(String[] args) {
        List listNew = new ArrayList<>();
        listNew.add("string");
//        listNew.add(123);// 编译报错
        for (String s : listNew) {
            System.out.println(s);
        }
    }
}

输出:

string

那么,泛型这一套是怎么实现的呢,这就很有意思,jdk 有个传统,就是向上兼容,也就是说每发行一个版本,老版本的代码必定能够在新版本的 jdk 上运行。因此,为了兼容 java 5 之前的集合使用方式,jdk 的研发人员,采用了 “泛型擦除”的方式进行设计。

泛型擦除

老实说,刚开始知道泛型擦除这个概念的时候个人觉得有点拉闸..由于没使用过 c++ 和 python(我承认我菜..目前还没有去学习其他编程语言的想法),因此便不知道其他语言是怎么实现泛型的。

那么,什么是泛型擦除呢,泛型擦除是一种面向编译期设计的方法。所有的我们平时见到的如:

List
List
List list;
List list;
List list;

这些泛型语法,经过编译期,到运行期的时候,统统变成了:

List list

也就是指定的类型统统向上转型成了 Object,所以我说泛型擦除是一种面向编译期设计的方法。

那么知道了泛型是什么,理解了泛型前后的编程规范,并且知道了泛型的实现后,让咱们来了解了解泛型怎么使用吧。

泛型怎么使用

如下例子,很简单,在创建类的时候在类名右边使用 尖括号括起来,然后里面随便定义个字母即可,这个字母就代表你可传进来的类型。

public class GenericClass {

    private T obj;

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }

    @Override
    public String toString() {
        return "GenericClass{" +
                "obj=" + obj +
                '}';
    }

    public static void main(String[] args) {
        GenericClass demo1 = new GenericClass<>();
        demo1.setObj("demo1");
        String obj = demo1.getObj();
        System.out.println(obj);
        System.out.println(demo1);

        GenericClass demo2 = new GenericClass<>();
        demo2.setObj(1);
        Integer obj1 = demo2.getObj();
        System.out.println(obj);
        System.out.println(demo2);
    }
}

输出:

demo1

GenericClass{obj=demo1}

demo1

GenericClass{obj=1}

通配符

泛型有个概念是通配符,泛型通配符有三种

  • 无界通配符,接受任意类型,等同于
  • :上界通配符,这里的 Object 可以是任意类,代表的意思是接受任意继承自 Object 的类型
  • :下界通配符,这里的 Object 可以是任意类,代表的意思是接受任意 Object 的父类

无界通配符

无界通配符 看起来意味着”任何事物”,因此使用无界通配符好像等价于使用原生类型,但是它仍旧是很有价值的,因为,实际上它是在声明:”我是想用 Java 的泛型来编写这段代码,我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。

public class Main {
    public static void main(String[] args) {
        Map map1 = new HashMap();
        Map map2 = new HashMap();
        Map map3 = new HashMap();
        Map map4 = new HashMap();//编译失败
        Map map5 = new HashMap();
        Map map6 = new HashMap();
    }
}

上界通配符

上界通配符很有意思,如下代码,在 1 处其实会产生编译报错,因为上界通配符不允许 set 和 add 值,为什么呢,上界通配符的意思是我允许存放所有父类的子类,但是泛型代表的是具体类型,这里的具体类型画重点,就像 2 和 3的用法,虽然采用的上界通配符,但是里面的类型是确定的。

如果你想使用不确定类型,那么直接采用多态特性,比如 4 那样即可。

public class Main {
    public static void main(String[] args) {
        List sonList = new ArrayList<>();    // 1
//        sonList.add(new Son());  编译报错
        sonList = Arrays.asList(new Son(), new Son());          // 2
        for (Father father : sonList) {
            System.out.println(father.getClass().getName());
        }
        List daughterList = Arrays.asList(new Daughter(), new Daughter()); // 3

        List list1 = new ArrayList<>();             // 4
        list1.add(new Son());
    }
}

上界通配符也有一个有意思的点,由于 add 方法的入参是一个泛型,如下图:

通俗易懂讲泛型

由于编译器无法得知这里需要 Father的儿子还是女儿,因此它不会接受任何类型的 Father。如果你先把 Son 向上转型为 Father,也没有关系——编译器仅仅会拒绝调用像 add() 这样参数列表中涉及通配符的方法。

但是!对于入参是 Object 类型的方法,比如 contains(Object o),编译器允许调用他们。

通俗易懂讲泛型

下界通配符

下界通配符也很有意思,上界通配符其实已经指定了具体的类型,在下面的代码就是 Father,所以这个 list 可以随意的 add 值。因为这里 Son 和 Daughter 都是 Father 的子类,所以允许 add,但是 get 的时候就无法拿到具体值了。

public class Main {
    public static void main(String[] args) {
        List list = new ArrayList<>();
        list.add(new Son());
        list.add(new Daughter());
        for (Object o : list) {

        }
    }
}

泛型业界内有两句总结:

  • 频繁往外读取内容的,适合用上界Extends。
  • 经常往里插入的,适合用下界Super。

其他问题

在学习泛型的过程中,遇到一个很有意思的关于数组的点也记录一下。

如下例子(这个例子是 java 编程思想里面的):

在 1 和 2 处竟然会抛出异常!!我之前都不知道的,一直以为这样能塞值成功。

这是因为,虽然定义的时候将 Apple 数组向上转型成了 Fruit 数组,但是运行时的数组机制知道它处理的是 Apple[],因此会在向数组中放置异构类型时抛出异常。

class Fruit {
}

class Apple extends Fruit {
}

class Jonathan extends Apple {
}

class Orange extends Fruit {
}

public class CovariantArrays {
    public static void main(String[] args) {
        Fruit[] fruit = new Apple[10];
        fruit[0] = new Apple(); // OK
        fruit[1] = new Jonathan(); // OK
        fruit[2] = new Orange(); // 1
        // Runtime type is Apple[], not Fruit[] or Orange[]:
        try {
            // Compiler allows you to add Fruit:
            fruit[0] = new Fruit(); // 2
        } catch (Exception e) {
            System.out.println(e);
        }
        try {
            // Compiler allows you to add Oranges:
            fruit[0] = new Orange(); // 3
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

文章为本人学习过程中的一些个人见解,漏洞是必不可少的,希望各位大佬多多指教,帮忙修复修复漏洞!

Original: https://www.cnblogs.com/cyrus-s/p/15455744.html
Author: 三木同学
Title: 通俗易懂讲泛型

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/715181/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • maven的安装和仓库的种类和彼此关系

    ; ; Maven软件的下载 为了使用Maven管理工具,哦我们首先要到官网去下载他的安装软件,通过百度搜索Mav嗯如下: 点击Download连接,就可以直接进入到Maven软件…

    技术杂谈 2023年6月21日
    093
  • .net自动摘要等算法HanLP.net

    参考资料: 在CSharp中调用HanLP 目前自动摘要算法似乎没有.net 版本,而以java,python 居多 自动摘要算法一般使用textrank算法 比如java开源的:…

    技术杂谈 2023年7月24日
    082
  • 技术管理进阶——技术部如何做绩效考核设计?

    原创不易,求分享、求一键三连 之前有个同学问我技术部的绩效方案怎么设计,想着这么多年的考核与被考核,我陷入了沉思,一方面是我对考核的认识未必正确、全面,另一方面是有些同学未必能接受…

    技术杂谈 2023年6月1日
    099
  • 大屏数据可视化 B端UI设计后台PC网页UI设计U3D+可视化落地教程

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    技术杂谈 2023年5月31日
    0101
  • 领域驱动设计框架Axon实践-InfoQ

    spring statemachine activiti – 搜索https://cn.bing.com/search?q=spring+statemachine+ac…

    技术杂谈 2023年6月1日
    093
  • 聊聊Vim的工作原理

    聊聊Vim的工作原理 日常里一直在用Vim这个编辑器,前阵子学习关于Linux中的fd(文件描述符)时,发现vim的进程描述符会比上一个自动加一,后续了解到vim的工作原理后,解开…

    技术杂谈 2023年7月11日
    097
  • C7N 操作当前 table cell

    场景: 在 C7N table 组件中,实现 cell 中的内容,双击展开或收起 思路: 通过 onCell 方法实现 方案: Original: https://www.cnbl…

    技术杂谈 2023年5月30日
    092
  • MyBatis学习大全(狂神秦疆版)

    一、 MyBatis 1.什么是Mybaits 概念:MyBatis 是一款优秀的 持久层框架 它支持自定义 SQL、存储过程以及高级映射。 MyBatis 免除了几乎所有的 JD…

    技术杂谈 2023年6月21日
    065
  • python中的*和**

    简介:Python中的与*操作符使用最多的就是两种用法。 1.用做运算符,即表示乘号,*表示次方。 用于指定函数传入参数的类型的。*用于参数前面,表示传入的多个参数将按照元组的形式…

    技术杂谈 2023年7月25日
    069
  • 基础篇:JAVA集合,面试专用

    没啥好说的,在座的各位都是靓仔 List 数组 Vector 向量 Stack 栈 Map 映射字典 Set 集合 Queue 队列 Deque 双向队列 关注公众号,一起交流,微…

    技术杂谈 2023年7月25日
    089
  • 新买的百度云服务器随便玩玩之部署简单页面

    新买的百度云服务器随便玩玩之部署简单页面 1.vscode 连接百度云服务器 vscode下载拓展 2.在远程资源管理器中添加新的远程资源管理 3.输入云服务器公网ip地址;输入密…

    技术杂谈 2023年6月21日
    082
  • delphi button 实现下拉列表

    delphi;gutter:true; unit Unit1;</p> <p>interface</p> <p>uses Windo…

    技术杂谈 2023年5月31日
    078
  • IOC常用的创建对象方式

    1、User.java public class User { private String name; public User() { System.out.println(&q…

    技术杂谈 2023年7月11日
    048
  • kafka学习

    Kafka概述 Kafka是分布式(点对点模式)(发布-订阅模式)消息系统,由Scala 写成, 它主要用于处理流式数据。本质是基于消息队列缓存数据. Kafka对消息保存时根据T…

    技术杂谈 2023年7月24日
    092
  • DateTimeFormatter.BASIC_ISO_DATE

    DateTimeFormatter (Java Platform SE 8 ) (oracle.com) 作者:习惯沉淀 如果文中有误或对本文有不同的见解,欢迎在评论区留言。 如果…

    技术杂谈 2023年6月1日
    085
  • Kafka详解

    Kafka介绍 Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,…

    技术杂谈 2023年7月24日
    066
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球