一、泛型的概念

    泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

    C++中国实现泛型是使用模板类和模板方法,Java是从JDK1.5实现泛型的,下面我们来看看Java中泛型相关的知识点。

二、定义泛型类
public class Generic<T> {

    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }
}

实例化泛型类时,如下:

Generic<String> genericString = new Generic<>("abc");
Generic<Integer> genericInteger = new Generic<>(123);
System.out.println(genericString.getKey());
System.out.println(genericInteger.getKey());


需要注意的是泛型的类型参数必须是引用类型(类、接口、数组等都是引用类型)而不能是简单类型,如Generic<int> generic = new Generic<>(123);是不允许的。

当然和 List 等一样,实例化泛型类可以传入任意类型而并不一定非要传入泛型类实参,只不过既然我们将类定义为泛型类,其目的就是希望开发者们能够传入确定的类型实参,以增加程序健壮性:



Generic generic = new Generic("abc");
Generic generic2 = new Generic(123);
Generic generic3 = new Generic(true);
三、定义泛型接口
public interface Generator<T> {
    public T fun();
}

实现泛型接口

public class PersonGenerator<T> implements Generator<T> {
    @Override
    public T fun() {
        return null;
    }
}


如果实现泛型接口时传入了泛型实参,则该类中所有使用泛型的地方都要替换成传入的泛型实参:

public class PersonGenerator implements Generator<String> {
    @Override
    public String fun() {
        return null;
    }
}
四、泛型方法



为了判断数组中是否包含某值写了如下两个重载方法:

public static void main(String[] args) {
    Integer[] integers = new Integer[]{1, 2, 3};
    String[] strings = new String[]{"a", "b", "c"};
    System.out.println(contains(2, integers));
    System.out.println(contains("b", strings));
}
public static boolean contains(Integer integer, Integer[] integers) {
    return Arrays.asList(integers).contains(integer);
}
public static boolean contains(String s, String[] strings) {
    return Arrays.asList(strings).contains(s);
}



但如果还想要判断 Float 类型的数组中是否包含某个值就有需要编写一个重载方法,好在我们可以通过泛型方法有效的避免这些冗余的方法:

public static void main(String[] args) {
    Integer[] integers = new Integer[]{1, 2, 3};
    String[] strings = new String[]{"a", "b", "c"};
    Float[] floats = new Float[]{0.1f, 0.2f, 0.3f};
    System.out.println(contains(2, integers));
    System.out.println(contains("b", strings));
    System.out.println(contains(0.2f, floats));
}
public static <T> boolean contains(T t, T[] ts) {
    return Arrays.asList(ts).contains(t);
}

需要注意的是方法返回值前需要包含形式参数,如<T>否则该方法不能被称为泛型方法,编译也将出错。
值得一提的是,如果同时保留以下两个方法:

public static <T> boolean contains(T t, T[] ts) {
    return Arrays.asList(ts).contains(t);
}
public static boolean contains(Integer integer, Integer[] integers) {
    return Arrays.asList(integers).contains(integer);
}

contains("b", strings)会自动匹配泛型方法,而contains(2, integers)匹配的是普通方法而不是泛型方法。



五、泛型通配符
5.1 无限定通配符-Unbounded Wildcard


泛型类 Generic<Number>

Generic<Integer>

可以认为是两个完全没有关联的新类型,两者之间不具有任何继承关系,所以下面的代码会出现编译错误:


public static void main(String[] args) {
    Generic<Number> genericNumber = new Generic<>(123);
    Generic<Integer> genericInteger = new Generic<>(123);
    printMsg(genericNumber);  // 编译通过
    printMsg(genericInteger); // 编译出错,因为 Generic<Integer> 和 Generic<Number> 二者之间没有任何继承关系
}
public static void printMsg(Generic<Number> generic) {
    System.out.println(generic.getKey());
}


而如果就是希望 printMsg 方法既能接收 Generic<Number>又能够接收 Generic<Integer>类型,甚至是能够接收传入了任意实参类型的 Generic泛型类(如 Generic<String>Generic<Random>等),则需要用到泛型通配符 ?了:



public static void main(String[] args) {
    Generic<Number> genericNumber = new Generic<>(123);
    Generic<Integer> genericInteger = new Generic<>(123);
    printMsg(genericNumber);  // 编译通过
    printMsg(genericInteger); // 编译通过
}
public static void printMsg(Generic<?> generic) {
    System.out.println(generic.getKey());
}


5.2 上限通配符-Upper Bounded Wildcard


为泛型添加上边界,即传入的类型实参必须是指定类型或指定类型的子类。使用

extends

指定上限通配符

public static void main(String[] args) {
    Generic<Number> genericNumber = new Generic<>(123);
    Generic<Integer> genericInteger = new Generic<>(123);
    Generic<Float> genericFloat = new Generic<>(0.5f);
    Generic<String> genericString = new Generic<>("Hello");
    printMsg(genericNumber);  // 编译通过
    printMsg(genericInteger); // 编译通过
    printMsg(genericFloat);   // 编译通过
    printMsg(genericString);  // 编译出错
}
public static void printMsg(Generic<? extends Number> generic) {
    System.out.println(generic.getKey());
}


因为

Generic<? extends Number> generic

指定了传入的类型实参必须是 Number 类或 Number 类的子类,所以

printMsg(genericString);

出错,因为 String 不是 Number 的子类

5.3 下限通配符-Lower Bounded Wildcard

和上限通配符类似,下限通配符使用super关键字实现:

public static void main(String[] args) {
    Generic<Number> genericNumber = new Generic<>(123);
    Generic<Integer> genericInteger = new Generic<>(123);
    Generic<Float> genericFloat = new Generic<>(0.5f);
    Generic<String> genericString = new Generic<>("Hello");
    printMsg(genericNumber);  // 编译通过
    printMsg(genericInteger); // 编译通过
    printMsg(genericFloat);   // 编译出错
    printMsg(genericString);  // 编译出错
}
public static void printMsg(Generic<? super Integer> generic) {
    System.out.println(generic.getKey());
}

因为Generic<? super Integer> generic指定了传入的类型实参必须是 Integer 类或 Integer 类的父类,所以printMsg(genericFloat);printMsg(genericString);出现编译错误,因为 Float 和 String 都不是 Integer 类的父类


六、类型擦除


Java 的泛型只在编译阶段有效,编译过程中正确检验泛型结果后,会将泛型相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法,即泛型信息不回进入运行时阶段:

public static void main(String[] args) {
    Generic<Integer> genericInteger = new Generic<>(123);
    Generic<String> genericString= new Generic<>("Hello");
    System.out.println(genericInteger.getClass() == genericString.getClass());  // 返回 true
}

结果返回true,说明虽然编译时Generic<Integer>Generic<String>是不同的类型,但因为泛型的类型擦除,所以编译后genericIntegergenericString为相同的类型


命名规则

JDK 中文档经常能看到TKVEN等类型参数,而我们在编写泛型相关代码时,这些符号都可以随意使用,实际上还可以使用 JDK 文档中从来没用过的符号,如ABC等,但却极力不推荐这样做

JDK 文档中各符号的含义:

  • T:类型
  • K:键
  • V:值
  • E:元素(如集合类中的元素全部用该符号表示)
  • N:Number



参考:


https://zhuanlan.zhihu.com/p/38008813