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

强化基础-Java-泛型基础

什么是泛型?

泛型其实就参数化类型,也就是说这个类型类似一个变量是可变的。

为什么会有泛型?

在没有泛型之前,java中是通过Object来实现泛型的功能。但是这样做有下面两个缺陷:
1 获取值的时候必须进行强转
2 没有错误检查

Object data[] = new Object[]{"one", "two", 1, new Object()};
String str = (String) data[0];

一般来说我们会把相同类型的数据放到一起,但是有没有发现如果使用object我们可以放入任意类型的数据,编译器也不会报错,这样在使用的时候就增加了类型转换异常的概率。
那么使用泛型呢?

List<String> strList = new ArrayList<>();
strList.add("one");
// 这句代码编译器就会提醒你不能这样使用
strList.add(1);

非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。

泛型擦除

泛型信息只存在于代码编译阶段,但是在java的运行期(已经生成字节码文件后)与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

// 这个例子 最后输出的结果为 true class的结果为:java.util.ArrayList
public class Pair<T> {public static void main(String[] args) {List<Integer> integerList = new ArrayList<>();List<String> stringList = new ArrayList<>();System.out.println(Objects.equals(integerList, stringList));System.out.println(integerList.getClass());System.out.println(stringList.getClass());}
}

再看下面这个例子:

public class Pair<T> {private T data;public static void main(String[] args) {Pair<String> stringPair = new Pair<>();System.out.println(stringPair.getClass());Field[] declaredFields = stringPair.getClass().getDeclaredFields();Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));System.out.println("========================");Pair<Integer> integerPair = new Pair<>();System.out.println(integerPair.getClass());Field[] fields = integerPair.getClass().getDeclaredFields();Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));}
}

从运行结果我们可以证明在运行时类型已经被擦除为Object类型
在这里插入图片描述

在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如则会被转译成普通的Object 类型,如果指定了上限,如则类型参数就被替换成类型上限。
1 没有限定泛型的界限

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

擦除后,没有限定泛型的界限所以是Object类型:

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

2 限定了泛型的界限

public class Pair<T extends Comparable> {private T data;public static void main(String[] args) {Pair<String> stringPair = new Pair<>();System.out.println(stringPair.getClass());Field[] declaredFields = stringPair.getClass().getDeclaredFields();Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));System.out.println("========================");Pair<Integer> integerPair = new Pair<>();System.out.println(integerPair.getClass());Field[] fields = integerPair.getClass().getDeclaredFields();Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));}
}

在这里插入图片描述

这就证明在擦除后:

public class Pair<T extends Comparable & Serializable> {private Comparable first;private Comparable second;
}

如果交换泛型的顺序: Pair<T extends Serializable & Comparable > 那么擦除以后的类型为Serializable,这个时候编译器会插入强制类型转换(也就是说我们获取Comparable 类型时候会强制转换),为了提高效率一般将标记接口往末尾放。

public class Pair<T extends Serializable & Comparable> {private T data;public static void main(String[] args) {Pair<String> stringPair = new Pair<>();System.out.println(stringPair.getClass());Field[] declaredFields = stringPair.getClass().getDeclaredFields();Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));System.out.println("========================");Pair<Integer> integerPair = new Pair<>();System.out.println(integerPair.getClass());Field[] fields = integerPair.getClass().getDeclaredFields();Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));}
}

在这里插入图片描述

所谓的插入强制类型转换,就是编译器在编译泛型表达式的时候会转化为两条指令:

  • 对原始方法的调用得到Object
  • 将返回的Object类型强制转换为具体的类型。

3 泛型方法的擦除
首先我们要区分一下泛型方法,泛型方法。只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。如果单纯的在方法中使用了泛型它不是泛型方法。
泛型方法:

public <E> E convert(T t) { 
}

非泛型方法:

public T getT(T t) {
}
public static <T extends Comparable> T min(T[] data) {return null;
}

泛型方法擦除后:

public static Comparable min(Comparable [] data) {return null;
}

需要注意的是泛型方法的多态是通过桥方法实现的

public class Pair<T> {private T time;public void setTime(T time) {this.time = time;}
}
// 被擦除以后
public void setTime(Object time) {
}

如果这个时候子类继承Pair,并指定了类型:

class DateInterVal extends Pair<LocalDate> {@Overridepublic void setTime(LocalDate time) {}
}

这个时候如果调用Pair的setTime方法,由于多态其实底层是这样来实现的:

setTime(setTime((LocalDate) time));

总结:1 虚拟机中没有类型,只有普通的类和方法 2 所有的类型参数都会替换为他们的限定类型 3 合成桥方法来保持多态 4为保证类型安全,必要时会插入强制类型转换

泛型方法

1 在类中的泛型方法
首先我们来区分几个定义方式,看注释部分。

public class Pair<T extends Comparable & Serializable> {private T data;// 编译无法通过 因为这个方法是静态方法,所以我们不能使用T类型,但是我们可以使用E类型,因为E类型是申明的public static <E> E convert2E(T t) {return null;}// 在非静态方法的情况下 可以使用上面的类中定义的泛型Tpublic <E> E convert2E(T t) {return null;}// 注意这里我们在静态方方法申明了一个T类型,这个T和类上的T类型是没有关联的,是一个全新的类型// 这个T可以和类的T是同一个类型,也可以不是同一个类型public static  <T> T accept(T t) {return null;}
}

2 泛型方法中的可变参数

public class Pair<T> {static class Dot {private int x;private int y;public Dot(int x, int y) {this.x = x;this.y = y;}@Overridepublic String toString() {return "Dot{" +"x=" + x +", y=" + y +'}';}}// 泛型方法可变参数private static <T> void print(T ...es) {for (T t : es) {System.out.println(t  + "");}}public static void main(String[] args) {Dot dot = new Dot(1, 1);print(15000, "15000", dot);}
}

2 静态方法与泛型
一个基本原则,如果要在静态方法使用泛型的话,这个方法必须为泛型方法。

// 也就是说必须申明
public static  <T> T accept(T t) {return null;
}

泛型缺陷

1 不能使用基本类型实例化类型参数
也就是说没有

Pair<int> Pair<double> 类型

因为泛型在擦除以后为object类型,但是原生类型不能直接赋值为object,而是要使用包装类。
2 不能实例化类型参数
本质也是因为类型擦除导致的

String string = new T();
// 类型擦除以后,很显然是存在问题的
String string = new Object();

但是我们可以通过反射来创建一个实例:

 Pair<String> pair = Pair.class.newInstance();

3 运行时类型查询只适用于原始类型
下面这三条语句都是编译会报错的,因为虚拟中的对象总是一个特定的非泛型类型,所以类查询只能查询原始类型。

pair instanceof String;
pair instanceof Pair<String>;
pair instanceof Pair<T>;
// 这条语句是可以的 原始类型的查询
pair instanceof Pair

并且下面的语句也会返回true:

Pair<String> ps = new Pair<>();
Pair<Double> pd = new Pair<>();
System.out.println(ps.getClass() == pd.getClass());

4 不能创建参数化类型的数组
类型擦除以后变为Pair[] pars = new Pair[10]; 然后我们可以赋予pairs[0] = new Pair(); 没有编译错误,但存在运行时错误。

// 可以申明
Pair<String> [] pairs;
// 不可以实例化 也是一样的如果把String擦除 为Object 可能会导致运行时异常 不安全
Pair[] pairs = new Pair<String>[10];
// 如果是通配类型的 则可以 但是这样的话不是很安全因为里面可以存 Pair<String> 也可以存 Pair<Double> 
// 在使用的时候可能类型转换异常
Pair[] pairs = new Pair<?>[10];

5 Varargs 警告

public static <T> void addAll(Collection<T> coll, T... ts){// 这里其实创建了数组就违背了不能创建数组for(T t : ts) {coll.add(t);}
}
public static void main(String[] args) {Collection<Pair<String>> table = new ArrayList<>();Pair<String> pair1 = new Pair<>();Pair<String> pair2 = new Pair<>();addAll(table, pair1, pair2);
}
static class Pair<T> {}

6 不能实例化类型变量
T[] array = new T[10]
类型擦除后上述定义变为Object[] array = new Object[10]; 这样一来我们可以将任何类型赋予array[0], 比如array[0] = “1”; 编译器不会报错,但运行时在使用的时候就有可能会出错了。

// 编译不会通过
T t = new T();
// 编译不会通过
T[] array = new T[10]

这里也可以通过反射来进行:

public T[] demo() {T data[] = (T[]) Array.newInstance(Pair.class, 2);return data;
}

7 泛型类的静态上下文中类型变量无效
也就是静态不能和泛型一起使用,如果一定要一起使用的话,必须申明。

// 通不过
public static T t;
// 如果一定要使用则需要申明
public static <T> void addAll(Collection<T> coll, T... ts){for(T t : ts) {coll.add(t);}}

8 不能抛出或捕获泛型类的实例

public static <T extends Throwable> void doWork() {try {// 会产生编译错误} catch (T e) {}
}

泛型中的继承

继承泛型类时,必须对父类中的类型参数进行初始化
1 使用泛型初始化父类的泛型

public class Bar<T> extends Foo<T> {}

2 使用具体的类型

public class Bar extends Foo<String> {}

特别注意:

// 这里的继承关系是 Integer 和 Double 继承
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); 
box.add(new Double(10.1));
// Box<Integer> Box<Number> 他们并不是继承关系 这一定要注意 

原文链接
以 Collections 类为例,ArrayList 实现 List ,List 继承 Collection 。 所以 ArrayList 是 List 的一个子类型,它是 Collection 的一个子类型。 只要不改变类型参数,子类型关系在类型之间保留。
在这里插入图片描述

泛型中的限定

在通配符类型中,允许类型参数发生变化。
1 子类限定

// 类上
class Son<T extends Foo> {
}
// 方法上
public <T> T demo2(Bar<? extends Number> data) {
}
// 方法的申明
public <T extends Integer> T demo3(Bar<? super Integer> data) {
}

2 超类限定

// 方法上
public <T> T demo1(Bar<? super Integer> data) {
}

相关文章:

强化基础-Java-泛型基础

什么是泛型&#xff1f; 泛型其实就参数化类型&#xff0c;也就是说这个类型类似一个变量是可变的。 为什么会有泛型&#xff1f; 在没有泛型之前&#xff0c;java中是通过Object来实现泛型的功能。但是这样做有下面两个缺陷&#xff1a; 1 获取值的时候必须进行强转 2 没有…...

c++20协程详解(一)

前言 本文是c协程第一篇&#xff0c;主要是让大家对协程的定义&#xff0c;以及协程的执行流有一个初步的认识&#xff0c;后面还会出两篇对协程的高阶封装。 在开始正式开始协程之前&#xff0c;请务必记住&#xff0c;c协程 不是挂起当前协程&#xff0c;转而执行其他协程&a…...

go: go.mod file not found in current directory or any parent directory.如何解决?

这个错误表明你正在执行 go get 命令&#xff0c;但是当前目录或任何父目录中都找不到 go.mod 文件。这可能是因为你的项目还没有使用 Go Modules 进行管理。 要解决这个问题&#xff0c;有几种方法&#xff1a; go mod init <module-name> 其中 <module-name>…...

Go-Gin全局错误处理中间件

为了防止报错引起Gin服务挂掉以及错误日志记录&#xff0c;我们使用全局错误中间件进行管理。 package middlewareimport ("ToDoList/global""github.com/gin-gonic/gin""go.uber.org/zap""net""net/http""net/http/h…...

图神经网络实战(6)——使用PyTorch构建图神经网络

图神经网络实战&#xff08;6&#xff09;——使用PyTorch构建图神经网络 0. 前言1. 传统机器学习与人工智能2. 人工神经网络基础2.1 人工神经网络组成2.2 神经网络的训练 3. 图神经网络4. 使用香草神经网络执行节点分类4.1 数据集构建4.2 模型构建4.3 模型训练 5. 实现香草图神…...

【Flutter】windows环境配置

windows 11 环境 官方教程 配置了flutter 环境变量在系统的path里 bin 路径。 死活没反应 关闭了git关闭了dart.exe关闭了vs还是不行卸载重新来 新版git flutter doctor 还需要android 环境...

毕马威:《智慧之眼:开启汽车感知新时代》

在全球科技飞速发展和产业革新的大潮中&#xff0c;汽车产业正在以前所未有的速度向网联化、智能化的方向转型。汽车传感器作为智能联网汽车发展的关键环节之一&#xff0c;扮演着举足轻重的角色。 毕马威一直关注汽车产业的变化与发展&#xff0c;为了更好地为汽车行业赋能&a…...

每日三个JAVA经典面试题(三十四)

1.Mybatis的一级、二级缓存 MyBatis提供了两种缓存机制来提高查询效率&#xff1a;一级缓存和二级缓存。 一级缓存&#xff08;Session级别&#xff09; 作用范围&#xff1a;一级缓存是基于SqlSession的。这意味着&#xff0c;如果你在同一个SqlSession中执行两次相同的查询…...

C# 学习第五弹——语句

一、if语句 —简单if语句 —if else 语句 —if else if else 语句 1、简单if语句 if&#xff08;表达式&#xff09;{语句} &#xff08;1&#xff09;表达式必须使用圆括号括起来&#xff1b; &#xff08;2&#xff09;表达式&#xff1a;关系表达式或逻辑表达…...

什么是Java中的JVM(Java虚拟机)?它如何工作?

Java中的JVM&#xff0c;全称Java Virtual Machine&#xff08;Java虚拟机&#xff09;&#xff0c;是Java程序的运行环境&#xff0c;也是Java语言的核心和基础。它是一个虚拟的计算机&#xff0c;具有完善的硬体架构&#xff0c;如处理器、堆栈、寄存器等&#xff0c;以及相应…...

OmniGraffle Pro for mac 出色的图形设计软件

OmniGraffle Pro是一款非常出色的图形设计软件&#xff0c;它主要适用于Mac和iPad平台&#xff0c;可以用来轻松绘制各种精美的图表、示意图和界面设计。 软件下载&#xff1a;OmniGraffle Pro for mac中文注册激活版 以下是OmniGraffle Pro的一些主要特点和功能&#xff1a; 界…...

代码随想录阅读笔记-二叉树【合并二叉树】

题目 给定两个二叉树&#xff0c;想象当你将它们中的一个覆盖到另一个上时&#xff0c;两个二叉树的一些节点便会重叠。 你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠&#xff0c;那么将他们的值相加作为节点合并后的新值&#xff0c;否则不为 NULL 的节…...

Day35:学习尚上优选项目

学习计划&#xff1a;完成尚硅谷的尚上优选项目 学习进度&#xff1a;尚上优选项目 知识点&#xff1a; 四、 搭建平台管理端前端环境 权限管理模块-用户管理 开发为用户分配角色接口用户管理前端测试 权限管理模块-菜单管理 菜单管理需求菜单表设计开发菜单管理CRUD接口开…...

c模板编程c/c++20240401

c模板编程 #include<iostream> //#include<string> //#include<algorithm> template <typename T> T max(T a, T b) { return (a > b) ? a : b; } int main() { int i max(1, 2); // 返回 2 float f max(3.14f, 2.72f); // 返回 3…...

【TI毫米波雷达】IWR6843AOP的官方文件资源名称BUG,选择xwr68xx还是xwr64xx,及需要注意的问题

【TI毫米波雷达】IWR6843AOP的官方文件资源名称BUG&#xff0c;选择xwr68xx还是xwr64xx&#xff0c;及需要注意的问题 文章目录 demo工程out_of_box文件调试bin文件名称需要注意的问题附录&#xff1a;结构框架雷达基本原理叙述雷达天线排列位置芯片框架Demo工程功能CCS工程导…...

连接Redis不支持集群错误,ERR This instance has cluster support disabled,解决方案

1. 问题背景 调整redis的配置后&#xff0c;启动程序时&#xff0c; 会报如下错误&#xff1a; [redis://172.16.0.8xxx]: ERR This instance has cluster support disabledSuppressed: io.lettuce.core.RedisCommandExecutionException: ERR This instance has cluster supp…...

什么是json?json可以存放哪几种数据类型

JSON指的是JavaScript对象表示法(avaScript Object Notation)&#xff0c;是轻量级的文本数据交换格式&#xff0c;独立于语言: JSON使用JavaScript语法来描述数据对象&#xff0c;但是JSON仍然独立于语言和平台&#xff0c;JSON解析器和JSON库支持许多不同的编程语言&#xff…...

网络编程套接字应用分享【Linux C/C++ 】【UDP应用 | TCP应用 | TCP线程池小项目】

目录 前提知识 1. 理解源ip&#xff0c;目的ip和Macip 2. 端口号 3. 初识TCP&#xff0c;UDP协议 4. 网络字节序 5. socket 编程 sockaddr类型 一&#xff0c;基于udp协议编程 1. socket——创建套接字 2. bind——将套接字强绑定 3. recvfrom——接受数据 4. s…...

有关数据开发项目中使用HIVE由于无法update和delete的场景下,如何解决数据增量的思路

解决数据增量问题的思路在Hive中 在数据开发项目中&#xff0c;使用Hive进行数据处理时&#xff0c;由于Hive不支持update和delete语句&#xff0c;处理数据增量可能会变得有些棘手。然而&#xff0c;有几种策略和技术可以帮助我们解决这个问题&#xff0c;并确保数据增量的高…...

两数之和-考察哈希表的运用

题目 给定一个整数数组 n u m s nums nums和一个整数目标值 t a r g e t target target&#xff0c;请你在该数组中找出和为目标值 t a r g e t target target的那 两个整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同…...

DDrawCompat完整教程:Windows 11上经典游戏DirectDraw兼容性修复终极指南

DDrawCompat完整教程&#xff1a;Windows 11上经典游戏DirectDraw兼容性修复终极指南 【免费下载链接】DDrawCompat DirectDraw and Direct3D 1-7 compatibility, performance and visual enhancements for Windows Vista, 7, 8, 10 and 11 项目地址: https://gitcode.com/gh…...

基于LLM智能体的自动化研究工具autoresearch:从部署到实战调优

1. 项目概述&#xff1a;当AI成为你的全职研究助理如果你也曾在深夜面对海量文献、数据报告和网络信息感到无从下手&#xff0c;或者为一个研究课题的初步资料搜集耗费数天时间却收效甚微&#xff0c;那么darks0l/autoresearch这个项目可能会让你眼前一亮。简单来说&#xff0c…...

如何在老旧电视上实现流畅的1080P直播?MyTV-Android原生应用深度解析

如何在老旧电视上实现流畅的1080P直播&#xff1f;MyTV-Android原生应用深度解析 【免费下载链接】mytv-android 使用Android原生开发的视频播放软件 项目地址: https://gitcode.com/gh_mirrors/my/mytv-android 你是否曾为家中老旧智能电视的卡顿直播体验而烦恼&#x…...

开发者技能日志工具:用CLI与SQLite构建个人技术成长追踪系统

1. 项目概述&#xff1a;一个技能日志记录器的诞生 最近在整理自己的技术栈和项目经验时&#xff0c;我遇到了一个很多开发者都有的痛点&#xff1a;学了那么多东西&#xff0c;做了那么多项目&#xff0c;但真要写简历或者回顾成长路径时&#xff0c;记忆总是模糊的。今天学了…...

前端工程化:依赖管理最佳实践

前端工程化&#xff1a;依赖管理最佳实践 前言 依赖管理是前端工程化的基础&#xff01;如果你的项目依赖管理混乱&#xff0c;那你的项目就像一个堆满杂物的仓库&#xff0c;难以维护。今天我就来给大家讲讲前端依赖管理的最佳实践。 为什么需要依赖管理 版本控制&#xff1a;…...

告别炼丹玄学:用EfficientNet-B0到B7的缩放系数,在PyTorch里精准匹配你的算力

告别炼丹玄学&#xff1a;用EfficientNet-B0到B7的缩放系数&#xff0c;在PyTorch里精准匹配你的算力 当你在个人GPU或边缘设备上部署深度学习模型时&#xff0c;是否经常遇到这样的困境&#xff1a;模型要么太大导致显存溢出&#xff0c;要么太小无法达到预期精度&#xff1f;…...

智能体网格(Agent Mesh)架构解析:构建大规模异构智能体协同网络

1. 项目概述与核心价值最近在开源社区里&#xff0c;一个名为sampleXbro/agentsmesh的项目引起了我的注意。乍一看这个标题&#xff0c;你可能会觉得它有些神秘&#xff0c;甚至有点“缝合怪”的味道——sampleX、bro、agents、mesh&#xff0c;这些词组合在一起&#xff0c;到…...

鸣潮自动化终极指南:5分钟解放双手,告别重复刷图

鸣潮自动化终极指南&#xff1a;5分钟解放双手&#xff0c;告别重复刷图 【免费下载链接】ok-wuthering-waves 鸣潮 后台自动战斗 自动刷声骸 一键日常 Automation for Wuthering Waves 项目地址: https://gitcode.com/GitHub_Trending/ok/ok-wuthering-waves ok-ww 是一…...

开源代理解决 DeepSeek V4 与 Claude Code 的三个兼容性陷阱解决方案

在使用 Claude Code 的过程中&#xff0c;Anthropic 官方 API 的调用成本和网络问题一直是个痛点。DeepSeek V4 提供了兼容 Anthropic 格式的 API&#xff0c;价格优势明显&#xff0c;但实际对接时存在若干协议层面的差异&#xff0c;直接使用的话在进行 Agent spawn 工具调用…...

STM32时钟树配置避坑指南:从HSE到PLL,手把手教你调出72MHz系统时钟

STM32时钟树配置避坑指南&#xff1a;从HSE到PLL&#xff0c;手把手教你调出72MHz系统时钟 第一次接触STM32的时钟系统时&#xff0c;我盯着数据手册上那张复杂的时钟树图看了整整一个下午&#xff0c;脑子里全是问号。为什么需要这么多时钟源&#xff1f;PLL到底是怎么工作的&…...