Java泛型详解
1. 泛型概述
什么是泛型
泛型(Generic)就是规范数据类型的一种机制,用于解决类型不匹配的安全问题。
泛型的作用
- 类型安全:让程序安全问题从运行时期体现到了编译时期
- 强制限制:强制客观的限制类型,避免类型转换异常
- 编译优化:编译完成后Java系统会把限定规则删除
- 集合应用:在集合框架中广泛应用
泛型的优势
示例对比:
// 不使用泛型(容易出错)
ArrayList list = new ArrayList();
list.add("Hello");
list.add(123); // 可以添加不同类型
String str = (String) list.get(1); // 运行时异常
// 使用泛型(类型安全)
ArrayList<String> list = new ArrayList<String>();
list.add("Hello");
// list.add(123); // 编译时错误,无法通过编译
String str = list.get(0); // 无需强制转换
意义: 只能存储String类型的元素,从而避免了不同类型的混合存储。
2. 泛型的定义语法
基本语法
定义一个泛型需要在类、接口或方法名后加 <E>,此类或方法中就可以使用泛型了。
参数说明:
E代表所要规范的类型(Element的缩写)- 可以使用其他字母:
T(Type)、K(Key)、V(Value)等
3. 泛型定义在类上
历史背景
- JDK早期版本:当类中要操作的引用数据类型不确定时,使用Object来完成扩展
- JDK 1.5后:引入泛型来完成类型规范
泛型类定义
最简单的泛型类示例:
class GenericDemo<E> {
private E t;
public void setT(E t) {
this.t = t;
}
public E getT() {
return t;
}
}
泛型类使用
创建泛型类实例:
// 定义String类型的泛型类
GenericDemo<String> demo = new GenericDemo<String>();
此时,t 元素的类型为String。
验证泛型类型:
System.out.println(GenericDemo.class.getField("t").getType().toString());
// 输出结果为:String
完整的泛型类示例
public class Box<T> {
private T content;
public Box(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
@Override
public String toString() {
return "Box{content=" + content + "}";
}
}
// 使用示例
public class TestGenericClass {
public static void main(String[] args) {
Box<String> stringBox = new Box<>("Hello Generic");
Box<Integer> intBox = new Box<>(42);
System.out.println(stringBox.getContent()); // Hello Generic
System.out.println(intBox.getContent()); // 42
}
}
4. 泛型定义在接口上
泛型接口定义
泛型接口的定义与泛型类类似:
public interface GenericInterface<T> {
T process(T input);
void display(T data);
}
泛型接口实现
方式一:实现类不指定泛型类型
public class GenericImplementation<T> implements GenericInterface<T> {
@Override
public T process(T input) {
// 具体实现
return input;
}
@Override
public void display(T data) {
System.out.println("Data: " + data);
}
}
方式二:实现类指定具体类型
public class StringImplementation implements GenericInterface<String> {
@Override
public String process(String input) {
return input.toUpperCase();
}
@Override
public void display(String data) {
System.out.println("String Data: " + data);
}
}
5. 泛型定义在方法上
使用场景
如果泛型定义在类上,那么类中的方法操作的类型就固定为泛型类型了。为了让每个方法操作的类型不同,我们可以将泛型定义在每个方法上。
泛型方法语法
基本语法:
public <E> void show(E t) {
System.out.println("Value: " + t);
}
语法要点:
- 泛型参数
<E>必须在返回值类型之前 - 方法体中可以使用泛型参数
E
泛型方法示例
public class GenericMethodDemo {
// 泛型方法:交换数组中两个元素的位置
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// 泛型方法:打印数组元素
public static <E> void printArray(E[] array) {
for (E element : array) {
System.out.println(element);
}
}
// 泛型方法:查找最大值
public static <T extends Comparable<T>> T findMax(T[] array) {
T max = array[0];
for (T element : array) {
if (element.compareTo(max) > 0) {
max = element;
}
}
return max;
}
}
使用示例:
public class TestGenericMethod {
public static void main(String[] args) {
String[] strings = {"apple", "banana", "cherry"};
Integer[] numbers = {3, 1, 4, 1, 5};
// 使用泛型方法
GenericMethodDemo.printArray(strings);
GenericMethodDemo.printArray(numbers);
// 交换元素
GenericMethodDemo.swap(strings, 0, 2);
GenericMethodDemo.printArray(strings); // cherry, banana, apple
// 查找最大值
String maxString = GenericMethodDemo.findMax(strings);
Integer maxNumber = GenericMethodDemo.findMax(numbers);
System.out.println("Max string: " + maxString);
System.out.println("Max number: " + maxNumber);
}
}
💡 注意: 泛型类和类中泛型方法是不冲突的,可以同时存在。
6. 泛型定义在静态方法上
为什么静态方法需要独立定义泛型
因为泛型类是规范对象的类型,而静态方法属于类而不是对象,所以静态方法不能访问类上定义的泛型。
静态泛型方法语法
如果静态方法要使用泛型,必须将泛型定义在静态方法上。
语法要点:
- 泛型定义应该在
static关键字后面 - 在返回值类型之前
public static <E> void method(E t) {
// 方法体
}
静态泛型方法示例
public class StaticGenericDemo {
// 静态泛型方法:比较两个对象是否相等
public static <T> boolean isEqual(T obj1, T obj2) {
return obj1.equals(obj2);
}
// 静态泛型方法:创建数组
@SuppressWarnings("unchecked")
public static <T> T[] createArray(int length) {
return (T[]) new Object[length];
}
// 静态泛型方法:合并两个数组
public static <T> T[] mergeArrays(T[] array1, T[] array2) {
@SuppressWarnings("unchecked")
T[] result = (T[]) new Object[array1.length + array2.length];
System.arraycopy(array1, 0, result, 0, array1.length);
System.arraycopy(array2, 0, result, array1.length, array2.length);
return result;
}
}
使用示例:
public class TestStaticGeneric {
public static void main(String[] args) {
// 使用静态泛型方法
boolean result = StaticGenericDemo.isEqual("Hello", "Hello");
System.out.println("Are equal: " + result); // true
String[] arr1 = {"A", "B"};
String[] arr2 = {"C", "D"};
String[] merged = StaticGenericDemo.mergeArrays(arr1, arr2);
for (String s : merged) {
System.out.println(s); // A B C D
}
}
}
7. 泛型限定(通配符)
通配符类型
| 通配符 | 说明 | 用途 |
|---|---|---|
<E> | 类型明确 | 明确指定具体类型 |
<?> | 不明确类型 | 占位符、通配符,代表此泛型通用 |
7.1 无界通配符
语法: <?>
public void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
7.2 向上限定(上边界)
语法: <? extends E>
含义: 此泛型类型不明确,但是此泛型限定为E或者E的子类。
// 向上限定示例
public class BoundedGeneric {
// 只接受Number及其子类
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number number : list) {
sum += number.doubleValue();
}
return sum;
}
}
使用示例:
public class TestUpperBound {
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
// 可以传入Integer和Double,因为它们都是Number的子类
System.out.println(BoundedGeneric.sumOfList(intList)); // 6.0
System.out.println(BoundedGeneric.sumOfList(doubleList)); // 6.6
// List<String> stringList = Arrays.asList("a", "b", "c");
// BoundedGeneric.sumOfList(stringList); // 编译错误
}
}
7.3 向下限定(下边界)
语法: <? super E>
含义: 定义不明确泛型,接受E类型或者E的父类型。
// 向下限定示例
public class LowerBoundGeneric {
// 只接受Integer及其父类
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
}
使用示例:
public class TestLowerBound {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
List<Number> numList = new ArrayList<>();
List<Object> objList = new ArrayList<>();
// 可以传入Integer及其父类
LowerBoundGeneric.addIntegers(intList); // OK
LowerBoundGeneric.addIntegers(numList); // OK
LowerBoundGeneric.addIntegers(objList); // OK
// List<Double> doubleList = new ArrayList<>();
// LowerBoundGeneric.addIntegers(doubleList); // 编译错误
}
}
7.4 PECS原则
PECS (Producer Extends Consumer Super) 原则:
- Producer Extends:如果你需要从集合中读取数据,使用
? extends - Consumer Super:如果你需要向集合中写入数据,使用
? super
public class PECSExample {
// Producer: 从集合中读取数据
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T item : src) {
dest.add(item); // 读取src,写入dest
}
}
}
8. 泛型使用注意事项
8.1 类型擦除
Java的泛型是通过类型擦除实现的:
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
// 运行时类型相同
System.out.println(stringList.getClass() == intList.getClass()); // true
8.2 不能创建泛型数组
// 编译错误
// List<String>[] stringLists = new List<String>[10];
// 正确方式
@SuppressWarnings("unchecked")
List<String>[] stringLists = new List[10];
8.3 静态成员不能使用类的泛型
public class GenericClass<T> {
// private static T staticField; // 编译错误
// public static T getStaticField() { return staticField; } // 编译错误
// 静态方法需要自己定义泛型
public static <E> E getStaticValue(E value) {
return value;
}
}
9. 泛型的实际应用
9.1 自定义泛型集合
public class MyList<E> {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_CAPACITY = 10;
public MyList() {
elements = new Object[DEFAULT_CAPACITY];
}
public void add(E element) {
if (size == elements.length) {
resize();
}
elements[size++] = element;
}
@SuppressWarnings("unchecked")
public E get(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
return (E) elements[index];
}
public int size() {
return size;
}
private void resize() {
int newCapacity = elements.length * 2;
elements = Arrays.copyOf(elements, newCapacity);
}
}
9.2 泛型工具类
public class GenericUtils {
// 泛型方法:安全的类型转换
@SuppressWarnings("unchecked")
public static <T> T cast(Object obj, Class<T> type) {
if (type.isInstance(obj)) {
return (T) obj;
}
throw new ClassCastException("Cannot cast " + obj.getClass() + " to " + type);
}
// 泛型方法:创建列表
@SafeVarargs
public static <T> List<T> listOf(T... elements) {
return new ArrayList<>(Arrays.asList(elements));
}
// 泛型方法:查找元素
public static <T> int indexOf(T[] array, T target) {
for (int i = 0; i < array.length; i++) {
if (Objects.equals(array[i], target)) {
return i;
}
}
return -1;
}
}
10. 总结
泛型的优势
- 编译时类型检查:提前发现类型不匹配问题
- 消除类型转换:不需要显式的类型转换
- 提高代码重用:一个泛型类可以处理多种类型
- 增强可读性:代码更清晰地表达设计意图
泛型的应用场景
- 集合框架:ArrayList、HashMap等
- 工具类:提供通用的操作方法
- API设计:提供类型安全的接口
- 框架开发:Spring、Hibernate等框架大量使用泛型
使用建议
- 优先使用泛型:新代码应该使用泛型而不是原始类型
- 合理使用通配符:根据PECS原则选择合适的通配符
- 注意类型擦除:理解泛型的实现机制
- 避免过度复杂:不要创建过于复杂的泛型声明
泛型是Java中重要的特性,正确使用泛型可以让代码更加安全、清晰和易于维护。掌握泛型的各种用法和限制,是成为优秀Java开发者的必备技能。
本文详细介绍了Java泛型的概念、语法、应用场景和注意事项,希望对Java学习者深入理解和使用泛型有所帮助。