1. 泛型
泛型是Java中的一种强大的特性,它允许在编写代码时使用参数化类型,使得代码更加通用、类型安全(不需要强制类型转换)和可重用。泛型的本质是为了参数化类型,也就是说在泛型的使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别称为泛型类、泛型接口、泛型方法。
2. 泛型的使用范围
2.1泛型类
泛型类是使用了泛型类型参数的类。通过泛型类,可以在类的定义中使用一个或多个类型参数,从而实现类的通用性和复用性。泛型类的定义方式为在类后面使用尖括号<>来声明类型参数,并在类的方法、字段或构造函数中使用这些类型参数。
以下是一个简单的泛型类的示例:
public class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public static void main(String[] args) {
Box<Integer> intBox = new Box<>(10);
Box<String> stringBox = new Box<>("Hello");
System.out.println("Integer value: " + intBox.getValue());
System.out.println("String value: " + stringBox.getValue());
}
}
在这个示例中,Box 类是一个泛型类,使用了类型参数 T。在创建 Box 对象时,可以指定具体的类型,比如 Box 和 Box,从而分别创建一个存储整数和字符串的 Box 对象。通过泛型类,可以实现对不同类型数据的存储和操作,同时保持类型安全性。
2.2泛型接口
泛型接口是使用了泛型类型参数的接口。通过泛型接口,可以在接口中定义一个或多个类型参数,使得接口中的方法或常量可以使用这些类型参数,从而实现接口的通用性和复用性。
以下是一个简单的泛型接口的示例:
interface Pair<K, V> {
K getKey();
V getValue();
}
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 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());
}
}
在这个示例中,Pair 是一个泛型接口,使用了两个类型参数 K 和 V,代表键和值的类型。OrderedPair 类实现了 Pair 接口,并通过泛型类型参数指定了键和值的类型。通过泛型接口,可以定义具有通用性的接口,并且在实现类中指定具体的类型,从而实现对不同类型数据的处理。
2.3泛型方法
泛型方法是定义泛型类型参数的方法。通过泛型方法,可以在方法的定义中使用一个或多个类型参数,从而实现方法的通用性和复用性。泛型方法可以在静态方法和非静态方法中使用,也可以在泛型类中定义。
以下是一个简单的泛型方法的示例:
public class GenericMethodExample {
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static void main(String[] args) {
GenericMethodExample example = new GenericMethodExample();
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"Hello", "World"};
example.printArray(intArray);
example.printArray(stringArray);
}
}
在这个示例中,printArray 方法是一个泛型方法,使用了类型参数 T。在调用 printArray 方法时,可以传入不同类型的数组,而不需要为每种类型编写不同的方法。这样可以提高代码的复用性和灵活性。
泛型方法还可以使用类型参数的上限和下限进行约束,以限制传入方法的类型。例如, 表示类型参数 T 必须是 Number 类型或其子类。
2. 泛型的上下限
类型参数的上限和下限可以通过使用extends和super关键字来进行约束。
- 上限通配符extends:指定类型参数必须是指定的类型或其子类。例如,‘’表示类型参数‘T’必须是‘Number’类型或其子类。
public static <T extends Number> double sumOfList(List<T> list) {
double sum = 0.0;
for (T element : list) {
sum += element.doubleValue();
}
return sum;
}
- 下限通配符super:指定类型参数必须是指定的类型或其父类。例如‘’表示参数类型‘T’必须是‘Integer’类型或其父类。
public static void addNumbers(List<? super Integer> list) {
list.add(10);
list.add(20);
}
4.Java泛型常用类型参数和通配符
常用类型参数:
- T : 表示一个具体的类型。
- E : 通常表示集合中的元素类型。
- K和V : 通常表示键值对中的Key和Value。
- S和U : 用于表示在泛型方法中的第二个和第三个类型
通配符
- ?:表示未知类型,通常和extends或extends关键字一起使用,表示类型的上下限。
5. 泛型中的类型擦除
Java中的泛型在编译时会进行类型擦除(Type Erasure)。类型擦除是指在编译过程中,将泛型类型信息擦除,替换为原始类型(raw type)或限定类型(bounded type).这样做是为了兼容Java早期版本的代码,并且使得泛型在运行时不会产生额外开销。
类型擦除会导致以下几个方面受影响:
-
泛型类型擦除为原始类型:泛型类、接口或方法中的类型参数如果没有指定上限或下限,那么在类型擦除时会擦除为原始类型。例如,List 中的 T 在类型擦除后会变成 List。
-
泛型类型擦除为桥接方法:如果泛型类或接口实现了泛型方法,且泛型方法在子类中被重写,那么编译器会生成桥接方法来保持类型安全。桥接方法会擦除类型参数,并在调用时使用类型转换。
public interface Pair<T> { T getFirst(); void setFirst(T first); } public class StringPair implements Pair<String> { private String first; public String getFirst() { return first; } public void setFirst(String first) { this.first = first; } }在StringPair类中,‘Pair’接口中的‘getFirst’方法被思想为返回‘String’类型的方法。在编译时,编译器会生成一个桥接方法来保持类型的安全,类似于以下的形式:
public class StringPair implements Pair<String> { private String first; public String getFirst() { return first; } public void setFirst(String first) { this.first = first; } // 桥接方法 public Object getFirst() { return getFirst(); // 调用实际的泛型方法 } }这样,即使在泛型类型被擦除为 Object 类型时,编译器仍然可以保证在运行时调用正确的方法,从而保持类型安全。
-
泛型类型擦除为Object或限定类型:如果泛型类型参数有上限或下限,那么在类型擦除时会擦除为其限定类型。例如, 中的 T 在类型擦除后会被擦除为 Number 类型。
public class Box<T extends Number> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } -
泛型数组创建受限制:由于类型擦除的影响,无法直接创建泛型数组。例如,'new T[]'是不合法的,需要使用 Array.newInstance() 方法来创建泛型数组。
public ArrayWithTypeToken(Class<T> type, int size) { array = (T[]) Array.newInstance(type, size); }
6.泛型对使用反射的影响
- 类型擦除:泛型在编译时会进行类型擦除,因此在运行时无法获取泛型类型的具体信息。例如,对于泛型类‘Box’,在运行时只能获取到‘Box’类的信息,无法获取到‘T’的具体信息
- 获取泛型信息: 通过反射可以获取泛型类、接口、方法等的信息,但无法直接获取泛型类型的具体信息。通常需要结合‘Type’接口及其子接口(如‘ParameterizedType’、TypeVariable 等)来获取泛型类型的具体信息。
- 泛型数组:由于类型擦除的影响,无法直接创建泛型数组。在使用反射时,如果需要创建泛型数组,通常需要使用
- 桥接方法: 当泛型类或接口中的方法使用了泛型类型参数,并且在子类中被重写时,编译器会生成桥接方法来保持类型安全。在使用反射获取方法时,需要注意桥接方法的存在。
- 泛型边界: 在使用反射获取泛型方法或泛型字段时,需要考虑泛型类型的边界。例如,对于 ,需要通过反射来确定 T 的具体边界。
简单来说,用泛型就尽量不要用反射了,很多限制
本文详细介绍了Java中的泛型,包括泛型的原理、泛型类、泛型接口和泛型方法的应用,以及类型擦除对反射的影响。强调了泛型在提高代码通用性和类型安全性的优势,同时指出使用反射时需注意泛型的限制。

3万+

被折叠的 条评论
为什么被折叠?



