JDK1.5 新特性【泛型】

前言

        泛型在 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 。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

复旦EMBA美东国际课程走进哈佛、耶鲁、麻省理工、哥大等顶尖名校

2023夏末秋初&#xff0c;复旦大学EMBA“问道东西”国际课程重新起航&#xff0c;同学们来到美国东海岸&#xff0c;走进顶级名校&#xff0c;开启学习与交流。    同学感悟      此次美东国际课程&#xff0c;整个设计非常合理。哈佛大学&#xff0c;麻省理工以及哥伦…

图解系列--认证

单向散列函数 1.什么是单向散列函数 单向散列函数有一个输入和一个输出&#xff0c;其中输入称为消息&#xff0c;输出称为散列值。单向散列函数可以根据消息的内容计算出散列值&#xff0c;而散列值就可以被用来检查消息的完整性。 在指定的散列函数处理下&#xff0c;无论输…

linux 定时执行脚本

先写一个简单的shell脚本用来测试定时执行脚本 [rootVM-12-12-centos wz]# cat shell_cron_test.sh #!/bin/bashif [ -f "/home/wz/cron_test.txt" ];thennum$(($(wc -l /home/wz/cron_test.txt | cut -d -f 1)1))elsenum1 fi echo "$(date "%y-%m-%d …

Flume的安装部署及常见问题解决

1.安装地址 &#xff08;1&#xff09; Flume官网地址&#xff1a;http://flume.apache.org/ &#xff08;2&#xff09;文档查看地址&#xff1a;http://flume.apache.org/FlumeUserGuide.html &#xff08;3&#xff09;下载地址&#xff1a;http://archive.apache.org/dist…

利用SD存储介质扩展MAXQ20000的非易失性数据存储空间

SD存储卡是一种可移动存储介质&#xff0c;通常用于相机、手机、平板电脑等设备中存储照片、视频、音乐等数据。SD存储卡的全称为Secure Digital Memory Card&#xff0c;是由SD Card Association制定的一种标准格式。它具有体积小、存储容量大、读写速度快、价格低廉等优点。目…

在线随机字符串生成工具

具体请前往&#xff1a;在线随机字符串生成器--通过该工具生成动态复杂随机密码,随机字符串等&#xff0c;加密盐等

PC业务校验(已有该名称,已有该编码)

rules: {name: [{ required: true, message: "部门名称不能为空", trigger: "blur" },{min: 2,max: 10,message: "部门名称的长度为2-10个字符",trigger: "blur",},{trigger: "blur",validator: async (rule, value, callba…

命令执行相关函数及各类命令执行绕过技巧

相关函数 &#xff08;命令注入&#xff09; 命令执行的绕过

DSP2335的LED工程笔记

首先是确定时钟 在技术参考中&#xff0c;找到时钟章节 只能观察每个寄存器&#xff0c;才能看到寄存器控制那个外设的时钟 第二找到对应GPIO以及寄存器&#xff1b; 在我板子里面的原理图是 但是TI的提供的库函数是分ABC的&#xff0c;刚开始就不知道怎麽分。GPIO68到GPIO6…

4.Pod详解【四】

文章目录 4. Pod详解4.1 Pod介绍4.1.1 Pod结构4.1.2 Pod定义 4.2 Pod配置4.2.1 基本配置4.2.2 镜像拉取4.2.3 启动命令4.2.4 环境变量4.2.5 端口设置4.2.6 资源配额 4.3 Pod生命周期4.3.1 创建和终止4.3.2 初始化容器4.3.3 钩子函数4.3.4 容器探测4.3.5 重启策略 4.4 Pod调度4.…

支持4KHz回报还能无线充电,简约不简单的雷柏VT3S游戏鼠标上手

这两年国产鼠标的表现很让人惊喜&#xff0c;不仅外观做工越来越精细&#xff0c;配置也越来越强大&#xff0c;当然价格依然亲民。现在很容易找到一款搭载高端传感器、响应速度快、电池续航时间长&#xff0c;并且还支持无线充电的全能型鼠标。 我之前用雷柏的鼠标比较多&…

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

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

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

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

内容运营策略:个性化推荐

一、推荐系统流程 典型的推荐系统包括3个部分&#xff0c;即召回层&#xff08; Recall )、排序层&#xff08; Rank )和重排层&#xff08; ReRank )。 1&#xff0e;召回层&#xff08; Recall ) 召回层主要是从全量库中首先获取用户可能感兴趣的候选集&#xff0c;是推荐系…

【Qt开发流程之】窗口部件

qt类关系图 创建Qt项目时&#xff0c;发现提供的窗体默认 基类有&#xff1a;QMainWindow、QDialog、QWidget这三种。 之后&#xff0c;你会发现&#xff0c;这3中窗体在UI交互中&#xff0c;用的也是最多的。 以下是Qt类关系图&#xff1a; 基础窗口控件QWidget 由上图可以…

Swin Transformer

Swin Transformer 简介 下采样的层级设计&#xff0c;能够逐渐增大感受野。采用window进行注意力计算&#xff0c;极大降低了内存消耗&#xff0c;避免了整张图像尺寸大小的qkv矩阵滑窗操作包括不重叠的 local window&#xff0c;和重叠的 cross-window。不重叠的local window…

volatile 无法保证原子性 案例展示

volatile 无法保证原子性 在 Java 中&#xff0c;原子性是指一个操作是不可中断的&#xff0c;要么都执行要么都不执行。 但是 volatile 修饰的变量&#xff0c;只是保证了从主内存加载到工作内存的值是最新的&#xff0c;并不能保证对变量的操作是原子性的 变量的写操作和读…

关于缓存和数据库一致性问题的深入研究

如何保证缓存和数据库一致性&#xff0c;这是一个老生常谈的话题了。 但很多人对这个问题&#xff0c;依旧有很多疑惑&#xff1a; 到底是更新缓存还是删缓存&#xff1f;到底选择先更新数据库&#xff0c;再删除缓存&#xff0c;还是先删除缓存&#xff0c;再更新数据库&…

Spring Boot中实现支付宝、微信和银联支付的功能

Spring Boot中实现支付宝、微信和银联支付的功能 在Spring Boot中实现支付宝、微信和银联支付的功能&#xff0c;通常需要使用它们各自的SDK&#xff08;Software Development Kit&#xff09;。以下是一个简单的示例代码&#xff0c;演示了如何在Spring Boot项目中集成支付宝…

操作系统:操作系统教程第六版(骆斌、葛季栋、费翔林)习题一计算机操作系统概述

目录 前言1. 思考题2. 应用题 前言 本系列文章是针对操作系统教程第六版&#xff08;骆斌、葛季栋、费翔林&#xff09;的习题解答&#xff0c;其中简答题部分为博主自己搜索整理的&#xff0c;错漏之处在所难免。应用题部分有答案为依据。 1. 思考题 &#xff08;1&#xf…