Java之泛型

Java之泛型

目录

泛型类

语法

使用

泛型上界

定义

使用

类型擦除

通配符

通配符上界

 通配符下界

泛型方法

语法

使用 

泛型的限制


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


class ClassName<T1, T2, ..., Tn> {
}



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


class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
        // 可以只使用部分类型参数
}
使用
泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象 
class MyArray<T> { public Object[] array = new Object[10]; public void set(int pos,T val) { array[pos] = val; } public T get(int pos) { return (T)array[pos]; } public Object[] getArray() { return array; } } public class Test { public static void main(String[] args) { MyArray<String> myArray = new MyArray<>(); } }
泛型上界

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

定义
class 泛型类名称<类型形参 extends 类型边界> {
        ...
使用
class MyArray<T extends Number> { public Object[] array = new Object[10]; public void set(int pos,T val) { array[pos] = val; } public T get(int pos) { return (T)array[pos]; } public Object[] getArray() { return array; } } public class Test { public static void main(String[] args) { // 编译错误,因为 String 不是 Number 的子类型 // MyArray<String> myArray1 = new MyArray<>(); // 正常,因为 Integer 是 Number 的子类型 MyArray<Integer> myArray = new MyArray<>(); } }
class Alg<T extends Comparable<T>> { public T findMax(T[] array) { T max = array[0]; for (int i = 1; i < array.length; i++) { if(array[i].compareTo(max) > 0) { max = array[i]; } } return max; } }

类型擦除

Java中的泛型擦除是Java泛型实现的一个关键特性,它允许Java在编译时检查类型安全,但在运行时忽略泛型信息。这意味着,尽管你可以在编译时指定泛型类型,但在运行时,JVM看到的只是原始类型,并不保留任何关于泛型参数的信息。

class MyArray<E> { // E 会被擦除为 Object } class MyArray<E extends Comparable<E>> { // E 被擦除为 Comprable }

 示例

class Alg<T extends Comparable<T>> { public T findMax(T[] array) { T max = array[0]; for (int i = 1; i < array.length; i++) { if(array[i].compareTo(max) > 0) { max = array[i]; } } return max; } }

针对上面的代码,编译结果如下:

编译器在类型擦除阶段做什么?

一、擦除泛型类型信息
移除类型参数:编译器会移除所有与泛型相关的类型参数信息。这意味着在编译后的字节码中,将不再包含任何泛型类型参数的信息。
替换为原始类型:泛型类型参数会被替换为其上界(如果指定了上界的话,比如T extends Comparable<T>中的T会被替换为Comparable<T>;如果没有指定上界,则默认为Object)。例如,List<String>和List<Integer>在编译后的字节码中都表现为List。
二、调整类型信息
方法参数、局部变量和字段的类型调整:除了泛型类型本身被擦除外,编译器还会相应地调整方法参数、局部变量和字段的类型,以确保它们在运行时的一致性。
三、插入类型转换
类型强制转换:由于类型信息在运行时不可用,编译器会在需要的地方插入必要的类型转换。例如,从List<String>中获取元素时,虽然运行时这个列表只是普通的List,但编译器会在幕后添加一个从Object到String的显式类型转换。
四、确保类型安全
编译时类型检查:在编译阶段,编译器会利用泛型类型信息进行类型检查,确保类型安全。例如,编译器会阻止向List<String>中添加整数或其他非字符串对象,或者尝试将List<Integer>赋值给List<Double>等不兼容的操作。
五、桥接方法的生成
向后兼容性:为了保持与未使用泛型的旧代码的兼容性,编译器可能会为泛型类生成桥接方法。这些桥接方法允许泛型类的方法与原生类型的方法共存,并且可以相互调用。
总的来说,编译器在类型擦除阶段的主要目标是确保泛型代码在运行时能够安全、正确地执行,同时尽量减少对现有代码的影响。通过移除泛型类型信息并插入必要的类型转换,编译器使得泛型代码能够与非泛型代码无缝对接,从而保证了Java平台的整体一致性和稳定性。 
通配符
?用于在泛型的使用,即为通配符。
class Message<T> { private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Test{ //可以传入任意类型的Message public static void fun(Message<?> temp){ System.out.println(temp.getMessage()); } public static void main(String[] args) { Message<String> message = new Message<>(); message.setMessage("你好!"); fun(message); Message<Integer> message2 = new Message<>(); message2.setMessage(199); fun(message2); } }
通配符上界
<? extends 上界> 
上界通配符用于指定一个类型的上界,即泛型参数必须是该类型或其子类。这种通配符主要用于读取操作,因为它允许你安全地读取数据,但不允许你写入数据(除了null,因为null可以被视为任何类型的实例)。

特点:

add()方法:不能添加任何元素(除了null),因为编译器无法确定具体的类型,添加其他元素可能会导致类型安全问题。
get()方法:可以安全地读取元素,并且返回的类型是T或T的子类,但通常需要用T或T的父类(如Object)来接收,因为编译器无法确定具体的子类类型。
class Food { } class Fruit extends Food { } class Apple extends Fruit { } class Banana extends Fruit { } class Message<T> { private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Test1 { public static void funExtends(Message<? extends Fruit> temp){ System.out.println(temp.getMessage()); //通配符的上界来说 是不可以进行修改元素的 /* temp.setMessage(new Banana()); temp.setMessage(new Apple());*/ //向上转型 Fruit fruit = temp.getMessage(); } public static void main(String[] args) { Message<Apple> message1 = new Message<>(); message1.setMessage(new Apple()); Message<Banana> message2 = new Message<>(); message2.setMessage(new Banana()); funExtends(message1); funExtends(message2); } }
 通配符下界
<? super 下界> 
下界通配符用于指定一个类型的下界,即泛型参数必须是该类型或其父类。这种通配符主要用于写入操作,因为它允许你安全地添加T或T的子类类型的元素,但在读取时只能确保是Object类型。 

特点:

add()方法:可以添加T或T的子类类型的元素,因为所有这些类型都是下界类型的子类或相同类型。
get()方法:读取元素时,由于类型的不确定性,通常只能用Object类型来接收。如果需要具体的类型,则需要进行类型转换。 
class Food { } class Fruit extends Food { } class Apple extends Fruit { } class Banana extends Fruit { } class Plate<T> { private T plate ; public T getPlate() { return plate; } public void setPlate(T plate) { this.plate = plate; } } public class Test1 { public static void funSuper(Plate<? super Fruit> temp){ // 此时可以修改!!添加的是Fruit 或者Fruit的子类 temp.setPlate(new Apple());//这个是Fruit的子类 向上转型 temp.setPlate(new Banana());//这个是Fruit的本身 // Fruit fruit = temp.getPlate(); //不能接收,这里无法确定是哪个父类 System.out.println(temp.getPlate());//只能直接输出 } public static void main(String[] args) { Plate<Fruit> message1 = new Plate<>(); Plate<Food> message2 = new Plate<>(); funSuper(message1); funSuper(message2); } }
泛型方法
语法
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
使用 
class Alg2 { //泛型方法 public static <T extends Comparable<T>> T findMax(T[] array) { T max = array[0]; for (int i = 1; i < array.length; i++) { if(array[i].compareTo(max) > 0) { max = array[i]; } } return max; } } public class Test2 { public static void main1(String[] args) { Integer[] array = {1,13,51,71,19}; Integer ret = Alg2.<Integer>findMax(array); System.out.println(ret); } }
泛型的限制
1. 不能使用基本数据类型作为泛型类型参数
Java中的泛型只能使用引用数据类型作为类型参数,不能使用基本数据类型(如int、double等)。如果需要使用基本数据类型的泛型,通常需要使用它们对应的包装类(如Integer、Double等)。

2. 不能创建泛型类型的数组
由于类型擦除的原因,Java不允许直接创建泛型类型的数组。例如,List<String>[] listArray = new ArrayList<String>[10]; 是不允许的。如果需要类似的功能,可以考虑使用ArrayList<List<String>>等集合类来实现。

3. 不能实例化泛型类型的对象(除了使用new操作符的匿名内部类)
Java不允许直接实例化泛型类型的对象,如List<String> list = new List<String>(); 是不允许的。应该使用具体的类来实例化泛型,如List<String> list = new ArrayList<>();。

4. 运行时类型查询的限制
由于类型擦除,在运行时无法直接查询泛型参数的具体类型。例如,instanceof 运算符和getClass()方法不能用于检查泛型类型参数的具体类型。这意味着,在运行时,所有的泛型类型都会被当作它们的原始类型(即擦除后的类型)来处理。

5. 泛型类型参数不具有继承关系
即使两个类之间存在继承关系,它们的泛型类型参数之间也不具有继承关系。例如,List<A>并不是List<B>的子类,即使A是B的子类。这要求开发者在编写泛型代码时特别注意类型安全。

6. 泛型上界和下界的限制
上界:通过extends关键字,可以限制泛型类型参数必须是某个类型的子类或实现类。这有助于在编译时检查类型兼容性,提高代码的安全性。
下界:通过super关键字,可以限制泛型类型参数必须是某个类型的父类型。这允许在泛型方法或类中灵活处理不同但兼容的类型。
7. 泛型通配符的限制
泛型通配符(如?、<? extends T>、<? super T>)用于表示未知类型或受限类型。然而,它们也带来了一些限制,如在使用上界通配符时不能添加除null以外的元素,而在使用下界通配符时读取的元素类型只能为Object等。

8. 反射和泛型
Java的反射机制可以在运行时访问类的信息,但由于类型擦除,反射无法直接获取泛型类型参数的具体信息。这要求开发者在使用反射和泛型时需要特别注意类型安全和类型转换的问题。

Read more

Flutter 组件 intl_messages 的适配 鸿蒙Harmony 实战 - 驾驭百万级多语言消息系统、实现鸿蒙端异步国际化与文本语义插值治理方案

Flutter 组件 intl_messages 的适配 鸿蒙Harmony 实战 - 驾驭百万级多语言消息系统、实现鸿蒙端异步国际化与文本语义插值治理方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 intl_messages 的适配 鸿蒙Harmony 实战 - 驾驭百万级多语言消息系统、实现鸿蒙端异步国际化与文本语义插值治理方案 前言 在鸿蒙(OpenHarmony)生态的全球化政务协同门户、多语言即时通讯系统以及需要支持上百个语种实时热更新的各类专业应用中,“文本翻译的加载效率与动态解析能力”是决定用户交互质感的最后一道“感知防线”。面对包含上万条多语言 Key、具备复杂复数规则与动态变量插值的 0307 批次资产资产。如果依然采用基于“同步 JSON 预拉取”或“静态代码生成(Code Generation)”的传统国际化模式。不仅会导致在应用启动初期产生严重的内存峰值与 IO 阻塞,更会因为无法实现在不更新 HAP 的前提下修改单条翻译,引发严重的运营维护滞后风险。 我们需要一种“异步解耦、语义对齐”的国际化艺术。 intl_messages

By Ne0inhk
鸿蒙电商购物全栈项目——数据安全与合规

鸿蒙电商购物全栈项目——数据安全与合规

《鸿蒙APP开发从入门到精通》第39篇:鸿蒙电商购物全栈项目——数据安全与合规 🛡️📝📊 内容承接与核心价值 这是《鸿蒙APP开发从入门到精通》的第39篇——数据安全与合规篇,100%承接第38篇的数据分析与商业洞察场景,并基于电商购物场景的数据安全与合规要求,设计并实现鸿蒙电商购物全栈项目的数据安全与合规功能。 学习目标: * 掌握鸿蒙应用数据安全的核心设计与实现; * 实现数据加密、数据脱敏、数据备份; * 理解数据合规的战略设计与实现; * 实现GDPR合规、用户数据权益保护、数据审计; * 掌握数据安全与合规的协同管理策略; * 优化电商购物项目的数据安全与合规水平。 学习重点: * 鸿蒙应用数据安全的全流程设计原则; * 数据合规的战略规划与技术落地; * 数据安全与合规的协同管理策略。 一、 数据安全基础 🛡️ 1.1 数据安全定义 数据安全是指保护电商购物项目中的数据安全,主要包括以下方面: * 数据加密:加密数据; * 数据脱敏:脱敏数据; * 数据备份:备份数据。 1.2 数据安全架构 数据安全采用分层架构,

By Ne0inhk
2025年最新最全Linux 系统安装Minio详细教程

2025年最新最全Linux 系统安装Minio详细教程

1.MinIO简介 MinIO 是一款高性能、分布式对象存储系统,专为云原生和容器化环境设计。它采用 Apache License 2.0 开源协议,兼容 Amazon S3 API,支持海量数据的存储与管理。 核心特点 高性能架构 MinIO 使用纠删码技术实现数据冗余,读写速度可达每秒数百 GB,适合高吞吐场景。 兼容 S3 协议 完全兼容 Amazon S3 API,现有基于 S3 的应用无需修改即可迁移到 MinIO。 轻量级部署 单二进制文件即可运行,最低配置仅需 512MB 内存,支持 Kubernetes 和 Docker 快速部署。 多云支持 提供混合云解决方案,能在公有云、私有云和边缘计算环境中无缝运行。 典型应用场景

By Ne0inhk

HarmonyOS 文件预览服务避坑指南:从入门到真香

一、背景引入:这玩意儿是干啥的? 咱今天聊的这个 Preview Kit,中文名儿叫"文件预览服务"。听名字就知道,这玩意儿就是帮你预览文件的。 你可能会问:“预览文件?我自己写个组件不就完了吗?” 嘿,您要真这么想,那我得给您点个赞——有这股劲儿,当年我写代码也是这么想的。但踩了几个坑之后,我就服了。 为啥要用 Preview Kit? 咱说个实际场景: 你在应用里做了个文件管理器,用户点了个 Word 文档,你得让人家看到内容吧?这时候你有几个选择: 1. 自己写解析器:docx、xlsx、pptx、pdf… 您慢慢写,写完了叫我一声 2. 接第三方 SDK:WPS、OnlyOffice,选一个,然后掏钱 3. 用 Preview

By Ne0inhk