本文并不是要从头开始介绍泛型,只要是对Java稍有了解的人大概都不会对泛型感到陌生,如果对泛型还完全没有过了解,可以参考这几篇文章。
Java泛型就是这么简单
Java帝国之泛型
Java官方教程-通配符
也可以参考下我自己写的
Java基础 - 没明白过的泛型(上)
Java基础 - 没明白过的泛型(中)
而本文主要是小结一下学习泛型时遇到的一些疑惑。
基本上只要是扯到通配符的都没搞清楚过,比如说:
? extends xxx
)和下界通配符(? super xxx
)又是干什么用的?T extends xxx
和 ? extends xxx
又有什么区别?? super T
又是啥?文章顺序和问题顺序并非一致
在讲通配符之前,有几个概念和知识点还是得先搞清楚,后面说起来方便些:
ArrayList<E>
中的E
称为类型参数变量
ArrayList<Integer>
中的Integer
称为实际类型参数
整个称为ArrayList<E>
泛型类型
整个ArrayList<Integer>
称为参数化的类型ParameterizedType
-- java3y《泛型就是这么简单》
当我需要一个父类的时候,我都可以用一个子类来代替它
List<Parent>
中插入一个Child对象以下是我对通配符的一些理解:
Child
是Parent
的子类,那么List<Child>
也应该是List<Parent>
的子类。但是泛型是不支持的,至于原因,可参见上面提到的Java帝国之泛型word is cheap , show me the code
还是拿比较经典的Fruit List来举例子
现在有下面三个类
Fruit
Apple extends Fruit
Orange extends Fruit
现在我想打印一个一个List,List中可以装Fruit或是Fruit的子类,其他的都不行,那么我们写成这样行不行呢?
public static void print(List<Fruit> list) {
...
}
这样肯定不行,我们上面说过,泛型不是协变的,你这样只能传List<Fruit>
而不能传List<Apple>
了。
public static <T extends Fruit> void printByGenericParam(List<T> list) {
for (T fruit : list) {
System.out.println(fruit);
}
}
public static void print(List<? extends Fruit> list) {
for (Fruit fruit : list) {
System.out.println(fruit);
}
}
这里可以看到无论是类型参数变量还是通配符都可以达到我们想要的效果。
但是通配符无须在参数上进行任何声明(比如<T>
),它只是在参数上进行了规定,使你可以传递Fruit的子类。
你甚至可以理解成下面这样:
类型参数变量T:
T -> T extends Fruit // 对T进行了限制,缩小了T的范围
通配符:
Fruit -> ? extends Fruit // 对参数Fruit进行了规定,允许你传递Fruit的子类,扩大了Fruit的范围
不同点:
虽然他们都可以完成功能,但其中还是稍微有一些差异。
泛型T: 例如传一个Apple,那么它的类型就是Apple
通配符:例如传一个Apple,它的声明类型实际上是一个Fruit,使用了多态才能调用Apple的toString()方法。
?相对于T来说有很多的限制,比如说:
但是通配符也有类型参数做不到的:
这个语法其实也很好的证明了类型参数和通配符的不同之处,以 ? super T
为例:
其中这个T
就意味着你将来会传进来一个具体的参数,例如你传进来一个Apple,那么这个式子就变成了? super Apple
,而通配符的作用就是扩大了Apple
的范围,把它变成了Apple
或者其父类
例如我们现在有一个求最小值函数。
sort
方法需要 T 实现Comparable
接口,所以在泛型参数中需要添加T extends Comparable<T>
public static <T extends Comparable<T>> T min(List<T> list) {
Collections.sort(list);
return list.get(0);
}
现在这个代码要求我们传进去的T都实现Comparable接口。
例如传Apple进去,Apple就必须实现Comparable。
但现在如果有这样一个需求。
Fruit 实现了Comparable接口
Apple和Orange都没有直接实现Comparable接口,即没有实现Comparable<Apple> 或者是 Comparable<Orange>接口
现在我希望Apple和Orange统一的使用Fruit的compareTo方法
此时类型参数似乎就搞不定了,此时就到了通配符上场了:
Comparable<T>
变成
Comparable<? super T>
public static <T extends Comparable<? super T>> T min(List<T> list) {
Collections.sort(list);
return list.get(0);
}
public static void main(String[] args) {
List<Apple> appleList = new ArrayList<>();
appleList.add(new Apple());
min(appleList);
}
? extends/super 在前面已经讲过了,但是当它和集合一起使用的时候还有一些限制。下面我们都是使用List作为例子。
PECS:Producer extends Consumer super (生产者使用extends,consumer使用super)
List<? extends T>
只能往外面拿,不能往里面写List<? super T>
只能往里面写,不能往外面拿List<? extends Fruit>
public void print(List<? extends Fruit> list) {
...
}
那么你的集合里面可能装的是 Apple,Orange,Fruit
Fruit
list -> Apple
Orange
List<Apple>
,那你还能往里面随便丢个Orang
e进去吗?Fruit
,无论你取出来的是Apple
还是Orange
,根据向上转型,你都可以把它当成是Fruit
,但你无法确定它具体是哪一种类型,所以被取出来的只能当做Fruit处理List<? super Fruit>
如果上面能够理解,那么这个也就很好理解了。
public void add(List<? super Fruit> list){
...
}
那么你的集合里面可能装的是 Apple,Orange,Fruit
Fruit
list -> Food
Obejct
List<Fruit>
,还是List<Food>
,我扔个Apple
进去总没问题吧List<Food>
,我总不能强转成Fruit
吧这个就比较让人头疼了,我们前面说通配符的作用是对参数进行规定,使得它可以传递其子类或者父类从而解决泛型无法协变的问题。
但是你一个?
却什么都不加你是想干嘛,我为什么不直接使用Class
而非要使用Class<?>
呢?
实际上它俩在功能上也确实没有什么区别,只是不使用通配符会有警告
Class clazz1 = String.class; // 会报rowTypes的warning警告
Class<?> clazz2 = String.class;
因为Class<T>
在定义的时候使用了泛型,所以如果什么都不传,它会认为你使用了没有类型的Class
,就好比你使用了一个无类型的List
List list = new ArrayList();
这里就直接套用Java官方教程中的话了。
The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype.
Class<?>
的例子)Object
的getClass
方法。由泛型协变问题导致的类型转换问题(即Java数组协变会带来的问题)
PECS
原文:https://www.cnblogs.com/bax-life/p/14629985.html