Java 泛型(Generics)是 Java SE 5 引入的一个强大特性,它允许你定义类、接口和方法时使用类型参数,从而使代码更加灵活和可重用。本篇博客将详细讲解 Java 泛型的概念、使用方法和注意事项,并通过多个代码示例,帮助新人快速理解和掌握泛型的使用。
一、什么是 Java 泛型?
泛型是参数化类型的一种机制,允许类、接口和方法在定义时使用类型参数,而在使用时再指定具体类型。这使得代码更加通用、类型安全并且易于维护。
1.1 泛型的优势
- 类型安全:在编译时检测类型错误,减少运行时类型转换异常。
- 代码重用:通过泛型编写的代码可以用于多种类型,减少冗余代码。
- 可读性和可维护性:泛型代码更加清晰,容易理解和维护。
二、泛型类和泛型接口
2.1 定义泛型类
定义泛型类时,需要在类名后面添加类型参数。类型参数可以是单个字母(如 T
)、多个字母(如 K, V
)或具有描述性的名字。
public class Box<T> {private T content;public void setContent(T content) {this.content = content;}public T getContent() {return content;}
}
2.2 使用泛型类
在使用泛型类时,需要指定具体的类型参数。
public class Main {public static void main(String[] args) {Box<String> stringBox = new Box<>();stringBox.setContent("Hello");System.out.println(stringBox.getContent()); // 输出: HelloBox<Integer> integerBox = new Box<>();integerBox.setContent(123);System.out.println(integerBox.getContent()); // 输出: 123}
}
2.3 定义泛型接口
泛型接口的定义方式与泛型类类似。
public interface Pair<K, V> {K getKey();V getValue();
}public class OrderedPair<K, V> implements Pair<K, V> {private K key;private V value;public OrderedPair(K key, V value) {this.key = key;this.value = value;}public K getKey() {return key;}public V getValue() {return value;}
}public class Main {public static void main(String[] args) {Pair<String, Integer> pair = new OrderedPair<>("One", 1);System.out.println("Key: " + pair.getKey() + ", Value: " + pair.getValue()); // 输出: Key: One, Value: 1}
}
三、泛型方法
3.1 定义泛型方法
泛型方法是在方法定义中使用类型参数,类型参数放在方法返回类型前面。
public class Utility {public static <T> void printArray(T[] array) {for (T element : array) {System.out.print(element + " ");}System.out.println();}
}
3.2 调用泛型方法
在调用泛型方法时,编译器会根据传入的参数推断类型参数。
public class Main {public static void main(String[] args) {Integer[] intArray = {1, 2, 3};String[] strArray = {"A", "B", "C"};Utility.printArray(intArray); // 输出: 1 2 3 Utility.printArray(strArray); // 输出: A B C }
}
四、泛型边界
4.1 上界通配符
使用 extends
关键字定义泛型的上界,表示参数类型必须是指定类型或其子类。
public class Box<T extends Number> {private T content;public void setContent(T content) {this.content = content;}public T getContent() {return content;}
}public class Main {public static void main(String[] args) {Box<Integer> integerBox = new Box<>();integerBox.setContent(123);System.out.println(integerBox.getContent()); // 输出: 123Box<Double> doubleBox = new Box<>();doubleBox.setContent(45.67);System.out.println(doubleBox.getContent()); // 输出: 45.67}
}
4.2 下界通配符
使用 super
关键字定义泛型的下界,表示参数类型必须是指定类型或其父类。
public class Box<T> {private T content;public void setContent(T content) {this.content = content;}public T getContent() {return content;}
}public class Utility {public static void addNumbers(Box<? super Integer> box) {box.setContent(123);}
}public class Main {public static void main(String[] args) {Box<Number> numberBox = new Box<>();Utility.addNumbers(numberBox);System.out.println(numberBox.getContent()); // 输出: 123}
}
五、通配符类型
通配符类型使用 ?
表示,在使用泛型类或接口时可以表示未知类型。
public class Utility {public static void printBox(Box<?> box) {System.out.println("Content: " + box.getContent());}
}public class Main {public static void main(String[] args) {Box<String> stringBox = new Box<>();stringBox.setContent("Hello");Utility.printBox(stringBox); // 输出: Content: HelloBox<Integer> integerBox = new Box<>();integerBox.setContent(123);Utility.printBox(integerBox); // 输出: Content: 123}
}
六、泛型的类型擦除
Java 泛型在编译时会进行类型擦除,这意味着在运行时泛型信息会被移除,所有类型参数都会被替换为其上界(如果没有指定上界,则替换为 Object
)。
6.1 类型擦除的影响
类型擦除可能导致以下问题:
- 类型安全检查的丢失:在运行时无法获取泛型类型参数的信息,可能导致类型转换异常。
- 重载方法的冲突:由于类型擦除,不同的泛型方法在运行时可能冲突。
public class Main {public static void printList(List<String> list) {for (String element : list) {System.out.print(element + " ");}System.out.println();}// 会导致编译错误,因为类型擦除后签名相同// public static void printList(List<Integer> list) {// for (Integer element : list) {// System.out.print(element + " ");// }// System.out.println();// }public static void main(String[] args) {List<String> stringList = Arrays.asList("A", "B", "C");printList(stringList); // 输出: A B C }
}
6.2 避免类型擦除问题
为了避免类型擦除带来的问题,可以使用不同的方法名或显式地进行类型转换。
public class Main {public static void printStringList(List<String> list) {for (String element : list) {System.out.print(element + " ");}System.out.println();}public static void printIntegerList(List<Integer> list) {for (Integer element : list) {System.out.print(element + " ");}System.out.println();}public static void main(String[] args) {List<String> stringList = Arrays.asList("A", "B", "C");printStringList(stringList); // 输出: A B C List<Integer> integerList = Arrays.asList(1, 2, 3);printIntegerList(integerList); // 输出: 1 2 3 }
}
七、小结
通过这篇博客,我们详细讲解了 Java 泛型的概念、使用方法和注意事项。我们了解了如何定义和使用泛型类、泛型接口和泛型方法,以及如何设置泛型边界和使用通配符类型。我们还探讨了类型擦除带来的问题及其解决方案。
希望这篇博客能够帮助你快速理解和掌握 Java 泛型。如果你对泛型还有其他疑问或有更多的使用技巧,欢迎在评论区分享和讨论。记住,编程不仅仅是写代码,更是不断学习和交流的过程。Happy coding!