跳到主要内容

Java 泛型类

什么是泛型类?

泛型类是Java中一种特殊的类,它允许我们在创建类的时候指定类中某些属性或方法的参数类型。这样一来,我们可以编写更加通用的代码,提高代码的复用性,同时在编译时期就能够发现类型错误,增强代码的类型安全。

泛型类使用尖括号<>来声明类型参数,这些类型参数可以在类定义的内部被使用,就像普通的类型一样。

提示

泛型类在Java 5及其以后的版本中可用。如果你使用的是较早版本的Java,将无法使用泛型特性。

泛型类的基本语法

泛型类的定义方式如下:

java
public class ClassName<T> {
private T field;

public void setField(T field) {
this.field = field;
}

public T getField() {
return field;
}
}

其中,T是类型参数的名称,它可以是任何合法的标识符,但通常我们会使用单个大写字母来表示类型参数,例如:

  • T - Type(类型)
  • E - Element(元素)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数字)

创建泛型类实例

要创建泛型类的实例,需要在类名后面的尖括号中指定具体的类型:

java
// 创建存储String类型的Box
Box<String> stringBox = new Box<String>();
stringBox.setField("Hello World");
String str = stringBox.getField();

// 创建存储Integer类型的Box
Box<Integer> integerBox = new Box<Integer>();
integerBox.setField(123);
Integer number = integerBox.getField();

从Java 7开始,可以使用菱形运算符<>进行类型推断,简化实例化代码:

java
Box<String> stringBox = new Box<>();

多类型参数的泛型类

泛型类可以有多个类型参数,每个参数用逗号分隔:

java
public class Pair<K, V> {
private K key;
private V value;

public Pair(K key, V value) {
this.key = key;
this.value = value;
}

public K getKey() {
return key;
}

public V getValue() {
return value;
}
}

使用多类型参数的泛型类:

java
Pair<String, Integer> pair = new Pair<>("Age", 25);
String key = pair.getKey(); // "Age"
Integer value = pair.getValue(); // 25

泛型类的类型擦除

Java的泛型是通过类型擦除来实现的,这意味着泛型信息只在编译时可用,而在运行时会被擦除。编译后,泛型类型将被替换为原始类型(通常是Object或指定的上界)。

java
Box<Integer> intBox = new Box<>();
Box<String> strBox = new Box<>();

System.out.println(intBox.getClass() == strBox.getClass()); // 输出true

输出结果:

true

这是因为在运行时,Box<Integer>Box<String>都被类型擦除为相同的原始类型Box

泛型类型约束

可以使用extends关键字来限制类型参数必须是某个类的子类型:

java
public class NumericBox<T extends Number> {
private T number;

public NumericBox(T number) {
this.number = number;
}

public double sqrt() {
// 由于T是Number的子类,我们可以调用doubleValue()方法
return Math.sqrt(number.doubleValue());
}
}

使用带约束的泛型类:

java
NumericBox<Integer> intBox = new NumericBox<>(16);
System.out.println(intBox.sqrt()); // 输出: 4.0

// 编译错误,String不是Number的子类
// NumericBox<String> strBox = new NumericBox<>("Hello");

输出结果:

4.0

实际应用案例

案例1:通用数据容器

以下是一个简单的数据容器泛型类,可以存储任何类型的数据:

java
public class Container<T> {
private T data;

public Container() {
}

public Container(T data) {
this.data = data;
}

public void setData(T data) {
this.data = data;
}

public T getData() {
return data;
}

public boolean hasData() {
return data != null;
}
}

使用示例:

java
// 存储字符串
Container<String> stringContainer = new Container<>("Hello World");
System.out.println("String data: " + stringContainer.getData());

// 存储用户对象
class User {
private String name;
private int age;

public User(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}

Container<User> userContainer = new Container<>(new User("John", 30));
System.out.println("User data: " + userContainer.getData());

输出结果:

String data: Hello World
User data: User{name='John', age=30}

案例2:自定义泛型栈

下面是一个使用泛型实现的简单栈数据结构:

java
public class Stack<E> {
private Object[] elements;
private int size = 0;
private static final int INITIAL_CAPACITY = 10;

public Stack() {
elements = new Object[INITIAL_CAPACITY];
}

public void push(E element) {
ensureCapacity();
elements[size++] = element;
}

@SuppressWarnings("unchecked")
public E pop() {
if (size == 0) {
throw new IllegalStateException("Stack is empty");
}

E result = (E) elements[--size];
elements[size] = null; // 避免内存泄漏
return result;
}

public boolean isEmpty() {
return size == 0;
}

public int size() {
return size;
}

private void ensureCapacity() {
if (size == elements.length) {
Object[] newElements = new Object[2 * size + 1];
System.arraycopy(elements, 0, newElements, 0, size);
elements = newElements;
}
}
}

使用示例:

java
Stack<String> stack = new Stack<>();
stack.push("First");
stack.push("Second");
stack.push("Third");

while (!stack.isEmpty()) {
System.out.println(stack.pop());
}

输出结果:

Third
Second
First

泛型类与继承

泛型类也可以扩展其他类和实现接口。以下是一个扩展ArrayList的泛型类示例:

java
import java.util.ArrayList;

public class SimpleList<T> extends ArrayList<T> {
public T getFirst() {
if (isEmpty()) {
return null;
}
return get(0);
}

public T getLast() {
if (isEmpty()) {
return null;
}
return get(size() - 1);
}

public void addIfAbsent(T element) {
if (!contains(element)) {
add(element);
}
}
}

使用示例:

java
SimpleList<Integer> numbers = new SimpleList<>();
numbers.add(10);
numbers.add(20);
numbers.add(30);
numbers.add(20); // 重复元素

System.out.println("First: " + numbers.getFirst());
System.out.println("Last: " + numbers.getLast());

numbers.addIfAbsent(20); // 已存在,不会添加
numbers.addIfAbsent(40); // 不存在,会添加

System.out.println("List: " + numbers);

输出结果:

First: 10
Last: 20
List: [10, 20, 30, 20, 40]

泛型类的常见问题与解决方案

1. 无法创建泛型类型的数组

java
// 编译错误:无法创建泛型数组
// T[] array = new T[10];

解决方案:使用Object数组并进行类型转换,或使用ArrayList。

java
// 方案1:使用Object数组
@SuppressWarnings("unchecked")
T[] array = (T[]) new Object[10];

// 方案2:使用ArrayList
List<T> list = new ArrayList<>();

2. 泛型类的静态上下文限制

泛型参数不能在静态上下文中使用:

java
public class Box<T> {
private T value;

// 编译错误:无法在静态上下文中使用T
// private static T defaultValue;

// 编译错误:无法在静态方法中引用类型参数T
// public static T getDefaultValue() {
// return null;
// }
}

解决方案:为静态方法添加自己的类型参数:

java
public class Box<T> {
private T value;

// 为静态方法添加单独的类型参数
public static <E> Box<E> createBox(E value) {
Box<E> box = new Box<>();
box.setValue(value);
return box;
}

public void setValue(T value) {
this.value = value;
}

public T getValue() {
return value;
}
}

总结

Java泛型类是Java面向对象编程中的一个强大特性,它允许我们编写更加通用和类型安全的代码。通过使用泛型类:

  1. 我们可以创建能够处理多种不同类型数据的类
  2. 编译器能够在编译时检查类型安全性
  3. 避免了手动类型转换,减少了运行时错误
  4. 提高了代码的复用性和可读性

掌握泛型类的使用对于编写高质量的Java代码至关重要,尤其是在处理集合框架和创建通用库时。

练习

为了加深对泛型类的理解,请尝试完成以下练习:

  1. 创建一个泛型类Pair<F, S>,存储两个不同类型的值。
  2. 实现一个泛型类MaxFinder<T extends Comparable<T>>,提供一个方法找出数组中的最大元素。
  3. 创建一个泛型类Cache<K, V>,实现简单的键值存储功能。
警告

记住,泛型只在编译时提供类型安全性。在运行时,由于类型擦除,泛型类型信息会丢失。因此不能依赖运行时的类型检查。

资源推荐