Java基础面试题 - Java泛型的作用是什么?
一、什么是Java泛型
Java泛型(Generics)是JDK 5.0引入的一个重要特性,它允许在定义类、接口和方法时使用类型参数(type parameters)。这种参数化类型的能力使得代码可以应用于多种数据类型,同时保持类型安全。
泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
二、泛型的主要作用
1. 类型安全
泛型的主要作用是提供编译时的类型检查,避免在运行时出现ClassCastException
异常。使用泛型可以让编译器在编译阶段就发现类型不匹配的问题。
// 不使用泛型
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 需要强制类型转换
// 使用泛型
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 无需强制类型转换
2. 消除强制类型转换
泛型可以消除代码中的许多强制类型转换,这使得代码更加清晰可读,减少了出错的可能性。
// 不使用泛型
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 需要强制类型转换
// 使用泛型
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 自动类型推断
3. 代码复用
泛型使得算法和数据结构可以独立于具体的数据类型工作,提高了代码的复用性。
// 可以用于任何类型的Pair类
public class Pair<T, U> {
private T first;
private U second;
public Pair(T first, U second) {
this.first = first;
this.second = second;
}
// getters and setters
}
// 使用示例
Pair<String, Integer> pair1 = new Pair<>("Age", 25);
Pair<Double, Double> pair2 = new Pair<>(3.14, 2.71);
三、泛型的基本使用
1. 泛型类
泛型类是在类名后面添加类型参数声明来定义的。
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
// 使用示例
Box<Integer> integerBox = new Box<>();
integerBox.set(10);
Integer value = integerBox.get();
2. 泛型接口
泛型接口的定义方式与泛型类类似。
public interface Generator<T> {
T next();
}
// 实现泛型接口
public class StringGenerator implements Generator<String> {
@Override
public String next() {
return "Generated String";
}
}
3. 泛型方法
泛型方法是在方法声明中引入类型参数的方法。
public class Util {
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
}
// 使用示例
String middle = Util.<String>getMiddle("John", "Q.", "Public");
// 或者更简单的形式,利用类型推断
String middle = Util.getMiddle("John", "Q.", "Public");
四、泛型的高级特性
1. 类型通配符
类型通配符使用问号(?)表示,表示未知类型。
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
2. 有限制的通配符
可以限制通配符的类型范围。
// 上界通配符 - 接受T及其子类型
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
// 下界通配符 - 接受T及其父类型
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
3. 泛型擦除
Java的泛型是通过类型擦除实现的,这意味着在编译时类型参数信息会被移除,生成的字节码中不包含泛型信息。
// 编译前
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);
// 编译后(类型擦除后)
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
五、泛型的实际应用示例
1. 集合框架中的泛型
Java集合框架广泛使用泛型来保证类型安全。
// 不使用泛型
List list = new ArrayList();
list.add("string");
list.add(10); // 可以添加任何类型
String s = (String) list.get(1); // 运行时错误
// 使用泛型
List<String> list = new ArrayList<>();
list.add("string");
// list.add(10); // 编译时错误
String s = list.get(0); // 安全
2. 自定义泛型数据结构
public class GenericStack<T> {
private List<T> stack = new ArrayList<>();
private int top = 0;
public void push(T item) {
stack.add(top++, item);
}
public T pop() {
return stack.remove(--top);
}
public boolean isEmpty() {
return top == 0;
}
}
// 使用示例
GenericStack<Integer> intStack = new GenericStack<>();
intStack.push(1);
intStack.push(2);
System.out.println(intStack.pop()); // 输出2
六、泛型的限制
-
不能使用基本类型:泛型类型参数必须是引用类型,不能是基本类型。
// 错误 List<int> list = new ArrayList<>(); // 正确 List<Integer> list = new ArrayList<>();
-
不能实例化类型参数:
public static <T> void append(List<T> list) { T elem = new T(); // 编译错误 list.add(elem); }
-
不能声明静态泛型变量:
public class MobileDevice<T> { private static T os; // 编译错误 }
-
不能对参数化类型使用instanceof:
if (list instanceof List<String>) { // 编译错误 }
七、总结
Java泛型通过参数化类型提供了以下主要优势:
- 更强的类型检查:在编译时检测出更多的错误
- 消除类型转换:使代码更清晰简洁
- 更高的代码复用:可以编写更通用的算法和数据结构
泛型是Java语言中一个强大且复杂的特性,正确使用泛型可以显著提高代码的质量和可维护性。虽然初学者可能会觉得泛型有些难以理解,但一旦掌握,它将成为你Java编程工具箱中不可或缺的一部分。