JDK1.5 新特性【泛型】

news2024/10/7 14:27:10

前言

        泛型在 JavaSE 阶段是学习过的,但是毕竟处理定义一些简单的集合就很少用到它了,至于最近 Flink 中遇到的 泛型方法,更是感觉闻所未闻,以及源码中加在接口、方法、类前的各种 <T,V> 让我实在自觉羞愧,于是今天就来专门深入学习一下泛型。为了和前几篇文章对应,这里就叫 JDK1.5 新特性吧。

        后续应该还会再去深入学习一些基础的东西,比如注解反射,不用它就学不会。

泛型

1、为什么要使用泛型

要回答这个问题,我们先看看现在我们是怎么使用泛型的。

/**
         * 泛型定义了这个 ArrayList 存储的类型都是 String 类型
         */
        ArrayList<String> list = new ArrayList<>();
        list.add("name");
        list.add(10);    //编译报错

我们可以看到,当我们定义了一个泛型为 String 类型的 ArrayList 集合时,当我们往进添加一条 int 类型的数据就会报错,这是因为这里的泛型约束了我们的集合中的数据类型必须都是一致的 String 类型。

那么在 JDK1.5 之前,在没有引入泛型之前是怎么防止数据类型不一致的呢?

/**
         * 在 JDK1.5 之前是没有泛型的
         * 所以当时的 集合 存储的都是 Object 类型
         */
        ArrayList list = new ArrayList();
        list.add("hello");
        list.add(10);

在 JDK1.5 之前,集合存储的都是 Object 类型的数据,所以不管是数值类型、String 还是别的引用类型,通通可以存进去而且不会报错。但是这也给我们后期处理数据造成了麻烦。

Iterator iterator = old_list.iterator();
        while (iterator.hasNext()){
            // 遍历到 10 会报错 java.lang.Integer cannot be cast to java.lang.String
            String next = (String) iterator.next(); 
            System.out.println(next);            
}

对于上面集合中的数据,集合中存在多种数据类型,当我们使用迭代器或者循环遍历时,必然会因为取出数据的类型不一致造成报错,因为我们没法判断下一个数据的类型。

那么当时的集合是怎么进行使用的呢?

对于我们上面的集合(包含 String 和 int 两种数据类型),放在当时是这样的:

/**
             * 所以在 JDK1.5 之前 使用集合需要判断元素类型
             */
            Object object = iterator.next();
            if (object instanceof String){
                String next = (String) object;
                System.out.println(next);
            }else if (object instanceof Integer){
                int next = (int) object;
                System.out.println(next);
            }

我们需要判断每个数据的类型,防止因为数据类型不一致而转换失败。很明显这种方式是极其复杂繁琐的。

2、泛型概述

所以为什么要使用泛型?因为泛型提供了编译时的类型安全检测机制,它可以帮助我们程序员在编译时就检测到非法的类型。

泛型的本质是参数化类型,也就是说操作的数据类型被指定为一个参数。

泛型是一种把数据类型的明确工作推迟到创建对象或者调用方法的时候才去明确的特殊类型。

注意:泛型参数只能是引用类型,不能是原始类型(比如 int、double、float、char)。

泛型可以使用在 方法、接口、类,分别称作:泛型类、泛型方法和泛型接口。

3、泛型类

3.1、基本格式

基本格式:修饰符 class 类名<类型>{}

public class Student<T>{}

注意:这里的 T 可以随便写,但是我们常用的是 T(Type)、E(Element Java集合框架中经常使用)、K(Key)、V(Value) 等参数类型来表示泛型。

3.1、泛型类的使用

我们先定义一个普通的 Student 类,只有一个 id 属性:

public class Student {
    public String id;   // 学号

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

我们可以这样来得到它的属性。 

Student<String> student = new Student<>();
        student.setId("2023001");
        System.out.println(student.getId());

        但是,我们的学号可能是 String 类型,也可能是 int 类型,这该怎么办?难道把 id 设置为 Object 类型?设置为 Object 类型当然是可以的,但是当我们要对该学号进行一些处理的时候(比如学号+1 或者 学号拼接一个字符串)就有可能又涉及到强制转换了(Object -> String 或者 Object-> Integer)。

所以,这里我们这里就可以定义一个泛型类:

public class Student<T> {
    public T id;   // 学号

    public T getId() {
        return id;
    }

    public void setId(T id) {
        this.id = id;
    }
}
   Student<String> student = new Student<>();
        student.setId("2023001");
        System.out.println(student.getId());
        
        // 注意:泛型只能是引用类型
        Student<Integer> stu = new Student<>();
        stu.setId(2023001);
        System.out.println(stu.getId());

        Student<Double> st = new Student<>();
        st.setId(12.0);
        System.out.println(st.getId());

可以看到,我们定义泛型类后,每次创建 Student 对象时,我们可以随意地指定它参数的类型。

注意:我们虽然指定了泛型,但是我们依然可以创建一个没有指定泛型的对象,只不过此时我们类的属性类型以及 getter 方法的返回值都变成了 Object,setter 方法的参数也变成了 Object,所以这当我们要取值的时候,又要进行一个类型的判断。所以当我们既然使用了泛型类,就一定要指定泛型参数!

4、泛型方法

4.1、基本格式

基本格式:修饰符 <泛型参数> 返回值类型 方法名 (类型 变量名){}

public <T> void show(T t){...}

4.2、泛型方法的使用

我们继续定义一个 Person 类,我们需要定义一个 show 方法,你给它传什么类型的参数,它就返回什么类型的参数。因为参数类型不同,所有这里我们需要多次重载该 show 方法:

public class Person {

    public Integer show(int grade){
        return grade;
    }

    public String show(String grade){
        return grade;
    }

    public Double show(double grade){
        return grade;
    }
}

但是很明显,这样代码的冗余度很高。所以我们可以先使用泛型类来进行优化:

public class Person<T> {
    public T show(T grade) {
        return grade;
    }
}

这样明显我们类中的代码简洁了很多,但是我们在调用该方法的时候依然很复杂:

Person<String> a = new Person<>();
        String res1 = a.show("100");
        Person<Integer> b = new Person<>();
        int res2 = b.show(95);
        Person<Double> c = new Person<>();
        double res3 = c.show(95.8);

这就需要我们定义一个泛型方法:

public class Person {
    public<T> T show(T grade) {
        return grade;
    }
}

使用泛型方法: 

Person person = new Person();
        String res1 = student.show("s");
        int res2 = student.show(90);
        double res3 = student.show(96.5);

很明显,使用泛型方法后,不管是类的定义还是方法的调用都是最简洁的。

5、泛型接口

5.1、基本格式

基本格式:修饰符 interface 接口名<类型>{...}

public interface MyInterface<T>{...}

5.2、泛型接口的使用

5.2.1、普通方法

我们定义一个接口,并定义一个抽象方法,要求它的所有实现类的方法参数类型和返回值类型都是统一的类型。

public interface MyInterface <T>{
    T show(T t);
}

注意:这里接口中的方法并不是泛型方法,所以它的 T 指的就是泛型接口中的 T。 

public class Person implements MyInterface<String>{
    @Override
    public String show(String grade) {
        return grade;
    }
}

可以看到,使用泛型接口,就相当于约束了其实现类的一些实现要求(比如上面 这个泛型接口,就要求了它的实现类必须实现 show 方法,并且该方法的参数类型和返回值类型都与接口的泛型类型必须保持一致)。

5.2.2、泛型方法

那,如果我们要实现上面 4.2 中的效果,也就是说这个show方法传入什么类型的参数,返回什么类型的结果,这又该怎么实现呢?我们可以在接口中定义一个泛型方法:

public interface MyInterface <T>{
    <M> M show(M m);
}

注意:泛型接口中的泛型类型是用来约束其 实现类 或者 非泛型方法的参数类型和返回值类型(比如上面 5.1.1)或者泛型方法的参数类型或者返回值类型之一(因为如果一个泛型方法的返回值类型和参数类型都是接口的泛型类型那么这个泛型方法就没有意义了),而泛型方法中的泛型类型是用来约束方法的返回值类型和参数类型的。

public class Person<T> implements MyInterface<T>{
    @Override
    public <M> M show(M m) {
        return m;
    }
}

我们调用方法:

Person<String> person = new Person<>();
        String res1 = person.show("100");
        int res2 = person.show(96);
        double res3 = person.show(98.5);

可以看到我们这个Student类的泛型 String 是完全没有必要的,所以我们可以这样改造:

public interface MyInterface{
    <M> M show(M m);
}
public class Person implements MyInterface{
    @Override
    public <M> M show(M m) {
        return m;
    }
}
Person person = new Person();
        String res1 = person.show("100");
        int res2 = person.show(96);
        double res3 = person.show(98.5);

6、泛型类型通配符

  • 类型通配符 <?> 一般用于接受使用,不能够做添加。
  • List <?> 表示元素类型未知的 list ,它的元素可以匹配任何类型。
  • 带通配符的 List 仅表示它是各种泛型 list 的父类,并不能把元素添加到其中(不能调用 add方法 )。

6.1、泛型类型通配符的使用

public class Test {

    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("tom");
        list1.add("bob");
        list1.add("mike");
        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(10);
        list2.add(18);
        list2.add(20);

        printList(list1);
        printList(list2);
    }


    /**
     * 不知道接受到的list会是什么类型
     * @param list 不能调用 add
     */
    public static void printList(List<?> list){
        list.forEach(System.out::println);
    }
    

}

6.2、泛型类型通配符的上限和下限

类型通配符的上限:<? extends A> 表示类型必须是 A 或者 A 的子类。

类型通配符的下限:<? super B> 表示类型必须是 B 或者 B 的父类。

7、泛型擦除机制

泛型只在编译阶段限制参数类型的传递,在运行阶段都会被擦除,也就是说在运行时我们的 .class 文件中是没有泛型的!

我们可以通过反编译工具将我们的 .class 文件反编译为 .java 文件,完成后,我们就会发现,我们所定义的泛型 <T>、<K>... 其实在编译后都变成了 Object 。

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

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

相关文章

leetcode34.排序数组中查找元素第一个和最后一个位置两种解题方法(超详细)

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/?envTypelist&envIdZCa7r67M这道题&#xff0c;读者可能会说这道题有什么好…

二阶低通滤波器(二阶巴特沃斯滤波器)

连续传递函数G(s) 离散传递函数G(z) 差分方程形式 二阶巴特沃斯滤波器参数设计 设计采样频率100Hz&#xff0c;截止频率33Hz。 注意&#xff1a;设计参数使用在离散系统中&#xff01; 同理&#xff0c;其他不同阶数不同类型的滤波器设计&#xff0c;如二阶高通滤波器、二阶…

Transformer ZOO

Natural Language Processing Transformer:Attention is all you need URL(46589)2017.6 提出Attention机制可以替代卷积框架。引入Position Encoding&#xff0c;用来为序列添加前后文关系。注意力机制中包含了全局信息自注意力机制在建模序列数据中的长期依赖关系方面表现出…

嵌入式开发--赛普拉斯cypress的铁电存储器FM25CL64B

嵌入式开发–赛普拉斯cypress的铁电存储器FM25CL64B 简介 FM25CL64B是赛普拉斯cypress出品的一款铁电存储器&#xff0c;这种存储器最大的优势是可以像RAM一样随机存储&#xff0c;和按字节写入&#xff0c;也可以像ROM一样掉电仍然可以保存数据&#xff0c;是一种相当优秀的…

宠物信息服务预约小程序的效果如何

宠物的作用越来越重要&#xff0c;因此铲屎官们对自己爱宠的照顾也是加倍提升&#xff0c;而市场围绕宠物展开的细分服务近些年来逐渐增多&#xff0c;且市场规模快速增长。涉及之广&#xff0c;涵盖宠物衣食住行、医疗、美容、婚丧嫁娶等&#xff0c;各品牌争相抢夺客户及抢占…

java游戏制作-拼图游戏

一.制作主界面 首先创建一个Java项目命名为puzzlegame。 再在src中创建一个包&#xff0c;用来制作主界面 代码&#xff1a; 结果&#xff1a; 二.设置界面 代码&#xff1a; 三.初始化界面 代码&#xff1a; 优化代码&#xff1a; 结果&#xff1a; 四.添加图片 先在Java项…

思维模型 留白效应

本系列文章 主要是 分享 思维模型 &#xff0c;涉及各个领域&#xff0c;重在提升认知。因留白而遐想。 1 留白效应的应用 1.1 留白效应在艺术领域的应用 欧洲的艺术和设计领域有很多经典的实际案例&#xff0c;其中荷兰画家文森特梵高的作品《星夜》是一幅非常著名的油画&am…

【沐风老师】3DMAX一键云生成器插件使用教程

3DMAX云生成器插件使用教程 3DMAX云生成器插件&#xff0c;是一款将物体变成云的简单而富有创意的工具。该工具通过在物体周围创建粒子结合材质&#xff0c;最终形成渲染后的云的效果。 【支持版本】 3dMax2018 – 2023 默认的扫描线渲染器 【安装方法】 1.复制“安装文件”…

4、FFmpeg命令行操作10

音视频处理流程 先看两条命令 ffmpeg -i test_1920x1080.mp4 -acodec copy -vcodec libx264 -s 1280x720 test_1280x720.flv ffmpeg -i test_1920x1080.mp4 -acodec copy -vcodec libx265 -s 1280x720 test_1280x720.mkv ffmpeg音视频处理流程

Mysql之单行函数

Mysql之单行函数 单行函数数值类型函数字符串类型的函数日期和时间函数加密与解密函数信息函数 单行函数 函数的定义 函数在计算机语言的使用中贯穿始终&#xff0c;函数的作用是什么呢&#xff1f;它可以把我们经常使用的代码封装起来&#xff0c; 需要的时候直接调用即可。这…

Hive 定义变量 变量赋值 引用变量

Hive 定义变量 变量赋值 引用变量 变量 hive 中变量和属性命名空间 命名空间权限描述hivevar读写用户自定义变量hiveconf读写hive相关配置属性system读写java定义额配置属性env只读shell环境定义的环境变量 语法 Java对这个除env命名空间内容具有可读可写权利&#xff1b; …

MySQL 的执行原理(三)

5.4. InnoDB 中的统计数据 我们前边唠叨查询成本的时候经常用到一些统计数据&#xff0c;比如通过 SHOW TABLE STATUS 可以看到关于表的统计数据&#xff0c;通过 SHOW INDEX 可以看到关于索引 的统计数据&#xff0c;那么这些统计数据是怎么来的呢&#xff1f;它们是以什么方…

4种经典的限流算法

0、基础知识 1000毫秒内&#xff0c;允许2个请求&#xff0c;其他请求全部拒绝。 不拒绝就可能往db打请求&#xff0c;把db干爆~ interval 1000 rate 2&#xff1b; 一、固定窗口限流 固定窗口限流算法&#xff08;Fixed Window Rate Limiting Algorithm&#xff09;是…

pm2在Windows环境中的使用

pm2 进程管理工具可以Windows操作系统上运行&#xff0c;当一台Windows电脑上需要运行多个进程时&#xff0c;或者运维时需要运行多个进程以提供服务时。可以使用pm2&#xff0c;而不再是使用脚本。 1. 使用PM2管理进程 1.1. 启动PM2项目 1.1.1. 直接启动项目 参数说明&…

c++ list容器使用详解

list容器概念 list是一个双向链表容器&#xff0c;可高效地进行插入删除元素。 List 特点&#xff1a; list不可以随机存取元素&#xff0c;所以不支持at.(position)函数与[]操作符。可以对其迭代器执行&#xff0c;但是不能这样操作迭代器&#xff1a;it3使用时包含 #includ…

C++ 运算符重载详解

本篇内容来源于对c课堂上学习内容的记录 通过定义函数实现任意数据类型的运算 假设我们定义了一个复数类&#xff0c;想要实现两个复数的相加肯定不能直接使用“”运算符&#xff0c;我们可以通过自定义一个函数来实现这个功能&#xff1a; #include <iostream> using…

RabbitMQ消息的可靠性

RabbitMQ消息的可靠性 一 生产者的可靠性 生产者重试 有时候由于网络问题&#xff0c;会出现连接MQ失败的情况&#xff0c;可以配置重连机制 注意&#xff1a;SpringAMQP的重试机制是阻塞式的&#xff0c;重试等待的时候&#xff0c;当前线程会等待。 spring:rabbitmq:conne…

MySQL 的执行原理(四)

5.5. MySQL 的查询重写规则 对于一些执行起来十分耗费性能的语句&#xff0c;MySQL 还是依据一些规则&#xff0c;竭尽全力的把这个很糟糕的语句转换成某种可以比较高效执行的形式&#xff0c;这个过程也可以 被称作查询重写。 5.5.1. 条件化简 我们编写的查询语句的搜索条件…

【STM32】ADC(模拟/数字转换)

一、ADC的简介 1.什么是ADC 1&#xff09;将【电信号】-->【电压】-->【数字量】 2&#xff09;ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字量&#xff0c;建立模拟电路到数字电路的桥梁。 3&#xff09;12位逐次逼近型ADC&#xff0c;1us转换时间&#xf…