泛型(Generics)是Java中的一种特性,它允许在编写类和方法时使用类型参数,以在使用时指定具体的类型。泛型提供了编译时类型安全性,并且可以增加代码的重用性和可读性。
使用泛型的主要目的是在编译时捕获类型错误,并提供更好的类型检查和类型推断。通过使用泛型,可以在编译时检查代码中的类型错误,避免在运行时出现类型转换错误或其他类型相关的异常。
泛型(拆字为广泛的数据类型,也叫类型参数)即将类型进行一个参数化。
注意:
这个参数是Java中的类型但不包括基本数据类型
泛型类中的泛型不能应用到静态方法上面
泛型类(Generic Class):可以在类的定义中使用类型参数,并在类的实例化时指定具体的类型。例如:
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
在上述示例中,Box
是一个泛型类,T
是类型参数。在实例化时,可以指定具体的类型,例如Box
、Box
等。
类型参数可以有多个,若在创建泛型类对象时没有传入参数,那么会默认编译为Object类型。
与泛型类基本相同。
泛型方法(Generic Method):可以在方法的定义中使用类型参数,并在方法的调用时指定具体的类型。例如:
public <T> T getValue(T[] array, int index) {
return array[index];
}
在上述示例中,
是一个类型参数,用于指定方法的返回类型。在调用方法时,可以指定具体的类型,例如getValue(array, 0)
。如果没有指明,则会将T设置为类型参数共同的超类。
通配符(Wildcard):可以使用通配符来表示未知类型或类型的子类。例如:
public void processList(List<?> list) {
for (Object element : list) {
// 对列表中的元素进行处理
}
}
在上述示例中,List>
表示一个未知类型的列表。这样可以接受任何类型的列表作为参数,但不能对列表中的元素做具体的类型假设。
在Java中,使用通配符(?
)作为泛型类型参数,可以创建一个未知元素类型的List。在这种情况下,编译器无法确定List中元素的具体类型,只知道它是某种类型的List。
public static void test() {
List<?> c = new ArrayList<String>();
c.add(null);
}
在这个例子中,我们创建了一个未知元素类型的List,命名为c3
,并使用ArrayList
来实例化它。由于c3
的元素类型未知,我们不能直接添加具体的元素对象,但是我们可以添加null
作为通配符类型的元素。
c3.add(null)
语句可以编译通过,因为null
可以被赋值给任何引用类型,包括通配符类型。这样,null
被添加到了c3
列表中。
限定类型参数(Bounded Type Parameters):可以使用限定类型参数来限制泛型类型的范围。例如:
public <T extends Number> double sum(T[] array) {
double total = 0;
for (T element : array) {
total += element.doubleValue();
}
return total;
}
在上述示例中,
表示类型参数T
必须是Number
类或其子类。这样可以确保在方法中调用Number
类的方法,如doubleValue()
。
类型通配符上限通过形如List extends Number> 来定义,表示只接受Number及其下层子类类型。
实例
public class GenericTest {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("icon");
age.add(18);
number.add(314);
//getUperNumber(name);//1
getUperNumber(age);//2
getUperNumber(number);//3
}
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
public static void getUperNumber(List<? extends Number> data) {
System.out.println("data :" + data.get(0));
}
}
输出结果:
data :18
data :314
解析: 在 //1 处会出现错误,因为 getUperNumber() 方法中的参数已经限定了参数泛型上限为 Number,所以泛型为 String 是不在这个范围之内,所以会报错。
类型通配符下限通过形如 List super Number> 来定义,表示类型只能接受 Number 及其上层父类类型,如 Object 类型的实例。
注意:规范上来说,上界通配符只读不写,下界通配符只写不读,原因在于,这里说的上界下界其实就是界限的意思。对于上界通配符,规定了上限,若向其写入null会报错;对于下界通配符,当使用get()获取返回值时,返回值类型为Object。
泛型擦除是指泛型类型参数在编译时被擦除的过程。在Java中,泛型类型参数只存在于代码编译阶段,而在运行时它们被擦除并替换为它们的原始类型或限定类型。
泛型擦除是为了保持与Java早期版本的二进制兼容性和互操作性。由于Java的泛型是在Java 5中引入的,为了确保现有的非泛型代码可以与新的泛型代码一起使用,Java编译器在编译过程中会将泛型类型信息擦除。
例如:
public class MyGenericClass<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
在编译过程中,编译器会将T
擦除为其原始类型Object
,因此编译后的代码将类似于以下形式:
public class MyGenericClass {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
这意味着在运行时,无法访问或使用泛型类型参数的具体类型信息。这种擦除限制了一些操作,例如无法在运行时创建泛型类型的实例,因为在运行时无法获得泛型类型参数的具体类型。
虽然泛型类型参数在运行时被擦除,但在编译过程中仍然进行了类型检查,以确保类型安全性。编译器在编译时会插入必要的类型转换代码,以确保在运行时不会出现类型错误。