Java 泛型类
什么是泛型类?
泛型类是Java中一种特殊的类,它允许我们在创建类的时候指定类中某些属性或方法的参数类型。这样一来,我们可以编写更加通用的代码,提高代码的复用性,同时在编译时期就能够发现类型错误,增强代码的类型安全。
泛型类使用尖括号<>
来声明类型参数,这些类型参数可以在类定义的内部被使用,就像普通的类型一样。
泛型类在Java 5及其以后的版本中可用。如果你使用的是较早版本的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(数字)
创建泛型类实例
要创建泛型类的实例,需要在类名后面的尖括号中指定具体的类型:
// 创建存储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开始,可以使用菱形运算符<>
进行类型推断,简化实例化代码:
Box<String> stringBox = new Box<>();
多类型参数的泛型类
泛型类可以有多个类型参数,每个参数用逗号分隔:
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;
}
}
使用多类型参数的泛型类:
Pair<String, Integer> pair = new Pair<>("Age", 25);
String key = pair.getKey(); // "Age"
Integer value = pair.getValue(); // 25
泛型类的类型擦除
Java的泛型是通过类型擦除来实现的,这意味着泛型信息只在编译时可用,而在运行时会被擦除。编译后,泛型类型将被替换为原始类型(通常是Object或指定的上界)。
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
关键字来限制类型参数必须是某个类的子类型:
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());
}
}
使用带约束的泛型类:
NumericBox<Integer> intBox = new NumericBox<>(16);
System.out.println(intBox.sqrt()); // 输出: 4.0
// 编译错误,String不是Number的子类
// NumericBox<String> strBox = new NumericBox<>("Hello");
输出结果:
4.0
实际应用案例
案例1:通用数据容器
以下是一个简单的数据容器泛型类,可以存储任何类型的数据:
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;
}
}
使用示例:
// 存储字符串
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:自定义泛型栈
下面是一个使用泛型实现的简单栈数据结构:
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;
}
}
}
使用示例:
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的泛型类示例:
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);
}
}
}
使用示例:
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. 无法创建泛型类型的数组
// 编译错误:无法创建泛型数组
// T[] array = new T[10];
解决方案:使用Object数组并进行类型转换,或使用ArrayList。
// 方案1:使用Object数组
@SuppressWarnings("unchecked")
T[] array = (T[]) new Object[10];
// 方案2:使用ArrayList
List<T> list = new ArrayList<>();
2. 泛型类的静态上下文限制
泛型参数不能在静态上下文中使用:
public class Box<T> {
private T value;
// 编译错误:无法在静态上下文中使用T
// private static T defaultValue;
// 编译错误:无法在静态方法中引用类型参数T
// public static T getDefaultValue() {
// return null;
// }
}
解决方案:为静态方法添加自己的类型参数:
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面向对象编程中的一个强大特性,它允许我们编写更加通用和类型安全的代码。通过使用泛型类:
- 我们可以创建能够处理多种不同类型数据的类
- 编译器能够在编译时检查类型安全性
- 避免了手动类型转换,减少了运行时错误
- 提高了代码的复用性和可读性
掌握泛型类的使用对于编写高质量的Java代码至关重要,尤其是在处理集合框架和创建通用库时。
练习
为了加深对泛型类的理解,请尝试完成以下练习:
- 创建一个泛型类
Pair<F, S>
,存储两个不同类型的值。 - 实现一个泛型类
MaxFinder<T extends Comparable<T>>
,提供一个方法找出数组中的最大元素。 - 创建一个泛型类
Cache<K, V>
,实现简单的键值存储功能。
记住,泛型只在编译时提供类型安全性。在运行时,由于类型擦除,泛型类型信息会丢失。因此不能依赖运行时的类型检查。
资源推荐
- Oracle Java泛型教程:https://docs.oracle.com/javase/tutorial/java/generics/index.html
- 《Java编程思想》- 第15章:泛型
- 《Effective Java》- 第26-33条:泛型