java泛型详解

java泛型详解

目录

一、泛型类

二、泛型方法

三、类型变量限定

四、类型擦除

4.1与4.2:虚拟机中没有泛型

4.3:翻译泛型表达式

五、继承规则(通配符的上下限)

捕获通配符。

六、约束与局限性


大部分的泛型文章只涉及到泛型类与泛型方法等一些部分,在这一篇文章中,尽量对泛型有一个详细全面的基础描述,介绍实现自己的泛型需要了解的各种知识。

为什么使用泛型?

泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行类型强制转换的代码具有更好的安全性可读性。泛型程序设计意味着代码可以被很多不同类型的对象所重用

一、泛型类

一个泛型类就是一个具有一个或多个类型变量的类。

例如,定义一个名为Pair的泛型类

public class Pair<T>{private T first;private T second;}

Pair类引入一个类型变量T,用<>括起来,并放在类型后面。

例如,当你要声明一个Strng类型变量的Pair类时可以:

Pair<String> p;

代表:

public class Pair<String>{private Sting first;private Sting second;}

同样,当first与second的类型不一样时,也可以设计如下一个泛型类。

public class Pair<T,U>{private T first;private U second;}

声明时,Pair<Sting,Date>p;

注:在java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值,T(有时使用U和S)表示“任意类型”

二、泛型方法

除了定义一个泛型类之外,我们也可以定义一个泛型方法。泛型方法可以定义在普通类与泛型类当中。

在Pair类原有的基础上,我们再增加getter与setter方法。

public class Pair<T>{private T first;private T second;public <T> T getFirst(){return first;}public <T> setFirst(T first){this.first = first;}}

在泛型方法中,类型参数<T>放在修饰符(此处是public)后面,返回类型前面(此处是T)。

三、类型变量限定

有时候,我们需要对类型T做一些限定,比如传入的类型需要是能够进行比较排序的类型。

例如,我们定义一个找出最小值的泛型方法min,类型T是继承了Comparable接口的类型。

class sort{public static <T extendsComparable> T min(T[] a){T smallest = a[0];for(int i = 0;i < a.length;i++){//如果T不限定成是继承了comparable接口的类,则不能够保证传入的类型变量拥有compareTo方法,在程序运行时容易出错。If(smallest.compareTo(T[i]) > 0)Smallest = T[i];}}}

在java继承中,可以根据需要限定多个接口超类型,但最多限定一个类,限定类型用“&”分隔。

例如限定一个继承Comparable接口、Serializable接口与Pair类接口的子类类型,则min方法应该这么定义:

public static <T extends Pair & Comparable & Serializable>T min(T[] a){…}

注意:限定类时,类应该放在限定列表的第一个

四、类型擦除

首先,记住以下四点:

1、虚拟机(JVM)中,没有泛型,只有普通的类与方法;

2、所有的类型参数都用它们的限定类型替换;

3、为保持类型安全性,必要时插入强制类型转换(虚拟机自动完成此项工作);

4、桥方法被合成来保持多态(虚拟机自动完成此项工作);

下面将分别阐述以上四点

4.1与4.2:虚拟机中没有泛型

在虚拟机中,所有的类与方法都是普通类。无论何时定义一个泛型,在虚拟机中,都自动提供一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数(<T>)后的泛型类型名。

例如前面的Pair<T>的原始类型名就是Pair。而类型变量T 就会被擦除,替换成Object。

public class Pair<T>{private T first;private T second;}

类型擦除后:

public class Pair{private Object first;private Object second;}

当T是有单个或者多个限定类型时,T被替换成限定列表的第一个:

class sort{

 public static <T extendsComparable> T min(T[] a)

{

  T smallest = a[0];

  for(int i = 0;i < a.length;i++)

{

If(smallest.compareTo(T[i]) > 0)

  Smallest = T[i];

}

}

}

类型擦除后:

class sort{

 public static Comparable min(Comparable [] a)

{

  Comparable smallest = a[0];

  for(int i = 0;i < a.length;i++)

{

If(smallest.compareTo(Comparable [i]) > 0)

  Smallest = [i];

}

}

}

4.3:翻译泛型表达式

给出如下的一个泛型类

class Pair<T>{

private Tfirst;

private T second;

public Pair(T first,T second)

{

 this.first = first;

 this.second = second;

}

public T getFIrst()

{

return first;

}

}

   当类型擦除后变成了

class Pair{

private Objectfirst;

private Objectsecond;

public Pair(Object first,Object second)

{

  this.first = first;

  this.second = second;

}

public Object getFIrst()

{

return first;

}

}

考虑如下代码:

Pair<String> p = new Pair(“hello”,”world”);

String p1 = p.getFirst();

记住,由于类型擦除,Pair类里面的getFirst

 public Object getFIrst()

{

return first;

}

返回的是Object对象,编译器自动插入String的强制类型转换,类似于

String p1 = (Strng)p.getFirst();

4.4、桥方法

桥方法的存在是为了解决泛型方法类型擦除后产生的问题。

假设有一个类继承自Pair<String>

class PairChild extends Pair<String>{

  public void setFirst(String first)

{

//如果first是字符串“hello”,则调用父类的setFirst()方法

If(first == “hello”)

  super.setFirst(first);

}

}

这段代码在类型擦除后变成了:

class PairChild extends Pair{

//注意此处的setFirst(String first)方法

  public void setFirst(String first)

{

//如果first是字符串“hello”,则调用父类的setFirst()方法

If(first == “hello”)

  super.setFirst(first);

}

}

此时注意父类Pair

public class Pair<T>

{

private T first;

private T second;

public <T> setFirst(T first){

this.first = first;

}

}

类型擦除后变成:

public class Pair

{

private Objectfirst;

private Object second;

public void setFirst(Objectsecond){…}

}

这里有一个public voidsetFirst(Object second){…}方法。

子类PairChild继承了父类的public void setFirst(Object second)方法,同时自身又有一个public void setFirst(String first),此时,setFirst方法有了多态。

那么当有以下代码时:

PairChlid pc = new PairChild();

pc.setFirst(“world”);

由于拥有父类参数的方法能传入子类,此时的pc.setFirst(“world”)应该调用public void setFirst(Object second)还是publicvoid setFirst(String first)方法呢?

为了避免这种疑惑,虚拟机在PairChlid类中生成一个桥方法

class PairChild extends Pair<String>{

//覆盖了父类的setFirst(Object first)方法的同时,调用了setFirst(String first)方法

public void setFirst(Object first){

 setFirst((String)first);

}

  public void setFirst(String first)

{

//如果first是字符串“hello”,则调用父类的setFirst()方法

If(first == “hello”)

  super.setFirst(first);

}

}

五、继承规则(通配符的上下限)

可以说,通配符的存在是为了解决泛型中的继承问题。

假设有一个父类Father和一个子类child

有一个方法public static void min(father f){..}

此时这样调用方法

 Child c = new Child();

min(c);//编译通过

但是如果是泛型方法:public static voidmin(Pair<father> f){..}

这样调用

Pair<child> c = new Pair<child>();

min(c);//编译错误

Child是Father的子类,但是Pair<Child>不是Pair<Father>的子类。

要解决这一点很简单,引入一个通配符“?”。

泛型方法min可以这样设计:

public static void min(Pair<? extends Father> f){..}

表示传入的参数类型“?”是Father的某个子类类型或者Father本身

此时

Pair<child> c = new Pair<child>();

min(c);//编译通过

  另外一种情况,当传入的参数类型不要求是father的子类而要求是father类的父类时,泛型方法可以这样定义:

public static void min(Pair<? extends Father> f){..}

表示传入的类型参数“?”应该是father的父类。

如果传入的类型参数无类型要求时,min可以这样定义:

public static void min(Pair<? > f){..}

表示任何的Pair参数对象都可以传递进min方法。

注:可能会有人疑惑参数类型T与通配符?有何不同?T一般是在定义泛型类与泛型方法时使用,并且没有参数类型是继承自…或者是…的父类的意思,与?并不相同,?也不是类型变量。

当然 public static <T> void min(pair<T> f){…} 与public static void min(Pair<? > f){..}作用相同。

捕获通配符。

考虑有一个swap方法用于交换Pair类型的first与second属性,怎么交换?。

public static void swap(Pair<? > f){

..   ? temp = f.getFirst();//编译错误,?并不是类型变量

}

此时应该用T先写一个方法swapTemp来捕获通配符。

public static <T> void swapTemp(Pair<T> f){

..   T temp = f.getFirst();

   f.setFirst(f.getSecond());

   f.setSecond(temp);

}

再将swap改写成:

public static void swap(Pair<? > f){

..   swapTemp (f);

}

六、约束与局限性

终于来到最后一个部分,这里简单阐述一下泛型的一些局限。

1、不能用基本类型实例化类型参数,没有Pair<int>。

2、运行时的类型检查只适应于原始类型查询。(原因参见类型擦除的4.1)

  例如:Pair<String>s = new Pair<String>();

  s instanceof Pair<String> 是错误的.

无论何种Pair,只能使用s instanceof Pair。GetClass方法返回的也是Pair。

3、不能创建参数化类型的数组。例如:Pair<String>[] ss = new Pair<String>[10]是错误的。当然,声明Pair<String>[] ss是可以的,只不过不能用new初始化。

4、不能实例化类型变量。如new T(…)是错误的,一般用getter与setter设置泛型属性。

5、禁止使用带有类型变量的静态域与方法。

如public static T first 与publicstatic T getFirst()是禁止的。

6、注意类型擦除后的冲突

 参考:《java核心技术卷I》

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

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

相关文章

Matlab进阶绘图第47期—气泡分组蝴蝶图

气泡分组蝴蝶图是分组蝴蝶图与气泡图的组合——在分组蝴蝶图每组柱子上方添加大小不同的气泡&#xff0c;用于表示另外一个数据变量&#xff08;如每组柱子值的和&#xff09;的大小。 本文利用自己制作的BubbleButterfly工具&#xff0c;进行气泡分组蝴蝶图的绘制&#xff0c…

Docker 入门使用说明

Docker 入门使用说明 Docker 安装 Docker 官网&#xff1a;Docker Docker 安装说明&#xff1a;Docker 安装说明 这里由于 Docker 在实时更新&#xff0c;所以每次安装 Docker 用来导入 key 的链接可能会有变化&#xff0c;这里就参考官方的安装方法即可 Docker 常用命令说…

用go实现一个任务调度类 (泛型)

用go实现一个任务调度类 &#xff08;泛型&#xff09; 源码地址&#xff1a; https://github.com/robinfoxnan/BirdTalkServer/blob/main/server/core/workmanager.go 1.概述 实现了一个简单的任务管理系统&#xff0c;允许用户定义任务和工作者&#xff0c;并将任务分配给…

ARM 用串口来实现灯的点亮

main.c #include "uart4.h"//封装延时函数void delay(int ms){int i,j;for(i0;i<ms;i){for(j0;j<2000;j){}}}int main(){led_init();uart4_init();//char buf[128];char *str;//char i;while(1){/*igetchar();putchar(i1);putchar(\n);putchar(\r);*/strgets(…

013_Linux(上传rz,下载sz,tar,zip,unzip)

目录 一、上传、下载 1、通过鼠标操作 &#xff08;1&#xff09;下载 &#xff08;2&#xff09;上传 2、通过命令操作 rz、sz &#xff08;1&#xff09;下载 sz &#xff08;2&#xff09;上传 rz 二、压缩、解压 1、tar命令 &#xff08;1&#xff09;压缩 &…

Jenkins hudson.plugins.git.GitSCM.ALLOW_LOCAL_CHECKOUT 属性设置问题

ERROR: Checkout of Git remote ‘local/path’ aborted because it references a local directory, which may be insecure. You can allow local checkouts anyway by setting the system property ‘hudson.plugins.git.GitSCM.ALLOW_LOCAL_CHECKOUT’ to true. Finished: F…

PMP考试备考——项目管理标准

项目的定义和特性 项目 是为创造独特的产品、服务或成果而进行的临时性工作。这意味着项目具有以下几个关键特征&#xff1a; 临时性&#xff1a;项目有一个明确的开始和结束日期&#xff0c;不同于持续运营的工作。独特性&#xff1a;每个项目都有其独特之处&#xff0c;即使…

QT----基于QT的人脸考勤系统ubuntu系统运行,编译开发板

目录 1 Ubantu编译opencv和seetaface库1.1 Ubantu编译opencv1.2 Ubuntu编译seetaface1.3 安装qt 2 更改代码2.1 直接运行报错/usr/bin/ld: cannot find -lGL: No such file or directory2.2 遇到报错摄像头打不开2.3 修改部分代码2.4 解决中文语音输出问题 3 尝试交叉编译rk358…

【 Mysql8.0 忘记登录密码 可以试试 】

** Mysql8.0 忘记登录密码 可以试试 ** 2024-3-21 段子手168 1、首先停止 mysql 服务 &#xff0c;WIN R 打开运行&#xff0c;输入 services.msc 回车打开服务&#xff0c;找到 mysql 服务&#xff0c;停止。 然后 WIN R 打开运行&#xff0c;输入 CMD 打开控制台终端输…

yolov5训练并生成rknn模型部署在RK3588开发板上,实现NPU加速推理

简介 RK3588是瑞芯微&#xff08;Rockchip&#xff09;公司推出的一款高性能、低功耗的集成电路芯片。它采用了先进的28纳米工艺技术&#xff0c;并配备了八核心的ARM Cortex-A76和Cortex-A55处理器&#xff0c;以及ARM Mali-G76 GPU。该芯片支持多种接口和功能&#xff0c;适…

构建Pytorch虚拟环境教程

构建PyTorch虚拟环境通常涉及使用诸如Anaconda或venv等工具来管理Python环境&#xff0c;以便在一个独立的空间中安装PyTorch和其他依赖项。以下是使用Anaconda创建PyTorch虚拟环境的步骤&#xff08;适用于不同操作系统&#xff0c;包括Windows、Linux和MacOS&#xff09;&…

数据分析-Pandas数据分类的转换控制

数据分析-Pandas数据分类的转换控制 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&am…

vue的URL和函数优化

URL优化 async…await 用于同步接收网络请求的结果 常规的代码 export async function articleGetAllService () {//发送异步请求&#xff0c;获取所有文章数据//同步等待服务器响应的结果&#xff0c;并返回&#xff0c;async,awaitreturn await axios.get(http://localhost:…

【 Vue.js 属性 | 生命周期 】

computed计算属性 规则&#xff1a; 1.用已有的属性计算不存在的属性 2.默认调用一次get() 3.简写时注意&#xff1a; 只有值不发生改变才可以是用简写&#xff08;函数&#xff09;&#xff0c;值发生改变必须使用对象&#xff0c;才可以配置set()方法 4.底层原理使用 Object.…

shell脚本入门练习(非常详细)零基础入门到精通,收藏这一篇就够了

【脚本1】打印形状 打印等腰三角形、直角三角形、倒直角三角形、菱形 #!/bin/bash \# 等腰三角形 read \-p "Please input the length: " n for i in \seq 1 $n\ do for ((j\$n;j>i;j--)) do echo \-n " " done for m in \seq 1 $i\ do…

淘宝1688京东...商品详情数据采集,按关键词搜索商品列表

淘宝、1688、京东等电商平台的商品详情数据采集以及按关键词搜索商品列表&#xff0c;通常可以通过以下几种方法实现&#xff1a; 请求示例&#xff0c;API接口接入Anzexi58 一、使用API接口 这些电商平台通常都提供开放API接口&#xff0c;允许开发者调用接口获取所需的数据…

【Linux】网络编程套接字一

网络编程套接字一 1.预备知识1.1理解源IP地址和目的IP地址1.2认识端口号1.3认识TCP协议1.4认识UDP协议1.5网络字节序 2.socket编程接口3.UDP网络程序3.1UDP Server服务器端3.2UDP Client客户端 4.根据UDP客户端服务端做的设计4.1字典热加载4.2shell命令行4.3聊天室 5.windows客…

WSL下Ubuntu+RTX4090安装CUDA+cuDnn+Pytorch

安装驱动 首先需要明确的是&#xff0c;在WSL下安装Ubuntu&#xff0c;如果要使用主机的GPU卡&#xff0c;只需要在主机Windows上安装驱动&#xff0c;Linux中不需要安装驱动&#xff0c;可以在Linux中使用nvidia-smi命令查看驱动版本。 安装CUDA 避坑注意事项&#xff1a;如…

网络原理(6)——IP协议

目录 一、网段划分 现在的网络划分&#xff1a; 1、一般情况下的家庭网络环境 2、IP地址 3、子网掩码 4、网关 以前的网络划分&#xff1a; 二、特殊IP 1、环回 IP 2、主机号为全 0 的IP 3、广播地址IP 三、路由选择&#xff08;路线规划&#xff09; 一、网段划分…

毕业论文降重(gpt+完美降重指令),sci论文降重gpt指令——超级好用,重复率低于4%

1. 降重方法&#xff1a;gpt降重指令 2. gpt网站 https://yiyan.baidu.com/ https://chat.openai.com/ 3. 降重指令——非常好用&#xff01;&#xff01;sci论文&#xff0c;本硕大论文都可使用&#xff01; 请帮我把下面句子重新组织&#xff0c;通过调整句子逻辑&#xff0…