泛型的定义:
泛型的本质就是参数化类型的应用,也就是说所有操作的数据类型都被指定为一个参数,在用到的时候才指定具体的类型。这种参数类型可以用在类,接口和方法中创建,分别称为泛型类,泛型接口和泛型方法。
泛型的作用
- 类型安全
泛型的主要目的是提到Java程序的安全,在编译时期强制类型检查,通过知道使用泛型泛型定义的变量限制,编译器可以在一个高得多得程度上验证类型假设。
- 消除强制类型转换
泛型得作用之一就是消除代码中得强制类型转换,减少出错得机会。
- 潜在得性能收益
泛型为较大得优化带了可能,编译器将强制类型转换,插入到字节码文件中,所有得工作都在编译期间完成。
- 泛型提高代码得复用性
可以通过泛型提高复用性,不需要写大量重复的代码。
泛型使用
泛型类
泛型类型用于定义类,称为泛型类,常见的泛型类有List,set,map。泛型类的定义格式:
class 类名称<泛型标识符:可以是任意字母>{}
例子:
//泛型类
/**
* 在定义泛型类的时候,一定要指明泛型表示
* 而我类中如果使用到该泛型,一定要用与类定义的泛型标识。
*
* @param <T>
*/
class Generic<T>{
//T类型跟类的是一样的,由外部指定
public Generic(T value) {
print(value);
}
//泛型类的一个方法,但不是泛型方法,只不过使用了T这个泛型做为形参而已。
public void print(T value) {
System.out.println(value.toString());
}
}
使用:
Generic<String> strGerneric=new Generic<>("字符类型");
Generic<Integer> intGerneric=new Generic<>(111);
结果:

注意:
泛型参数只能是类类型,不能是简单类型
不能对确切的泛型使用instanceof操作。
泛型接口
定义:
//泛型接口
/**
*和泛型类的定义要求类似,根式类似,可以指定任意字母当标识符
* @param <I>
*/
interface GenericInterface<I> {
void print(I value);
}`
在继承该接口的时候可以直接指定具体的类型,不也可以通过泛型类的形式使用。
class Test<I> implements GenericInterface<I>{
@Override
public void print(I value) {
}
}
class GenericTest implements GenericInterface<Integer>{
@Override
public void print(Integer value) {
}
}
泛型方法
泛型方法在定义的时候要注意格式,在定义的时候在方法类型返回的返回签名加上<标识符>
class GenericMethod<T> {
/**
* 泛型方法,虽然泛型的标识符与泛型类的相同都是T,但是两者却是不同
* 泛型方法中需要定义泛型标识,例如<T>
* @param value
* @return
*/
public <T> T method1(T value) {
return value;
}
public <M> void method2(M value) {
}
/**
* 这个不是泛型方法,只是利用泛型做为形参
* @param value
*/
public void notMethod(T value) {}
//也可以定义一个泛型方法与可变参数,例如使用的时候可以 genericMethod.changeParamsMethod(1,2,3);
public <T> void changeParamsMethod(T... valuse) {
for(T t:valuse) {
System.out.println(t);
}
}
/**
* 在静态方法中,是使用不了泛型类定义的标识的,例如下面是会报错
*/
/**
* public static void staticMethod(T value) {}
* 因为静态方法是不持有类的引用,是访问不了类非静态的方法,变量等。因此我们必须用泛型方法
*/
public static <T> void staticMethod(T value) {
}
}
泛型通配符
Integer是Number的子类,那么我们下面的那部分的代码会不会有问题呢?

可以看到上面的方法已经报错了,所明是不行的,通过提示信息我们可以看到Generic<Integer>不能被看作为`Generic<Number>的子类。由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的,那么如何解决呢?首先我们可以通过泛型方法来解决上面的问题,还有就是通过泛型通配符。
public static void main(String[] args) {
Generic<Integer> genericInteger=new Generic<>(1);
Generic<Number> genericNumber=new Generic<>(1);
showKey(genericInteger);
showKey(genericNumber);
showKeyWildcard(genericInteger);
showKeyWildcard(genericNumber);
}
/**
* 泛型方法
* @param nGeneric
*/
private static <T> void showKey(Generic<T> nGeneric) {
System.out.println(nGeneric.hashCode());
}
/**
* 泛型通配符
* @param value
*/
private static void showKeyWildcard(Generic<?> value) {
System.out.println(value.hashCode());
}
泛型通配符的类型
- 无边界通配符:<?>
使用外边界的通配符可以让泛型接受任意类型的数据
- 上边界通配符:<? extends 具体类型>
使用固定上边届的通配符的泛型可以接收的是指定类型及其子类类型的数据。
- 下边界通配符:<? super 具体类型>
使用固定下边界的通配符的泛型可以接收指定类型及其所有超类类型的数据。
泛型的擦除
Java的泛型是伪泛型,泛型基本导航都是在编译器这个层次来实现的,生成的Java类型字节码中是不包含泛型的类型。使用泛型的时候加上的泛型参数,会在编译器在编译的时候去掉,这个过程就是泛型擦除。具体参考泛型的擦除