一、泛型类
(1)泛型类的定义语法
class 类名称 <泛型标识,泛型标识,...> {
private 泛型标识 变量名;
......
}
(2)常用的泛型标识:T、E、K、V
/**
* @author xujin
* @createtime 2021-02-14 14:29
* @description
* 泛型类的定义
* <T>泛型标识-类型参数
* 创建对象的时候指定具体的数据类型
* 注意:
* (1)泛型类在创建对象的时候,没有指定类型,将按照Object类型来操作
* (2)泛型类的类型参数只能是类类型,不能是基本数据类型
* (3)泛型类型在逻辑上可以看成是多个不同的类型,但其Class类型实际上都是相同类类型
*/
public class Generic<T> {
private T value;
public Generic(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
二、泛型类派生子类
(1)子类也是泛型类,子类和父类的泛型类型要一致
class ChildGeneric<T> extends Generic<T>
public class Parent<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
如果不声明父类的泛型,那么父类默认就是Object类型,如下:
public class ChildFirst<T> extends Parent {
@Override
public Object getValue() {
return super.getValue();
}
}
这是由于如果子类的泛型类型和父类的泛型类型不同的话,在调用子类的构造方法的时候就会报错,因为调用子类的构造方法里面会调用父类的构造方法,正确如下:
public class ChildFirst<T> extends Parent<T> {
@Override
public T getValue() {
return super.getValue();
}
}
也可以进行如下写法,但是要保证子类的泛型列表中要有一个泛型类型和父类一致,如下:
public class ChildFirst<T,E,K,V> extends Parent<T> {
@Override
public T getValue() {
return super.getValue();
}
}
(2)子类不是泛型类,父类要明确泛型的数据类型
class ChildGeneric extends Generic<String>
public class ChildSecond extends Parent<Integer> {
@Override
public Integer getValue() {
return super.getValue();
}
}
三、泛型接口
(1)泛型接口的定义语法
interface 接口名称<泛型标识,泛型标识,...> {
泛型标识 方法名();
......
}
(2)实现类不是泛型类,接口要明确数据类型
(3)实现类也是泛型类,实现类和接口的泛型类型要一致。
四、泛型方法
(1)语法
修饰符 <T,E,...> 返回值类型 方法名(形参数列表){
方法体...
}
(2)public与返回值中间的泛型列表非常重要,可以理解为声明此方法为泛型方法
(3)只有声明了泛型列表的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
(4)泛型列表表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T
(5)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
public class GenericMethod {
public static <E> E genericMethod(E value) {
return value;
}
}
(2)泛型方法与可变参数
public <E> void print(E... e) {
for(E e1:e) {
System.out.println(e);
}
}
public static <E> void print(E... e) {
for (int i = 0; i < e.length; i++) {
System.out.println(e[i]);
}
}
(3)泛型方法总结
1)泛型方法能使方法独立与类而产生变化
2)如果static方法要使用泛型能力,就必须使使其成为泛型方法
五、类型通配符
(1)什么是类型通配符
1)类型通配符一般是使用“?”代替具体的类型实参
2)所以,类型通配符是类型实参。而不是类型形参
public class Box<E> {
private E value;
public E getValue() {
return value;
}
public void setValue(E value) {
this.value = value;
}
public static void showBox(Box<?> box) {
Object value = box.getValue();
}
}
(2)类型通配符的上限
语法:
类/接口<? extends 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的子类类型
public static void showBox(Box<? extends Number> box) {
Number value = box.getValue();
}
有类型通配符上限的集合是不能添加元素的
(3)类型通配符的下限
语法:
类/接口<? super 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的父类类型
public static void showBox(Box<? super Number> box) {
Object value = box.getValue();
}
而有类型通配符下限的集合是能添加元素的,但不能保证数据的正确性
六、类型擦除
泛型是Java1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,于泛型相关的信息会被擦除掉,我们称为--类型擦除
(1)无限制类型擦除
public class Erasure<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
泛型发生在生产字节码文件的时候,我们用反射来看一下字节码文件中value是什么类型的:
public class Test {
public static void main(String[] args) {
Erasure<Integer> erasure = new Erasure<>();
Class<? extends Erasure> erasureClass = erasure.getClass();
Field[] declaredFields = erasureClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName()+":"+declaredField.getType().getSimpleName());
}
}
}
运行结果如下:
(2)有限制的类型擦除
public class Erasure<T extends Number> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
运行结果:
七、泛型与数组
(1)可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象,这是因为泛型是在编译器做类型擦除,而数组会在整个编译器去持有相应的数据类型:
不过可以这样创建:
(2)可以通过java.lang.reflect.Array的newInstance(Class, int)创建(泛型)T[]数组
public class Fruit<T> {
private T[] array;
public Fruit(Class<T> clz, int size) {
array = (T[]) Array.newInstance(clz, size);
}
}