Java泛型完全指南
本篇博客带你从零掌握泛型。内容包括:泛型类、泛型接口、泛型方法、通配符与上下限、包装类等。所有代码均来自
com.wfs.demo2genericity等包,可直接运行验证。
1 认识泛型
1.1 什么是泛型?
泛型 (Generics)是 JDK 5 引入的特性,允许我们在定义类、接口、方法时使用类型变量 (如 <E>、<T>),在真正使用时才确定具体的类型。
java
public class ArrayList<E> {
...
}
- 泛型类:
class 类名<类型变量> { } - 泛型接口:
interface 接口名<类型变量> { } - 泛型方法:
修饰符 <类型变量> 返回值 方法名(形参) { }
注意 :类型变量建议用大写的英文字母,常用:E(Element)、T(Type)、K(Key)、V(Value)。
1.2 泛型的作用
- 编译阶段约束数据类型:在编译时就确定集合中能放什么类型的数据,防止误操作。
- 自动检查,避免强制类型转换 :取出数据时直接得到目标类型,无需手动强转,也就避免了
ClassCastException。 - 提高代码复用性:一套代码可以处理多种类型。
1.3 泛型的本质
把具体的数据类型作为参数传给类型变量。类似于方法的形式参数,泛型是类型的"形式参数",使用时传入"实际类型参数"。
【案例】GenericDemo1:使用泛型的好处
java
package com.wfs.demo2genericity;
import java.util.ArrayList;
public class GenericDemo1 {
public static void main(String[] args) {
// 不使用泛型(旧写法):
// ArrayList list = new ArrayList();
// list.add("java");
// list.add(23); // 不小心混入其他类型
// String s = (String) list.get(1); // 运行时 ClassCastException
// 使用泛型:
ArrayList<String> list = new ArrayList<String>();
list.add("java");
list.add("php");
// list.add(23); // 编译报错,无法添加非String类型
// list.add(99.9);
// 获取数据时无需强转
for (int i = 0; i < list.size(); i++) {
String s = list.get(i); // 直接得到String
System.out.println(s);
}
}
}
运行结果(仅输出两行):
java
php
结论:泛型让集合在编译时就限定了元素类型,避免了类型转换异常。
2 泛型类
2.1 定义语法
java
修饰符 class 类名<类型变量, 类型变量,...> {
// 可以使用类型变量定义字段、方法参数、返回值
}
2.2 自定义泛型类示例:模拟 ArrayList
【案例】MyArrayList(自定义泛型类)
java
// MyArrayList.java
package com.wfs.demo2genericity;
import java.util.ArrayList;
public class MyArrayList<E> {
private ArrayList list = new ArrayList();
public boolean add(E e) {
list.add(e);
return true;
}
public boolean remove(E e) {
return list.remove(e);
}
@Override
public String toString() {
return list.toString();
}
}
【使用】GenericDemo2
java
package com.wfs.demo2genericity;
public class GenericDemo2 {
public static void main(String[] args) {
// 泛型擦除:编译后泛型信息被擦除,运行时无泛型
MyArrayList<String> mlist = new MyArrayList<>(); // JDK 7钻石语法
mlist.add("hello");
mlist.add("world");
// mlist.add(555); // 编译报错
mlist.add("java");
mlist.add("前端");
System.out.println(mlist.remove("world")); // true
System.out.println(mlist); // [hello, java, 前端]
}
}
运行结果:
true
[hello, java, 前端]
解释 :MyArrayList<E> 的 E 在使用时被指定为 String,编译器会约束所有添加的元素必须是 String,取出时也是 String(实际是 Object,但编译器做了类型安全保证)。
3 泛型接口
3.1 定义语法
java
修饰符 interface 接口名<类型变量, 类型变量,...> {
// 抽象方法可以使用类型变量
}
3.2 实现方式
实现类可以在实现接口时指定具体类型 ,也可以继续保留泛型。
【案例】泛型接口 Data
java
// Data.java
package com.wfs.demo3genericity;
public interface Data<T> {
void add(T t);
void delete(T t);
void update(T t);
T query(int id);
}
【实现类】StudentData 和 TeacherData
java
// StudentData.java
public class StudentData implements Data<Student> {
@Override
public void add(Student student) { /* 实现代码 */ }
@Override
public void delete(Student student) { }
@Override
public void update(Student student) { }
@Override
public Student query(int id) { return new Student(); }
}
// TeacherData.java
public class TeacherData implements Data<Teacher> {
// 类似实现 ...
}
【使用】GenericDemo3
java
public class GenericDemo3 {
public static void main(String[] args) {
StudentData studentData = new StudentData();
studentData.add(new Student());
studentData.delete(new Student());
Student s = studentData.query(1);
}
}
说明 :Data<T> 接口通过泛型,让 StudentData 和 TeacherData 复用同一套接口定义,分别操作 Student 和 Teacher 对象,避免编写重复接口。
4 泛型方法
4.1 定义语法
java
修饰符 <类型变量, 类型变量,...> 返回值类型 方法名(形参列表) {
// 方法体内可以使用类型变量
}
- 注意 :泛型方法的类型变量放在返回值之前,且只在当前方法中有效。
【案例】GenericDemo4:泛型方法示例
java
package com.wfs.demo4genericity;
public class GenericDemo4 {
public static void main(String[] args) {
String[] names = {"赵敏", "张无忌", "周芷若", "小昭"};
printArray(names); // 自动推断 T 为 String
Student[] stus = new Student[3];
printArray(stus); // T 为 Student
Student max = getMax(stus); // 返回 Student
String max2 = getMax(names); // 返回 String
}
// 泛型方法:打印任意类型的数组
public static <T> void printArray(T[] arr) {
for (T t : arr) {
System.out.print(t + " ");
}
System.out.println();
}
// 泛型方法:获取数组中的最大值(假设有比较逻辑)
public static <T> T getMax(T[] arr) {
// 实际需要 T 实现 Comparable,这里仅示意
return arr.length > 0 ? arr[0] : null;
}
}
作用 :同一个方法可以处理 String[]、Student[] 等多种类型,无需重载。
5 通配符与上下限
5.1 通配符 ?
- 通配符
?表示"未知的类型",在使用泛型时(而不是定义)代表一切类型。 - 定义时用
T,使用时用?。
5.2 泛型上限与下限
- 上限 :
? extends Car→ 能接收Car或其子类。 - 下限 :
? super Car→ 能接收Car或其父类。
【案例】GenericDemo5:极品飞车游戏(通配符 + 上限)
java
package com.wfs.demo4genericity;
import java.util.ArrayList;
public class GenericDemo5 {
public static void main(String[] args) {
ArrayList<Xiaomi> xiaomis = new ArrayList<>();
xiaomis.add(new Xiaomi());
go(xiaomis); // 可以传递 Xiaomi 集合
ArrayList<BYD> byds = new ArrayList<>();
byds.add(new BYD());
go(byds); // 可以传递 BYD 集合
// ArrayList<Dog> dogs = new ArrayList<>();
// go(dogs); // 编译报错,因为 Dog 不是 Car 的子类
}
// 使用通配符上限:只允许 Car 及其子类的集合
public static void go(ArrayList<? extends Car> cars) {
// 只能遍历,不能添加元素(因为不知道具体子类型)
for (Car c : cars) {
System.out.println(c);
}
}
}
解释 :虽然 Xiaomi 和 BYD 都是 Car 的子类,但 ArrayList<Xiaomi> 和 ArrayList<BYD> 与 ArrayList<Car> 没有继承关系。通配符 ? extends Car 表示"任何 Car 的子类类型的 ArrayList",从而实现了参数的多态。
注意 :使用 ? extends Car 时,不能向集合中添加元素(除了 null),因为无法确定具体的子类型。
6 泛型与包装类
6.1 为什么需要包装类?
泛型不支持基本数据类型,只能支持引用数据类型(对象类型) 。因为泛型在编译后被擦除为 Object,而基本类型不是 Object 的子类。所以我们需要包装类。
| 基本数据类型 | 包装类 |
|---|---|
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
char |
Character |
float |
Float |
double |
Double |
boolean |
Boolean |
6.2 自动装箱与自动拆箱
- 自动装箱:基本类型自动转换为包装类对象。
- 自动拆箱:包装类对象自动转换为基本类型。
【案例】GenericDemo6:包装类与自动装箱拆箱
java
package com.wfs.demo5genericity;
import java.util.ArrayList;
public class GenericDemo6 {
public static void main(String[] args) {
// 泛型不支持基本类型,必须使用包装类
// ArrayList<int> list = new ArrayList<>(); // 编译错误
ArrayList<Integer> list = new ArrayList<>();
list.add(130); // 自动装箱:int -> Integer
list.add(120);
int rs = list.get(1); // 自动拆箱:Integer -> int
System.out.println(rs); // 120
// 手工包装
Integer it1 = Integer.valueOf(100);
Integer it2 = Integer.valueOf(100);
System.out.println(it1 == it2); // true,因为 -128~127 有缓存
Integer it11 = 130;
Integer it22 = 130;
System.out.println(it11 == it22); // false,超过缓存范围,不同对象
// 自动拆箱
int i = it11; // Integer 自动转为 int
System.out.println(i); // 130
}
}
6.3 包装类的常用功能
- 基本类型 → 字符串
java
int j = 23;
String rs1 = Integer.toString(j); // "23"
String rs3 = j + ""; // 推荐简单方式
- 字符串 → 基本类型(常用)
java
String str = "98";
int i1 = Integer.parseInt(str); // 98
int i2 = Integer.valueOf(str); // 推荐,同样返回 int
double d = Double.parseDouble("98.8");
【案例】完整演示
java
// 包装类新增功能
public static void main(String[] args) {
// 1. 基本类型 -> 字符串
int j = 23;
String rs1 = Integer.toString(j);
System.out.println(rs1 + 1); // 231
// 2. 字符串 -> 基本类型
String str = "98";
int i1 = Integer.parseInt(str);
System.out.println(i1 + 2); // 100
String str2 = "98.8";
double d = Double.parseDouble(str2);
System.out.println(d + 2); // 100.8
}
7 总结
| 知识点 | 要点 |
|---|---|
| 泛型本质 | 类型参数化,编译时检查,运行时擦除 |
| 泛型类 | class MyArrayList<E>,使用时指定具体类型 |
| 泛型接口 | interface Data<T>,实现时可确定类型 |
| 泛型方法 | public static <T> void test(T t),独立于类的泛型 |
| 通配符 | ? 用于使用泛型时,? extends Car 上限,? super Car 下限 |
| 包装类 | 8种基本类型对应的引用类型,解决泛型不支持基本类型问题 |
| 自动装箱/拆箱 | 基本类型与包装类之间自动转换 |
| 包装类常用功能 | parseInt、valueOf、toString 等 |
一句话:泛型让代码更安全、更通用;包装类让基本类型也能"万物皆对象"。
本文案例代码均位于
com.wfs.demo2genericity~demo5genericity包,可直接运行体验。下一节我们将学习集合框架,敬请期待!