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 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 wasm_ffi 深入鸿蒙端侧硬核 WebAssembly 虚拟机沙盒穿透适配全景:通过异步极速 FFI 中继管道打通底层高算力异构服务并全面实现无损语言壁垒交互 前言 在 OpenHarmony 应用向高性能计算领域扩展的过程中,如何优雅地接入已有的 C/C++ 算法库(如加密引擎、重型图像处理、数学模拟)而又不失跨平台的便捷性?传统的 NAPI 虽然稳健,但在 Flutter 生态中,直接利用 WebAssembly (WASM) 配合 FFI(External Function Interface)的语义可以在一定程度上实现代码的高度复用。wasm_ffi 库为 Flutter 开发者提供了一套在 Dart 环境下调用 WASM

By Ne0inhk
三种适用于Web版IM(即时通讯)聊天信息的加密算法实现方案

三种适用于Web版IM(即时通讯)聊天信息的加密算法实现方案

文章目录 * **第一部分:引言与核心密码学概念** * **1.1 为什么IM需要端到端加密(E2EE)?** * **1.2 核心密码学概念与工具** * **第二部分:方案一:静态非对称加密(基础方案)** * **2.1 方案概述与流程** * **2.2 前端Vue实现(使用node-forge)** * **1. 安装依赖** * **2. 核心工具类 `crypto.js`** * **3. Vue组件中使用** * **2.3 后端Java实现(Spring Boot)** * **1. 实体类** * **2. Controller层** * **3. WebSocket配置** * **2.4 密钥管理、注册与登录集成** * **1. 用户注册/登录时生成密钥** * **2. 密钥设置页面** * **2.

By Ne0inhk
前端代码生成的大洗牌:当 GLM 4.7 与 MiniMax 挑战 Claude Opus,谁才是性价比之王?

前端代码生成的大洗牌:当 GLM 4.7 与 MiniMax 挑战 Claude Opus,谁才是性价比之王?

在 AI 辅助编程领域,长期以来似乎存在一条不成文的铁律:如果你想要最好的结果,就必须为最昂贵的模型买单(通常是 Anthropic 或 OpenAI 的旗舰模型)。然而,随着国产大模型如 GLM 4.7 和 MiniMax M2.1 的迭代,这一格局正在发生剧烈震荡。 最近,一场针对Claude Opus 4.5、Gemini 3 Pro、GLM 4.7 和 MiniMax M2.1 的前端 UI生成横向测评,打破了许多人的固有认知。在这场包含落地页、仪表盘、移动端应用等五个真实场景的较量中,不仅出现了令人咋舌的“滑铁卢”,更诞生了性价比极高的“新王”。 本文将深入拆解这场测试的细节,透过代码生成的表象,探讨大模型在工程化落地中的真实效能与成本逻辑。

By Ne0inhk
【Java Web学习 | 第14篇】JavaScript(8) -正则表达式

【Java Web学习 | 第14篇】JavaScript(8) -正则表达式

🌈个人主页: Hygge_Code🔥热门专栏:从0开始学习Java | Linux学习| 计算机网络💫个人格言: “既然选择了远方,便不顾风雨兼程” 文章目录 * JavaScript 正则表达式详解 * 什么是正则表达式🤔 * JavaScript 正则表达式的定义与使用🥝 * 1. 字面量语法 * 2. 常用匹配方法 * test() 方法🍋‍🟩 * exec() 方法🍋‍🟩 * 正则表达式的核心组成部分🐦‍🔥 * 1. 元字符 * 边界符 * 量词 * 字符类 * 2. 修饰符 * 简单示例🍂 JavaScript 正则表达式详解 正则表达式是处理字符串的强大工具,在 JavaScript 中被广泛应用于表单验证、文本处理和数据提取等场景。本文将从正则表达式的基本概念出发,详细介绍其语法规则和实际应用方法。 什么是正则表达式🤔 正则表达式是用于匹配字符串中字符组合的模式,在 JavaScript

By Ne0inhk