本文简介:从零理解 Java 泛型的核心概念,掌握泛型类的定义、使用与类型上界约束,写出类型安全、通用优雅的 Java 代码。
目录
- 一、什么是泛型
- 二、为什么需要泛型(引出泛型)
- 三、泛型类的语法格式
- 四、泛型类的使用
- [1. 基本语法](#1. 基本语法)
- [2. 代码示例](#2. 代码示例)
- [3. 类型推导(菱形操作符)](#3. 类型推导(菱形操作符))
- 五、泛型类的上界
- [1. 语法规则](#1. 语法规则)
- [2. 代码示例](#2. 代码示例)
- 六、总结
一、什么是泛型
泛型的本质就是「参数化类型」 。
简单来说:把数据类型当作参数传递,让类、接口、方法可以通用处理多种数据类型,同时保留类型安全------ 编译期就检查类型,避免运行时才出类型转换错误。
通俗理解
打个比方:
- 普通代码像专用杯子------ 只能装水,装咖啡会串味
- 泛型像通用杯子------ 你指定装咖啡、茶还是可乐,它就只装对应东西,绝不会装错
一句话总结:泛型就是适用于许多许多类型的代码。从代码上讲,就是对类型实现了参数化。
二、为什么需要泛型(引出泛型)
传统做法的问题
我们来实现一个简单的 "数组容器" 类:内部维护一个数组,可以存放任意类型的数据,也可以根据下标取出元素。
class MyArray {
private Object[] array;
public MyArray(int capacity) {
array = new Object[capacity];
}
public void set(int index, Object value) {
array[index] = value;
}
public Object get(int index) {
return array[index];
}
}
这样写的问题
用 Object 数组确实可以存任何类型,但带来了两个麻烦:
-
类型不安全:什么都能存,编译器不检查,一不小心就存错类型
-
使用麻烦 :取出来是
Object类型,每次都要强制类型转换MyArray list = new MyArray(10);
list.set(0, "hello");
list.set(1, 123); // 居然也能存进去?
String s = (String) list.get(0); // 要强转
Integer i = (Integer) list.get(1); // 也要强转
泛型的解决方案
泛型的核心目的就是:指定当前容器要持有什么类型的对象,让编译器去做类型检查。
把类型作为参数传递,需要什么类型,就传入什么类型。这样既通用,又安全。
三、泛型类的语法格式
基本语法
// 形式一:简单泛型类
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
// 示例
class ClassName<T1, T2, ..., Tn> {
// ...
}
// 形式二:带继承关系的泛型类
class 泛型类名称<类型形参列表> extends 继承类 {
// 这里可以使用类型参数
}
// 示例:父类本身也是泛型类
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}
用泛型改写上面的容器类
class MyArray<E> {
private Object[] array;
public MyArray(int capacity) {
array = new Object[capacity];
}
public void set(int index, E value) {
array[index] = value;
}
public E get(int index) {
return (E) array[index];
}
}
📝 代码解释:
- 类名后面的
<T>(或<E>)是类型占位符,表示当前类是一个泛型类E不是具体类型,而是一个 "占位"------ 用的时候才指定是什么类型
📌 常见的类型参数命名约定
表格
| 符号 | 含义 | 适用场景 |
|---|---|---|
E |
Element | 集合中的元素 |
K |
Key | 键值对中的键 |
V |
Value | 键值对中的值 |
N |
Number | 数值类型 |
T |
Type | 泛型通用类型 |
S, U, V... |
第二、三、四个类型 | 多个类型参数时依次使用 |
四、泛型类的使用
1. 基本语法
// 第一步:定义一个泛型类的引用
泛型类<类型实参> 变量名;
// 第二步:实例化一个泛型类对象
new 泛型类<类型实参>(构造方法实参);
两个关键概念:
- 泛型类 :你自己定义的带类型参数的类(比如
MyArray<T>) - 类型实参 :创建对象时给类型参数指定的具体类型(比如
Integer、String)
两步可以合起来写:
泛型类<类型实参> 变量名 = new 泛型类<类型实参>(构造参数);
2. 代码示例
public class Main {
public static void main(String[] args) {
// 创建一个只能存 Integer 的 MyArray
MyArray<Integer> list = new MyArray<Integer>(10);
list.set(0, 100);
list.set(1, 200);
// 取出来直接就是 Integer 类型,不用强转
Integer num = list.get(0);
System.out.println(num);
// list.set(2, "hello"); // 编译报错!不能存 String
}
}
⚠️ 重点提醒:
泛型里不能直接写基本数据类型 (比如
int、double),必须用它们对应的包装类。表格
基本类型 包装类 byte Byte short Short int Integer long Long float Float double Double char Character boolean Boolean
3. 类型推导(菱形操作符)
Java 7 以后,编译器可以根据左边的声明推导出右边的类型,右边的 <> 里可以省略不写:
MyArray<Integer> list = new MyArray<>(); // 右边 <> 里不用再写 Integer
右边的 <> 被称为 "菱形操作符" ,简化了代码,效果和写全 new MyArray<Integer>() 完全一样。
💡 为什么要这样设计?
左边已经写了
<Integer>,编译器当然能推导出右边new MyArray也应该是Integer类型 ------ 何必重复写两遍呢?
五、泛型类的上界
为什么需要类型上界?
泛型默认是 "无界" 的,也就是 E 可以是任何类型。
但有时候我们希望泛型只能接受特定类型或它的子类,比如:
- 只允许数值类型 (
Number及其子类:Integer、Double等) - 只允许实现了某个接口 的类型(比如
Comparable,支持比较)
这时就需要用 extends 关键字来设置类型上界,给类型变量加上约束。
1. 语法规则
class 泛型类名称<类型形参 extends 类型边界> {
// ...
}
规则说明:
extends 类型边界:表示这个类型形参只能接收 类型边界本身 ,或它的子类 / 实现类- 如果类型边界是类:只能用它的子类
- 如果类型边界是接口:只能用实现了它的类
📝 小补充 :如果不写
extends,默认相当于extends Object------ 也就是所有类都可以。
2. 代码示例
// 要求 E 必须是 Number 或它的子类
public class MyArray<E extends Number> {
private E[] array;
public MyArray(int capacity) {
array = (E[]) new Object[capacity];
}
public void set(int index, E value) {
array[index] = value;
}
public E get(int index) {
return array[index];
}
}
使用时的类型检查:
MyArray<Integer> l1; // ✅ 正常:Integer 是 Number 的子类
MyArray<Double> l2; // ✅ 正常:Double 是 Number 的子类
MyArray<String> l3; // ❌ 编译错误:String 不是 Number 的子类
Java 中 Number 是所有数值包装类的父类,它的子类包括:
Integer、Long、Double、Float、Byte、Short
⚠️ 编译器报错意味着什么?
编译器会直接报错,提示你
String不在E extends Number的类型边界内。这就是泛型类型上界的价值 ------在编译期就拦住类型不匹配的问题,而不是等到运行时才炸。
六、总结
表格
| 知识点 | 核心要点 |
|---|---|
| 泛型是什么 | 参数化类型,把类型当参数传递,实现通用且类型安全的代码 |
| 为什么用泛型 | 避免强制类型转换,编译期类型检查,减少运行时错误 |
| 泛型类定义 | class 类名<T> { ... },T 是类型占位符 |
| 泛型类使用 | 类名<具体类型> 对象 = new 类名<>(构造参数); |
| 注意事项 | 不能用基本类型,必须用包装类 |
| 类型推导 | 菱形操作符 <>,右边类型可省略 |
| 类型上界 | <T extends 边界>,约束 T 必须是边界类型或其子类 |
🎓 写在最后 :泛型是 Java 集合框架的基石,也是学习后续
ArrayList、HashMap、泛型方法、通配符?的前提。把这篇文章的概念吃透,后面的路会好走很多