Java泛型(Generics)是Java SE 5引入的一种语言特性,旨在增强类型安全性和代码的重用性。泛型允许类、接口和方法操作对象的特定类型,同时在编译时进行类型检查。通过使用泛型,我们可以编写更通用、更灵活的代码,而无需在每个新类型时都重复实现相同的逻辑。
一、泛型的基本概念
泛型的核心思想是参数化类型,即将类型参数化,使代码可以操作多种类型,而无需指定具体的类型。例如,可以创建一个泛型类或方法,使其能够处理任意类型的对象,而不仅仅是某一种特定类型。
1. 泛型类
泛型类是包含一个或多个类型参数的类。类型参数用尖括号<>
括起来,并放在类名之后。例如:
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
在上述示例中,Box
类是一个泛型类,其中T
是类型参数。我们可以用不同的类型来实例化这个类:
public class GenericClassDemo {
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
System.out.println("String content: " + stringBox.getContent());
Box<Integer> integerBox = new Box<>();
integerBox.setContent(123);
System.out.println("Integer content: " + integerBox.getContent());
}
}
2. 泛型方法
泛型方法是定义了一个或多个类型参数的方法。类型参数在方法的返回类型之前声明。例如:
public class GenericMethodDemo {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] strArray = {"A", "B", "C"};
printArray(intArray);
printArray(strArray);
}
}
在上述示例中,printArray
方法是一个泛型方法,其中T
是类型参数。该方法可以处理任意类型的数组。
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;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
}
在上述示例中,Pair
接口和OrderedPair
类都是泛型的,可以使用不同的类型来创建实例:
public class GenericInterfaceDemo {
public static void main(String[] args) {
Pair<String, Integer> pair = new OrderedPair<>("One", 1);
System.out.println("Key: " + pair.getKey());
System.out.println("Value: " + pair.getValue());
}
}
二、泛型的使用方法
泛型在Java中有多种应用方式,包括限定泛型类型、泛型通配符和泛型方法等。
1. 类型边界
可以使用类型边界来限制类型参数的范围。Java提供了上界和下界来定义类型边界。
上界
上界通过关键字extends
指定,表示类型参数必须是指定类型或其子类型。例如:
public class Box<T extends Number> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
在上述示例中,T
必须是Number
类或其子类的类型。
下界
下界通过关键字super
指定,表示类型参数必须是指定类型或其父类型。例如:
public class LowerBoundDemo {
public static void addNumber(List<? super Integer> list) {
list.add(10);
}
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
addNumber(numberList);
System.out.println(numberList);
}
}
在上述示例中,方法addNumber
接受一个元素类型为Integer
或其父类型的列表。
2. 通配符
通配符?
用于表示未知类型,主要用于以下几种情况:
无界通配符
无界通配符表示任何类型。例如:
public class UnboundedWildcardDemo {
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
public static void main(String[] args) {
List<String> strList = Arrays.asList("A", "B", "C");
printList(strList);
List<Integer> intList = Arrays.asList(1, 2, 3);
printList(intList);
}
}
在上述示例中,printList
方法可以接受任何类型的列表。
有界通配符
有界通配符分为上界通配符和下界通配符。
上界通配符:
public class UpperBoundedWildcardDemo {
public static void printList(List<? extends Number> list) {
for (Number elem : list) {
System.out.println(elem);
}
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
printList(intList);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
printList(doubleList);
}
}
在上述示例中,printList
方法接受元素类型为Number
或其子类型的列表。
下界通配符:
public class LowerBoundedWildcardDemo {
public static void addNumber(List<? super Integer> list) {
list.add(10);
}
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
addNumber(numberList);
System.out.println(numberList);
List<Object> objectList = new ArrayList<>();
addNumber(objectList);
System.out.println(objectList);
}
}
在上述示例中,addNumber
方法接受元素类型为Integer
或其父类型的列表。
三、泛型在集合框架中的应用
Java集合框架广泛使用了泛型,使得集合类在类型安全性和灵活性上得到了极大的提升。以下是一些常见的泛型集合类及其应用:
1. List
List
接口及其实现类如ArrayList
、LinkedList
等都使用了泛型。例如:
import java.util.ArrayList;
import java.util.List;
public class GenericListDemo {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
for (String str : stringList) {
System.out.println(str);
}
}
}
在上述示例中,List<String>
表示一个存储String
类型元素的列表。
2. Set
Set
接口及其实现类如HashSet
、TreeSet
等也使用了泛型。例如:
import java.util.HashSet;
import java.util.Set;
public class GenericSetDemo {
public static void main(String[] args) {
Set<Integer> integerSet = new HashSet<>();
integerSet.add(1);
integerSet.add(2);
integerSet.add(3);
for (Integer num : integerSet) {
System.out.println(num);
}
}
}
在上述示例中,Set<Integer>
表示一个存储Integer
类型元素的集合。
3. Map
Map
接口及其实现类如HashMap
、TreeMap
等也使用了泛型。例如:
import java.util.HashMap;
import java.util.Map;
public class GenericMapDemo {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("One", 1);
map.put("Two", 2);
map.put("Three", 3);
for (String key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
}
}
}
在上述示例中,Map<String, Integer>
表示一个存储键为String
类型、值为Integer
类型的映射。
四、泛型的高级用法
1. 类型擦除
Java泛型是通过类型擦除实现的,这意味着在编译时泛型信息会被移除,替换为其原始类型(通常是Object
)。这也意味着无法在运行时获取泛型类型的信息。例如:
public class ErasureDemo {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
System.out.println(stringList.getClass() == integerList.getClass()); // true
}
}
在上述示例中,虽然stringList
和integerList
是不同类型的列表,但在运行时它们的类型是相同的。
2. 泛型与反射
由于类型擦除的存在,在使用反射时处理泛型类型需要特别小心。例如:
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class ReflectionGenericDemo {
public static void main(String[] args) throws Exception {
List<String> list = new ArrayList<>();
list.add("Hello");
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, 123);
for (Object obj : list) {
System.out.println(obj);
}
}
}
在上述示例中,通过反射将Integer
类型的元素添加到List<String>
中,这在编译时不会报错,但在运行时会导致逻辑错误。
3. 泛型数组
由于类型擦除的限制,Java不支持直接创建泛型数组。例如:
public class GenericArrayDemo<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArrayDemo(int size) {
array = (T[]) new Object[size]; // 需要强制类型转换
}
public void set(int index, T value) {
array[index] = value;
}
public T get(int index) {
return array[index];
}
public static void main(String[] args) {
GenericArrayDemo<String> stringArray = new GenericArrayDemo<>(10);
stringArray.set(0, "Hello");
System.out.println(stringArray.get(0));
}
}
在上述示例中,需要通过强制类型转换创建泛型数组,并使用@SuppressWarnings("unchecked")
注解来抑制编译器警告。
Java泛型提供了一种强大的机制,用于在编写通用代码时增强类型安全性和代码重用性。通过泛型,我们可以创建通用的类、接口和方法,而不必在每次需要处理新类型时重新实现相同的逻辑。泛型在Java集合框架中得到了广泛应用,极大地提高了代码的可读性和安全性。
黑马程序员免费预约咨询