一、泛型的底层
泛型在Java中的底层实现是通过类型擦除(Type Erasure)来实现的。在编译时,所有的泛型类型信息都会被擦除,只保留原始类型的信息,并且将泛型类型参数替换成限定类型或者Object类型。
具体来说,Java编译器会在编译时将所有的泛型类型参数替换成其限定类型或者Object类型,然后在生成字节码文件时,使用这些类型参数的实际类型进行替换。这就意味着,在运行时,Java虚拟机并不知道原始的泛型类型参数的信息,而只知道实际的类型。
通过类型擦除实现泛型的好处是可以使Java的泛型代码与旧版本的代码兼容,并且可以在编译时进行类型检查,避免了类型转换的错误。同时,它也带来了一些限制,比如无法在运行时获取泛型类型的信息,不能使用基本类型作为泛型参数等。
二、什么是泛型,泛型的定义
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
简单点说普通的方法定义,已经明确确定了需要传入的参数类型,调用方法的时候,只能改变传入的参数值,不能改变传入的参数类型,否则编译时会报错。
使用泛型定义方法,需要传入的参数类型和参数值都是没有明确确定的,调用方法的时候,由调用者确定传入的参数类型和参数值。
参数化类型,不同类型的参数可以由同一段代码处理。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
三、举例说明
泛型是Java语言的一个特性,允许类、接口、方法在定义时使用一个或多个类型参数,从而在使用时才确定这些参数的具体类型。以下是一些使用泛型的示例:
- List集合:可以定义一个List集合,指定元素类型为String,代码如下:
List<String> list = new ArrayList<>();
这里的List就是一个泛型接口,可以在尖括号中指定元素类型,这样在添加和获取元素时就不需要进行类型转换了。
- map集合
Map<String, Integer> map = new HashMap<>();
这里的map也是一个泛型接口。
- 自定义泛型类:可以定义一个泛型类,用于存储一些数据,代码如下:
public class MyList<T> {
private T[] data;
private int size;
public MyList(int capacity) {
data = (T[])new Object[capacity];
size = 0;
}
public void add(T element) {
data[size++] = element;
}
public T get(int index) {
return data[index];
}
// ...
}
这里的MyList类是一个泛型类,用于存储一些数据,可以在创建对象时指定元素类型。
- 泛型方法:可以定义一个泛型方法,用于对一些数据进行处理,代码如下:
public <T> T max(T x, T y) {
if (x.compareTo(y) > 0) {
return x;
} else {
return y;
}
}
这里的max方法是一个泛型方法,可以处理任意类型的数据,返回值类型和参数类型都是泛型类型T。