[疯狂Java]泛型:泛型构造器、泛型方法的重载问题、泛型数组的问题(应该摒弃)、?下限的典型应用

时间:2021-09-10 17:34:42

1. 泛型构造器:

    1) 构造器也是方法,既然有泛型方法,那么构造器也可以定义为泛型方法,那么就变成了泛型构造器;

    2) 由于构造器也是方法,因此反省构造器的定义和普通泛型方法的定义完全相同,例如:public <T> MyClass(T t) { ... }

    3) 使用泛型构造器:

          i. 和使用普通泛型方法一样没区别,一种是显式指定泛型参数,另一种是隐式推断,如果是显式指定则以显式指定的类型参数为准,如果传入的参数的类型和指定的类型实参不符,将会编译报错;

!典型示例:A a = new <String>A("lala"); // 菱形实参还是写在方法名之前

          ii. 这里唯一需要特殊注明的就是,如果构造器是泛型构造器,同时该类也是一个泛型类的情况下应该如何使用泛型构造器:

              a. 因为泛型构造器可以显式指定自己的类型参数(需要用到菱形,放在构造器之前),而泛型类自己的类型实参也需要指定(菱形放在构造器之后),这就同时出现了两个菱形了,这就会有一些小问题,具体用法再这里总结一下;

              b. 这里使用的类是这样定义的:public A<T> { public <E> A(E e) { ... } }

              b. 全指定,例如:A<String> a = new <Integer>A<String>(15); // 所有类型实参全部显式指定,Integer代表E,String代表T

              c. 全隐藏,例如:A<String> a = new A<>(15);   // 可以隐式推断出Integer是E,String是T

              d. 半隐藏,例如:A<String> a = new A<String>(15);  // 还是可以推断出E是Integer,而String是T

              e. 上面的叫做半隐藏T,但是不能半隐藏E,例如:A<String> a = new <Integer>A<>(15);  // 虽然也可以正确推断,但是这种语法不允许!!会直接编译报错!

!!因此这里麻烦的是需要稍微记忆一下,不能半隐藏E

!!平时使用的时候就使用其中一种,推荐是全隐藏,越简洁越好,就是用一种就不会增加记忆负担;


2. 泛型方法的重载问题:

    1) 泛型方法的定义非常灵活,只要形式不同就能形成重载,例如List<T>、List<T extends Xxx>、List<? super Xxx>等都会在重载时当成不一样的类型,因此都能形成重载;

    2) 但是重载泛型方法的时候不能产生歧义,如果产生歧义即使编译没错最后运行时也可能发生错误!

    3) 典型例子:

public static <T> void copy(Collection<T> dest, Collection<? extends T> src);
public static <T> void copy(Collection<? super T> dest, Collection<T> src);
!这两个方法形成重载,并且编译没有问题;

!但是如果在运行时这样调用:copy(new ArrayList<Number> ln, new ArrayList<Integer> li);  // 由于上面两个重载版本都符合,因此不知道到底应该调用该哪个方法,因此产生了歧义,随之而来的就是抛出异常!!

!!所以千万不要做这种模棱两可的事情;

3. 泛型数组的问题:这里不解释具体原理了,反正也没人用,所以就杜绝使用泛型数组

    1) Java严格地来说不支持泛型数组,像List<String>[] arr = new List<String>[10]; // 直接编译报错

    2) 其次,用类型参数创建数组也不支持,例如在一个泛型类中,出现这么一句话:new T[15];  // 也会直接编译报错

    3) 因此一定要小心这两种情况,也就是说编程的时候一定要杜绝上述两种情况!


4. ?下限的典型应用:

    1) 首先介绍一个简单的例子:就拿Collections的API(copy方法)来说吧:

public static <T> void copy(Collection<T> dest, Collection<? extends T> src) {
for (T ele: src) {
dest.add(ele);
}
}
!没有问题,类型兼容;

!!但如果现在要求该方法返回最后一个被复制的元素呢?可能你会这样改:

public static <T> T copy(Collection<T> dest, Collection<? extends T> src) {
T last = null;
for (T ele: src) {
last = ele;
dest.add(ele);
}
return last;
}
!!但是这样又会让src丢失类型信息:Integer last = copy(new ArrayList<Number>(), new ArrayList<Integer>());  // 由于返回值类型就是src的类型上限Number,而Number到Integer并不兼容,因此这里会抛出类型转换异常;

!!除非你对返回值进行强制类型转换,但是既然用了泛型还要那么麻烦地转换类型那不是很吃亏吗?

!!随意这里用?的上限来解决:

public static <T> T copy(Collection<? super T> dest, Collection<T> src) {
T last = null;
for (T ele: src) {
last = ele;
dest.add(ele);
}
return last;
}
!!这样src的类型就是确定的类型了,因此last的类型也是确定了类型,就是和src的类型一样!