当前位置: 首页 > news >正文

【手把手带你学JavaSE】泛型的理解与使用

目录

  • 一、初识泛型
  • 二、泛型类的定义
    • 2.1 语法
    • 2.2 举一个例子
  • 三、泛型类的使用
    • 3.1 语法
    • 3.2 类型推导(Type Inference)
    • 3.3 小结
    • 3.4 附加: 裸类型(Raw Type)
  • 四、泛型的编译
    • 4.1 擦除机制
    • 4.2 不能实例化泛型类数组
  • 五、泛型方法
    • 5.1 语法
  • 六、泛型的上界
    • 6.1 语法
  • 七、通配符
    • 7.1 什么是通配符?
    • 7.2 通配符上界
    • 7.3 通配符下界

一、初识泛型

什么是泛型?

泛型程序设计(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。Ada、Delphi、Eiffel、Java、C#、F#、Swift 和 Visual Basic .NET 称之为泛型(generics);ML、Scala 和 Haskell 称之为参数多态(parametric polymorphism);C++ 和 D称之为模板。具有广泛影响的1994年版的《Design Patterns》一书称之为参数化类型(parameterized type)。
(来源百度)

为什么要使用泛型?
一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的 代码,这种刻板的限制对代码的束缚就会很大。----- 来源《Java编程思想》对泛型的介绍。

泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现参数化。

泛型的主要目的:就是指定当前的容器,要持有什么类型的对象,让编译器去做检查;此时,就需要把类型,作为参数传递;需要什么类型,就传入什么类型。

二、泛型类的定义

2.1 语法

类名后的<T> 代表占位符,表示当前类是一个泛型类,T是形参,用来接收传入的数据类型(实参)。

class 泛型类名称<类型形参列表> {
	//这里可以使用类型参数
}

class ClassName<T1, T2, ..., Tn> {
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数*/ {
	//这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
	//可以只使用部分类型参数
}

【规范】类型形参一般使用一个大写字母表示,常用的名称有:

  • E 表示Element
  • K 表示Key
  • V 表示Value
  • N 表示Number
  • T 表示Type
  • S,U,V等等 - 第二、第三、第四个类型

2.2 举一个例子

public class MyArrayList {
    public Object[] array = new Object[10];
    public Object getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos, Object val) {
        this.array[pos] = val;
    }
}

像上面的代码默认为Object类,此时数组中可以存放任意类型的数据。
那么我们使用泛型是什么样子的呢?

public class MyArrayList <T>{
    public T[] array = (T[]) new Object[10];
    public T getPos(int pos) {
        return this.array[pos];
    }
    
    public void setVal(int pos, T val) {
        this.array[pos] = val;
    }
}

三、泛型类的使用

3.1 语法

// 定义一个泛型类引用
泛型类<类型实参> 变量名; 
// 实例化一个泛型类对象
new 泛型类<类型实参>(构造方法实参);

例如:

MyArray<Integer> list = new MyArray<Integer>();

注意:泛型只能接受类,所有的基本数据类型必须使用包装类!

3.2 类型推导(Type Inference)

当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写

MyArrayList<String> list = new MyArrayList<>(); 
// 可以推导出实例化需要的类型实参为 String

3.3 小结

  1. 泛型是将数据类型参数化,进行传递
  2. 使用表示当前类是一个泛型类
  3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换

3.4 附加: 裸类型(Raw Type)

裸类型是一个泛型类但没有带着类型实参,下面给出的就是一个裸类型

class MyArray<T> {
    //……
}
//裸类型
MyArray list = new MyArray();

裸类型并没有传递类型给形参,此时编译器也就不会自动进行类型检查和强制类型转换
所以我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制

四、泛型的编译

4.1 擦除机制

在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制;

Java的泛型机制是在编译期间实现的,编译器生成的字节码在运行期间并不包含泛型的类型信息。

4.2 不能实例化泛型类数组

泛型类数组是不能直接进行实例化的,去new一个泛型类的数组编译是通不过的

因为对于Java的数组来说,在编译时必须知道它持有的所有对象的具体类型,也就是说要实例化T类型的数组,编译器在编译时需要获得T类型;但我们知道泛型是具有擦除机制的,T类型在编译时会被擦除掉,此时也就不存在所谓的泛型了,自然也就无法进行实例化了;所以编译会报错。

五、泛型方法

5.1 语法

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表){
    //...
}

示例

public class Util {
    //静态的泛型方法 需要在static后用<>声明泛型类型参数
    public static <E> void swap(E[] array, int i, int j) {
        E t = array[i];
        array[i] = array[j];
        array[j] = t;
    }
}

使用示例
不使用类型推导

Integer[] a = { ... };
Util.<Integer>swap(a, 0, 9);

String[] b = { ... };
Util.<String>swap(b, 0, 9);

使用类型推导

Integer[] a = { ... };
swap(a, 0, 9);

String[] b = { ... };
swap(b, 0, 9);

六、泛型的上界

在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。

6.1 语法

class 泛型类名称<类型形参 extends 类型边界> {
   //...
}

泛型的的上界可以是类,也可以是接口;
上界为类,那么实参必须为上界的子类或者上界本身

//只接受 Number 的子类型作为 E 的类型实参
public class MyArray<E extends Number> {
    //...
}

MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型
MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型

上界为接口,那么实参必须是实现了该接口的类

//E必须是实现了Comparable接口的
public class MyArray<E extends Comparable<E>> {
    //...
}

七、通配符

7.1 什么是通配符?

?用于在泛型的使用,即为通配符

通配符是用来解决泛型无法协变的问题的,协变指的就是如果 Student 是 Person 的子类,那么 List 也应 该是 List 的子类。但是泛型是不支持这样的父子类关系的。

泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩充参数的范围.

7.2 通配符上界

? extends 类:设置泛型上限

<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类

通配符的上界,不能进行写入数据,只能进行读取数据。

7.3 通配符下界

? super 类:设置泛型下限

<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型

通配符的下界,不能进行读取数据,只能写入数据。

相关文章:

  • flink常见问题(持续更新)
  • ⌈Linux_ 感受系统美学⌋ 剖释“Linux下一切皆文件” | 底层级操作增进Linux内功
  • 【Java知识点大全】
  • 【华为机试真题 Python实现】矩阵扩散
  • 建立对单片机/嵌入式启动、运行的整体认知
  • 猿创征文 | 什么是PHP,PHP如何创建数据库
  • Kubernetes — StatefulSet 管理与使用
  • 想学习网络安全一定要学习web
  • 【leetcode刷题】数组篇
  • 基于VUE+Echarts大屏数据展示150套 (集合)
  • 【深度学习100例】—— 基于pytorch使用LSTM进行文本情感分析 | 第7例
  • 【基础巩固】详细总结对数组的理解
  • ⌈Linux_ 感受系统美学⌋ 剖释“Linux下一切皆文件” ,底层级操作增进Linux内功
  • 哪些是模糊用语-《软件方法》自测题解析020
  • 【设计模式】-创建型模式-第2章第5讲-【对象池模式】
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • css系列之关于字体的事
  • gitlab-ci配置详解(一)
  • httpie使用详解
  • Idea+maven+scala构建包并在spark on yarn 运行
  • leetcode388. Longest Absolute File Path
  • Linux Process Manage
  • React组件设计模式(一)
  • SpiderData 2019年2月23日 DApp数据排行榜
  • underscore源码剖析之整体架构
  • v-if和v-for连用出现的问题
  • VUE es6技巧写法(持续更新中~~~)
  • 分类模型——Logistics Regression
  • 面试遇到的一些题
  • 前端面试总结(at, md)
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • 新书推荐|Windows黑客编程技术详解
  • 用quicker-worker.js轻松跑一个大数据遍历
  • 运行时添加log4j2的appender
  • # 计算机视觉入门
  • #1015 : KMP算法
  • #Z0458. 树的中心2
  • $ git push -u origin master 推送到远程库出错
  • (1)SpringCloud 整合Python
  • (分布式缓存)Redis哨兵
  • (九十四)函数和二维数组
  • (一) storm的集群安装与配置
  • .CSS-hover 的解释
  • .net core Swagger 过滤部分Api
  • .net中调用windows performance记录性能信息
  • .one4-V-XXXXXXXX勒索病毒数据怎么处理|数据解密恢复
  • @staticmethod和@classmethod的作用与区别
  • @Transactional类内部访问失效原因详解
  • []利用定点式具实现:文件读取,完成不同进制之间的
  • [20170728]oracle保留字.txt
  • [BZOJ 3680]吊打XXX(模拟退火)
  • [BZOJ1178][Apio2009]CONVENTION会议中心
  • [BZOJ4016][FJOI2014]最短路径树问题
  • [bzoj4240] 有趣的家庭菜园
  • [C#]OpenCvSharp结合yolov8-face实现L2CS-Net眼睛注视方向估计或者人脸朝向估计