泛型(Generics)是一种允许在类、接口和方法中使用类型参数的机制,从而在编译时保证类型安全,并提高代码的复用性。泛型提供了一种解决类型不安全问题的方法,并使代码更具可读性和可维护性。
一.泛型类
定义格式
- 一个泛型类的定义包括类型参数。类型参数通常用尖括号(
<>
)括起来,并放在类名后面。可以使用一个或多个类型参数。
public class 方法名<泛型类型,.....> {}
注意事项
-
1.泛型的默认类型是Object,意味着没有加泛型就可以存储任意类型的数据
-
2.泛型只能编写引用数据类型(包装类在引用数据类型里面),不能用于基本数据类型(int, double, char…)
泛型类型标识符
-
常见的有:
E
、T
、K
、V
-
它们不是 Java 语言的专有名词,但它们在泛型编程中有一些约定俗成的含义:
E: Element(元素),通常用于集合类中,例如 List<E>、Set<E>。 T: Type(类型),一般用于通用的类型参数,例如 Box<T>。 K: Key(键),用于表示键的类型,通常出现在映射(Map)相关的泛型定义中,例如 Map<K, V> V: Value(值),用于表示值的类型,也出现在映射(Map)相关的泛型定义中,例如 Map<K, V>
-
具体的类型什么时候确认:
-
创建对象的时候确定到具体的类型,例如:
//创建Student类 class Student<E>{ // <E> : 给自己写的类加上了泛型 private E e; public E getE() { return e; } public void setE(E e) { this.e = e; } }
-
当Student 类实例化时没有明确指定其泛型类型时,默认认为所有的数据类型都是Object类型,这时候可以添加任何类型的数据
- 当Student 类实例化时指定泛型类型为String时,只能往里面添加String类型的数据
-
二.泛型方法
泛型方法可以分为两种:1.非静态的方法 2.静态的方法
1.非静态的方法
public class test<E> {
public static void main(String[] args) {
test<Integer> ex1 = new test(); //泛型参数 E 被指定为 Integer
ex1.printElement(111); // E 被推断为 Integer
test<String> ex2 = new test<>(); //泛型参数 E 被指定为 String
ex2.printElement("张三"); // E 被推断为 String
}
// 泛型方法定义
public void printElement(E e) {
System.out.println(e);
}
}
-
当text类被实例化时,泛型参数 E 被指定为 Integer时,只能使用Integer类型的数据
-
当text类被实例化时,泛型参数 E 被指定为 String时,只能使用String类型的数据
结论:非静态的方法内部的泛型,会根据类的泛型去匹配
2.静态的方法
public class GenericsDemo03{
public static void main(String[] args) {
String[] arr1 = {"张三","李四","王五"};
Integer[] arr2 = {11,22,33};
Double[] arr3 = {11.1,22.2,33.3};
printArray(arr1);
printArray(arr2);
printArray(arr3);
}
//这个类型 T 在调用方法时由传递的数组类型确定
public static<T> void printArray(T[] arr){
System.out.print("[");
for (int i = 0; i < arr.length-1; i++) {
System.out.print(arr[i] + ",");
}
System.out.println(arr[arr.length-1] + "]");
}
}
- 当往泛型方法中传入参数时,会根据传入的参数类确定确定具体的类型。下面arr1,arr2,arr3就分别对应了String类型,Integer类型,Double类型。
结论:静态方法中如果加入了泛型,必须申明出自己独立的泛型。在调用方法,传入实际参数的时候,确定到具体的类型
三.泛型接口
泛型接口有两种方式确定自己的具体类型:
-
1.实现类实现接口的时候确定到具体的类型
-
2.实现类实现接口,没有指定具体类型,就让接口的泛型,跟着类的泛型去匹配
1.实现类实现接口的时候确定到具体的类型
-
具体代码如下:
public class test { public static void main(String[] args) { } } //自定义接口 interface inter<E> { //接口中的抽象方法,方法的形参类型没想好,可以加一个泛型,然后在接口上也加泛型 void show(E e); } //1:实现类,实现接口的时候确定到具体的类型 class InterAImpl implements inter<String>{ @Override public void show(String s) { } }
2.实现类实现接口,没有指定具体类型,就让接口的泛型,跟着类的泛型去匹配
-
具体代码如下:
public class test { public static void main(String[] args) { } } interface inter<E> { //接口中的抽象方法,方法的形参类型没想好,可以加一个泛型,然后在接口上也加泛型 void show(E e); } //2.实现类实现接口,没有指定具体类型,就让接口的泛型,跟着类的泛型去匹配 class InterBImpl<E> implements inter<E>{ //实现类实现接口的时候,还没想好类型,还可以继续申明泛型 @Override public void show(E e) { } }
-
当text类被实例化时,泛型参数 E 被指定为 String时,只能使用String类型的数据
-
当text类被实例化时,泛型参数 E 被指定为 Integer时,只能使用Integer类型的数据
-
四.通配符(Wildcard)
-
无界通配符(?):表示任何类型
- 例如:
// 可以是任何类型的 List List<?> list = new ArrayList<String>();
- 例如:
-
上界通配符(? extends T):表示匹配 T 类型或其子类型
- 例如:
// 可以是 List<Number> 或 List<Integer> List<? extends Number> list = new ArrayList<Integer>();
- 例如:
-
下界通配符(? super T):表示匹配 T 类型或其父类型
- 例如:
// 可以是 List<Number> 或 List<Object> List<? super Integer> list = new ArrayList<Number>();
- 例如:
五.泛型的限制
-
不能创建泛型数组:由于泛型类型在运行时被擦除,不能直接创建泛型数组。例如,
new T[10]
是不允许的。 -
不能使用基本数据类型:泛型不能用于基本数据类型,如
int
、char
等。需要使用对应的包装类,如Integer
、Character
。 -
不能实例化泛型类型:不能直接实例化泛型类型,例如
new T()
是不允许的。可以通过传递类类型或使用反射来解决这个问题。 -
类型擦除:泛型类型在编译时会被擦除为其边界类型(通常是
Object
),这意味着在运行时泛型类型的信息丢失。 -
静态上下文限制:泛型类型不能用于静态字段或静态方法中,因为这些静态成员不依赖于具体的泛型类型。
六.类型擦除
基本概念:
Java中的泛型是通过类型擦除实现的。类型擦除是指在编译时,泛型类型信息会被擦除,转化为其边界类型或 Object
。这意味着在运行时,泛型信息不可用。这种设计使得泛型与Java的旧代码兼容,但也带来了一些限制,如无法在运行时获取泛型参数的实际类型。
如何工作
1.类型擦除:
-
泛型类型
T
会被擦除为其上限类型。例如,List<String>
和List<Integer>
都会被擦除为List
。 -
如果泛型有一个上限(如
T extends Number
),T
会被擦除为这个上限(即Number
)。
2.泛型方法和类型参数:
- 泛型方法的类型参数在方法内部使用,但在编译后会被擦除。例如,
public <T> void method(T param)
经过编译后,T
会被替换为Object
(如果没有明确的上限)。
3.实例化泛型类型:
- 由于类型擦除,不能直接使用泛型类型进行实例化,如
new T()
。泛型类型的创建需要通过反射来完成。
4.类型检查:
- 泛型擦除确保了在运行时泛型类型的类型安全性,通过编译器的检查来避免类型错误。
示例代码:
-
编译前:
public class Box<T> { private T value; public void setValue(T value) { this.value = value; } public T getValue() { return value; } }
-
编译后:
public class Box { private Object value; //泛型类型参数 T 被擦除为 Object public Box() { } public void setValue(Object value) { //泛型类型参数 T 被擦除为 Object this.value = value; } public Object getValue() { //泛型类型参数 T 被擦除为 Object return value; } }