12、Java基础之泛型的使用

news2024/11/19 2:29:58

一、泛型的理解

1、泛型的概念

所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。

2、泛型的引入背景

集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList <E>这个就是类型参数,即泛型。

二、泛型在集合中的使用

1、在集合中使用泛型之前的例子

在引入泛型之前,在集合中可能出现两个问题:

①问题一:无法约束存储数据的类型。

②问题二:强转时可能出现ClassCastException。

//在集合中加入泛型之前
    @Test
    public void test1(){
        List list = new ArrayList();
        //存储学生成绩
        list.add(78);
        list.add(89);
        list.add(91);
        list.add(82);

        //问题一:无法约束存储数据的类型
        list.add("Tom");

        for(Object obj : list){
            //问题二:强转时可能出现ClassCastException
            int studentScore = (Integer) obj;
            System.out.println(studentScore);
        }
    }

图示:

2、在集合中使用泛型例子

在集合中添加了泛型后,就避免了上面的两个问题

①避免了问题一,在编译时就会监测传入的数据类型。

②避免了问题二:强转问题。

//在集合中加入泛型
    @Test
    public void test2(){
        ArrayList<Integer> list = new ArrayList<>();

        list.add(78);
        list.add(88);
        list.add(91);
        list.add(54);
        //避免了问题一,在编译时就会监测传入的数据类型
//        list.add("Tom");
        //遍历方式1:
//        for(Integer score : list){
//            //避免了强转问题。
//            int stuScore = score;
//            System.out.println(stuScore);
//        }

        //遍历方式2:
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer stuScore = iterator.next();
            System.out.println(stuScore);
        }
    }

//在Map中使用泛型
    @Test
    public void test3(){
        HashMap<String, Integer> map = new HashMap<>();

        map.put("Tom",89);
        map.put("Jerry",70);
        map.put("Jack",65);
        map.put("Rose",97);

//        map.put(123,"Bob");

        Set<Map.Entry<String, Integer>> entry = map.entrySet();
        Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
        while(iterator.hasNext()){
            Map.Entry<String, Integer> e = iterator.next();
            String key = e.getKey();
            Integer value = e.getValue();
            System.out.println(key + "====" + value);
        }
    }

图示:

3、集合中使用泛型总结

①集合类及集合接口在jdk5.0后都改为带泛型的结构。

②在实例化集合时,可以指明使用的泛型。

③指明泛型以后,凡是在集合类或接口中凡是定义类或实现接口时,内部(属性、方法、构造器等结构)使用了类的泛型,那么都指定为实例化的泛型。

比如:add(E e);—>add(Integer e);

④泛型的类型都是类,不能使用基础数据类型,当需要使用基本数据类型时,可以使用包装类代替。

⑤在没指明泛型时,系统默认使用java.lang.Object类型。

三、自定义泛型结构

1、引入

1.1、泛型的声明

interface List<T> 和 class GenTest<K,V> 其中,T,K,V不代表值,而是表示类型。这里使用任意字母都可以。常用T表示,是Type的缩写。

1.2、泛型的实例化

一定要在类名后面指定类型参数的值(类型)。如:

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

Iterator<Customer> iterator = customers.iterator();

T只能是类,不能用基本数据类型填充。但可以使用包装类填充。

把一个集合中的内容限制为一个特定的数据类型,这就是generics背后的核心思想。

2、自定义泛型类、泛型接口

2.1、泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>

2.2、泛型类的构造器如下:public GenericClass(){}。而下面是错误的:public GenericClass<E>(){}

2.3、实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。

2.4、泛型不同的引用不能相互赋值。尽管在编译时ArrayList<String>和ArrayList<Integer>是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。

2.5、泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用要不用,一路都不要用

2.6、如果泛型结构是一个接口抽象类,则不可创建泛型类的对象

2.7、jdk1.7,泛型的简化操作:ArrayList<Fruit> flist = new ArrayList<>();

2.8、泛型的指定中不能使用基本数据类型,可以使用包装类替换。

2.9、在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。

2.10、异常类不能是泛型的。

2.11、不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。

2.12、父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:

①子类不保留父类的泛型:按需实现

②子类保留父类的泛型:泛型子类 结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自 己的泛型。

代码举例:

public class Order<T> {
    String orderName;
    int orderId;

    T orderT;

    public Order(){
        //错误的
//        T[] arr = new T[10];
        //正确写法
        T[] arr = (T[]) new Object[10];
    }

    public Order(String orderName,int orderId,T orderT){
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }
    //以下的三个方面方法都不是泛型方法

    public T getOrderT (){
        return orderT;
    }

    public void setOrderT(T orderT){
        this.orderT = orderT;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + '\'' +
                ", orderId=" + orderId +
                ", orderT=" + orderT +
                '}';
    }

    //静态方法中不能调用泛型结构
//    public static void show(T orderT){
//        System.out.println(orderT);
//    }

    public static void show(){
        //try-catch结构不能调用泛型
//        try{
//
//        }catch(T t){
//
//        }
    }


    //泛型方法:在方法中出现了泛型,并且方法的泛型与类声明的泛型参数不相同。
    //换句话说,泛型方法所在的类是不是泛型类都无所谓。
    //泛型方法时可以声明为静态的,因为泛型的类型是调用方法时确定的,并非实例化类时确定的
    public static  <E> List<E> copyToArrayList(E[] arr){
        List<E> list = new ArrayList<>();

        for(E e : arr){
            list.add(e);
        }

        return list;
    }

}
public class SubOrder extends Order<Integer>{//SubOrder:不是泛型类
    public static  <E> List<E> copyToArrayList(E[] arr){
        List<E> list = new ArrayList<>();

        for(E e : arr){
            list.add(e);
        }

        return list;
    }
}
public class SubOrder1<T> extends Order<T>{//SubOrder1<T>:仍然是泛型类
}

测试:

public class GenericTest1 {

    @Test
    public void test1(){
        //如果使用了泛型的类,在实例化的时候没有确定泛型的类型,那么此泛型类型默认为Object类的。
        //不建议这样使用,如果创建类使用了泛型,建议在实例化时指明泛型类型。
//        Order o1 = new Order();
//        o1.setOrderT(123);
//        o1.setOrderT("ABC");

        Order<String> order = new Order<>("Tom",1002,"order:Tom");

        order.setOrderT("order:AA");

    }

    @Test
    public void test2(){
        //由于子类继承了带泛型的父类时,指明了泛型的类型。则在子类实例化时不需要再声明泛型的类型。
        SubOrder o = new SubOrder();

        o.setOrderT(123);

        SubOrder1<Float> sub2 = new SubOrder1<>();
        sub2.setOrderT(12.3f);
    }




    @Test
    public void test3(){
        ArrayList<String> list1 = null;
        ArrayList<Integer> list2 = null;
        //泛型不同的引用不能相互赋值。
//        list1 = list2;

        Person p1 = null;
        Person p2 = null;

        p1 = p2;

    }

    @Test
    public  void test4(){
        Order<String> order = new Order<>();

        Integer[] arr = new Integer[]{1,2,3,4};
        //泛型方法在调用时,指明泛型参数的类型。与泛型类的类型不相干
        List<Integer> list = order.copyToArrayList(arr);
        System.out.println(list);
    }
}

3、自定义泛型方法

方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型 方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。

泛型方法的格式: [访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常

    //泛型方法:在方法中出现了泛型,并且方法的泛型与类声明的泛型参数不相同。
    //换句话说,泛型方法所在的类是不是泛型类都无所谓。
    //泛型方法时可以声明为静态的,因为泛型的类型是调用方法时确定的,并非实例化类时确定的
    public static  <E> List<E> copyToArrayList(E[] arr){
        List<E> list = new ArrayList<>();

        for(E e : arr){
            list.add(e);
        }

        return list;
    }

四、泛型在继承上的体现

1、泛型在继承方面的体现

虽然类A是类B的父类,但是G<A>与G<B>不具有子父类的关系,即类A和类B在结构上是并列的

补充:类A是类B的父类,A<G>和B<G>是父类关系

测试:

    @Test
    public void test1(){
        Object obj = new Object();
        String str = new String();

        obj = str;

        Object[] obj1 = null;
        String[] str1 = null;
        obj1 = str1;

        ArrayList<Object> list1 = null;
        ArrayList<String> list2 = null;
        //此时list1与list2并不具有子父类关系
//        list1 = list2;

        show(list1);
        show1(list2);

        Date date = new Date();
//        str = date;
    }

    public void show(List<Object> list){

    }

    public void show1(List<String> list){

    }

    @Test
    public void test2(){
        List<Object> list1 = null;
        ArrayList<Object> list2 = null;
        list1 = list2;

        List<Object> list3 = new ArrayList<>();
    }

五、通配符的使用

1、使用类型通配符:?

比如:List<?> ,Map<?,?>

List<?>是List<String>、List<Object>等各种泛型List的父类。

2、读取List<?>的对象

读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。

3、写入list中的元素

写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。唯一的例外是null,它是所有类型的成员。

/*
    2.通配符的使用

        通配符:?

        类A是类B的父类,G<A>和G<B>是没有关系的,但是它们有共同的父类G<?>
     */
    @Test
    public void test3(){
        List<Object> list1 = null;
        ArrayList<String> list2 = null;

        List<?> list = null;

        list = list1;
        list = list2;

        //编译通过
//        print(list1);
//        print(list2);


        //**************

        List<String> list3 = new ArrayList<>();
        list3.add("AA");
        list3.add("DD");

        list = list3;
        //添加(写入):对于List<?>的结构,不进行添加操作,除了add(null);以外
//        list.add("bb");
//        list.add("?");
        list.add(null);

        //获取(读取):允许读取操作,获取的数据的类型为Object类。
        Object o = list.get(0);
        System.out.println(o);


    }
    public void print(List<?> list){

        Iterator<?> iterator = list.iterator();
        while(iterator.hasNext()){
            Object next = iterator.next();
            System.out.println(next);
        }
    }

4、有限制条件的使用通配符

①通配符指定上限

上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=

<? extends Number> (无穷小 , Number]

②通配符指定下限

下限super:使用时指定的类型不能小于操作的类,即>=

<? super Number> [Number , 无穷大)

代码测试:

public class Person {
}

public class Student extends Person{
}

/*
    3.有限制条件的通配符的使用

        ? extends A:
                G<? extends A>:可以作为G<A>和G<B>的父类,其中B是A的子类。
        ? super A:
                G<? super A>:可以作为G<A>和G<B>的父类,其中B是A的父类。

     */
    @Test
    public void test4(){
        List<? extends Person> list1 = new ArrayList<>();
        List<? super Person> list2 = new ArrayList<>();

        List<Person> list3 = new ArrayList<>();
        List<Student> list4 = new ArrayList<>();
        List<Object> list5 = new ArrayList<>();

        list1 = list3;
        list1 = list4;
        //编译不通过
//        list1 = list5;

        list2 = list3;
        //编译不通过
//        list2 = list4;
        list2 = list5;
        
        //读取数据
        
        list1 = list4;
        Person person = list1.get(0);
        
        list2 = list3;
        Object object = list2.get(0);

        //添加数据

        //编译不通过
//        list1.add(new Person());

        //编译通过
        list2.add(new Person());
        list2.add(new Student());
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/149171.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[游戏测试]基于人工智能博弈树,极大极小(Minimax)搜索算法并使用Alpha-Beta剪枝算法优化实现的可人机博弈的AI智能五子棋游戏。

⬜⬜⬜ &#x1f430;&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea; (*^▽^*)欢迎光临 &#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;&#x1f430;⬜⬜⬜ ✏️write in front✏️ &#x1f4dd;个人主页&#xff1a;陈丹宇jmu &a…

关于idea中查看源码时的注释以及.class与.java文件的问题

文章目录问题描述解决方法问题描述 在使用idea编辑器学习java的时候发现有的人的idea将鼠标方法java自带的类方法上会出现解释注释&#xff0c;但是我的idea不可以&#xff0c;经过查询发现是idea中jdk选择的问题。 下图为能查看注释时的截图 按住ctrl点击方法名进入&#x…

分治和递归

目录 分治的概念&#xff1a; 递归的概念&#xff1a; 分治策略的特征&#xff1a; 分治法步骤&#xff1a; 例&#xff1a;阶乘&#xff01; 迭代 递归 关于递归使用栈 斐波拉切数列 迭代 递归 分治的概念&#xff1a; 将一个难以直接解决的大问题&#xff08;规模大…

【年终总结】我的前端之行,回顾2022,展望2023

&#x1f431;个人主页&#xff1a;不叫猫先生 &#x1f64b;‍♂️作者简介&#xff1a;前端领域新星创作者、华为云享专家、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; &#x1f4ab;系列专栏&#xff…

Vivado综合属性之MAX_FANOUT

本文介绍了综合属性MAX_FANOUT对Schematic的影响&#xff0c;通过本文可以理解通过寄存器复制的方式可以降低扇出。 高扇出信号可能会因为布线拥塞而出现时序问题。常用的规避方法是通过寄存器复制的方式降低扇出&#xff0c;可通过MAX_FANOUT实现寄存器复制。 MAX_FANOUT既可…

为金融业保驾护航,浪潮存储容灾方案获得权威媒体认可

近日&#xff0c;在2022中国金融科技年会上&#xff0c;经权威IT专家多项严格评审&#xff0c;浪潮金融行业数据存储与容灾解决方案&#xff0c;凭借安全、可靠、经济、高效四大优势&#xff0c;能够满足金融业务服务永远在线、数据永不丢失、性能永远满足、容量永远充足的核心…

【Linux】Linux编译器 gcc 的使用 | 动静态库的初步认识

&#x1f451;作者主页&#xff1a;进击的安度因 &#x1f3e0;学习社区&#xff1a;进击的安度因&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;Linux 文章目录一、前言二、gcc 演示翻译环境1、预处理2、编译3、汇编4、链接5、总结三、动静态链接库1、库…

代码随想录算法训练营第6天 1.两数之和、242. 有效的字母异位词、349.两个数组的交集

代码随想录算法训练营第6天 1.两数之和、242. 有效的字母异位词、349.两个数组的交集 两数之和 力扣题目链接(opens new window) 给定一个整数数组 nums 和一个目标值 target&#xff0c;请你在该数组中找出和为目标值的那 两个 整数&#xff0c;并返回他们的数组下标。 首…

JS数字日期转中文日期(封装函数,dayjs转换时间格式)

JS数字日期转中文日期往期相关文章场景复现封装函数&#xff08;数字日期转中文日期&#xff09;实际应用往期相关文章 文章内容文章链接JS数组对象——根据日期进行排序&#xff0c;按照时间进行升序或降序排序https://blog.csdn.net/XSL_HR/article/details/128579840?spm1…

Markdown使用说明

Markdown使用说明欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注…

[网鼎杯 2020 朱雀组]phpweb

目录 信息收集 方法一&#xff1a;in_array()函数绕过 方法二&#xff1a;反序列化漏洞利用 信息收集 抓个包&#xff0c;发现POST传入以下内容 funcdate&pY-m-dh%3Ai%3Asa func和p的值分别为一个待执行的函数和函数的参数 构造payload 尝试funcphpinfo&p 回显 H…

【学习笔记之Linux】权限

权限概念 一件事是否允许被谁“做”&#xff0c;这就是权限。权限 用户 文件属性。   在Linux上&#xff0c;用户分为普通用户和root。root是超级管理员 ≈ 天王老子&#xff0c;只能够有一个。root的命令提示符是#&#xff1b;普通用户通过root创建&#xff0c;可以有多个…

【案例教程】地下水环评(一级)实践技术及Modflow地下水数值模拟

【前沿】地下水数值模拟技术应用与地下水环评报告编制方法实践线上直播课程&#xff0c;主要围绕的环评导则&#xff0c;结合不同行业类别&#xff0c;实例讲解地下水环境影响评价的原则、内容、工作程序、方法。包括数据处理分析、数值模型构建以及环评报告编写等。涉及地下水…

【自学C++】C++ int

C int C int教程 C 中的 int 用来表示一个 整数&#xff0c;也可以叫做整型&#xff0c;int 的取值范围是介于 short 和 long 之间的。 C int定义详解 语法 int varname value;参数 参数描述int定义 int 类型变量使用的类型。varname变量名。value可选&#xff0c;变量的…

Linux应用编程---9.消息队列

Linux应用编程—9.消息队列 ​ 消息队列用于进程之间的通讯&#xff0c;可以在如父子进程、兄弟进程这样的具有亲缘关系的进程之间传递数据&#xff0c;也可以用于具有非亲缘关系的进程之间通讯。消息队列可以传递结构体&#xff0c;所以可以发送任意数据类型。与消息队列有关…

数据结构(一)——链表

链表与邻接表 介绍 链表作为一种基础数据结构&#xff0c;具有几个特点&#xff1a; 优点&#xff1a;插入、删除非常快&#xff08;需要知道需要插入和删除节点前一个位置&#xff09;缺点&#xff1a;查询、访问&#xff08;用索引&#xff09;非常的慢 链表的创建方法一…

Selenium用法详解【cookies操作】【JAVA爬虫】

简介本文主要讲解java代码利用Selenium控制浏览器获取网站的cookies,对网站cookies的相关操作教程。cookies操作cookies 是识别用户登录与否的关键&#xff0c;爬虫中常常使用 selenium jsoup 实现 cookie持久化&#xff0c;即先用 selenium 模拟登陆获取 cookie &#xff0c;…

你可能从未想过的:人工智能未来50年的安全领域问题

前言 随着人工智能技术的普及和发展&#xff0c;很多人工智能出现的故障和问题也会愈发明显。本文简单讲述了未来50年人工智能发展过程中可能会出现的景象和问题。 一、人工智能独立 尽管很可能第一批人工智能是由人类发明制作的&#xff0c;但随着大量基础设施的完善&#x…

javaweb-会话技术CookieSession

文章目录会话技术Cookie&Session1&#xff0c;会话跟踪技术的概述2&#xff0c;Cookie2.1 Cookie的基本使用2.2 Cookie的原理分析2.3 Cookie的使用细节2.3.1 Cookie的存活时间2.3.2 Cookie存储中文3&#xff0c;Session3.1 Session的基本使用3.2 Session的原理分析3.3 Sess…

4.8、网际控制报文协议 ICMP

为了更有效地转发 IP 数据报和提高交付成功的机会 在网际层使用了网际控制报文协议 ICMP(Internet Control Message Protocol)。 主机或路由器使用 ICMP 来发送 差错报告报文\color{red}差错报告报文差错报告报文和询问报文\color{red}询问报文询问报文。 ICMP报文被封装在IP…