在Java中,泛型是十分强大的功能,它允许我们在类、方法和接口中使用类型参数,提供安全且灵活的代码。反省通过让你指定通用类型参数来实现“类型独立性”,避免显式的类型转换(数据类型的强转肯定都用过),同时提高了代码的可读性和可维护性。
1.在类上的应用
泛型类指的是在类定义时使用类型参数(比如<T>, <E>, <K,V>等)。这种类能够处理多种类型,而不必为每种类型编写多个相似的类。
<T>
:泛型类型参数,表示任意类型。通常用于类、方法的单一类型参数。<E>
:泛型元素类型参数,通常用于表示集合类中元素的类型,如List<E>
、Set<E>
等。<K, V>
:泛型键值对类型参数,常用于表示映射类(如Map<K, V>
)的键值对类型。注:这些字母只是符号,代表不同的泛型类型参数,命名可以根据需求进行调整,其他字母也可以使用,其本质上就是一个占位符而已,没有特定的功能,但 Java 的社区约定了这些字母的使用方式,帮助代码更具可读性和一致性,最好还是遵守这些默认的编程规范。
public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
===========================使用示例======================
Box<Integer> intBox = new Box<>();
intBox.set(10); // T 被替换为 Integer 类型
Integer value = intBox.get(); // 返回类型为 Integer
- 在实例化时,
T
被替换为具体的类型(如Integer
或String
)。 - 泛型类提供了类型安全性,在编译时就可以检查类型是否匹配。
2.在方法上的应用
泛型方法是指方法本身使用泛型,允许方法在调用时指定类型。它与泛型类不同,泛型参数只作用于该方法。
public class Util {
public static <T> void printArray(T[] array) { // <T> 表示这是一个泛型方法
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}
public static <T>
表示定义一个泛型方法,并且T
只在该方法的作用域内有效。- 方法的参数
T[] array
表明它可以接受任何类型的数组作为参数。
使用
Integer[] intArray = {1, 2, 3};
String[] strArray = {"apple", "banana", "cherry"};
Util.printArray(intArray); // 输出 1 2 3
Util.printArray(strArray); // 输出 apple banana cherry
- 通过
<T>
声明泛型参数后,调用该方法时可以传入不同类型的参数。 - 编译器会自动根据方法传入的参数类型推断泛型类型。
3. 通配符的使用
通配符是 Java 泛型中用于表示未知类型的符号。常见的通配符有三种类型:
?
:表示未知类型。? extends T
:表示一个类型为T
或其子类型的类型。? super T
:表示一个类型为T
或其父类型的类型。
3.1 ?:表示任何类型
?是最基本的通配符,表示任何类型。通常用于不关系具体类型的情况下。
List<?> list = new ArrayList<String>(); // 可以是任何类型的 List
3.2 ? extends T :表示某一个类型的子类型
? extends T
表示类型为 T
或 T
的子类。这个通配符通常用于读取数据,因为它允许你读取 T
或 T
的子类的元素。
public static void printList(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
printList(intList); // 适用于 List<Integer>
? extends T
允许传入T
或T
的任何子类(包括Integer
、Double
等)。- 通常用于读取数据,因为你可以读取
T
或T
的任何子类,但不能向集合中添加元素(除了null
)。
3.3 ? super T:表示某一个类型的父类型
? super T
表示类型为 T
或 T
的父类。这个通配符通常用于向集合中添加数据,因为它允许你添加 T
或 T
的子类的元素。
public static void addNumbers(List<? super Integer> list) {
list.add(10); // 可以安全地添加 Integer 或其子类
}
List<Number> numberList = new ArrayList<>();
addNumbers(numberList); // 可以传入 List<Number> 或 List<Object>
? super T
允许传入T
或T
的任何父类(包括Object
)。- 通常用于向集合中添加数据,因为它保证你可以添加
T
类型或其子类的对象。
通配符的总结
通配符 | 含义 | 用法 |
---|---|---|
? | 表示任何类型 | 用于不关心类型时(例如:List<?> ) |
? extends T | 表示 T 或 T 的子类 | 用于读取数据(例如:List<? extends Number> ) |
? super T | 表示 T 或 T 的父类 | 用于向集合中添加数据(例如:List<? super Integer> ) |
?
用于无法确定具体类型时,例如在某些方法中只需要通用类型参数时。? extends T
用于方法参数中,当你只需要读取集合中的元素,且知道它们是T
或T
的子类时。? super T
用于方法参数中,当你需要向集合中添加元素,且知道它们是T
或T
的父类时。
4. 泛型的限制
尽管泛型在 Java 中非常强大,但它也有一些限制:
-
不能使用基本数据类型:Java 的泛型只能使用对象类型,不能直接使用基本数据类型,如
int
、char
等。你可以使用包装类,如Integer
、Character
等。 -
不能创建泛型数组:由于泛型类型在运行时会被擦除,所以你不能创建泛型数组。
// 错误,不能创建泛型数组
// T[] array = new T[10];
// 正确,使用 Object 数组
Object[] array = new Object[10];
-
类型擦除:Java 泛型使用类型擦除(Type Erasure)机制,在运行时泛型类型信息被擦除,只保留原始类型。因此,你不能通过反射获取泛型的实际类型。
public class Box<T> {
// 在运行时 T 会被擦除成 Object
}
注:Java 泛型中,?
(通配符)和字母(如 T
、E
、K
、V
等)的泛型类型参数的确都能够表示某种类型,但它们的使用场景和目的有很大的区别。简而言之,字母泛型通常是在定义类、方法或者接口时用于表示某个类型,而通配符 ?
则是在使用这些类、方法时表示任意类型,并且具有更强的灵活性。
5. 总结
- 字母泛型:用于定义类或方法时,表示一个占位符类型。类型在使用时确定,编译器会进行类型检查。
- 通配符
?
:用于方法参数中,表示一个未知类型。在方法内部,?
提供了更灵活的类型接受能力,同时通过? extends T
和? super T
可以限定操作范围(如只允许读取、只允许写入)。 - 通配符增加了代码的灵活性,尤其在处理泛型集合时,避免了类型的严格限制,使得方法能够接受多种类型的数据。