泛型
在java中,像List, Map, Set等容易经常使用到,而且给我们编码带来了很大的便利,比如List<String> 我们就能够得到一个String的List容器,List<Integer> 就能够产生出一个Integer的List容器,这其中就用到了泛型。泛型就是参数化类型:
- 适用于多种数据类型执行相同的代码
- 泛型中的类型在使用时指定
- 泛型归根到底就是“模版”
优点:使用泛型时,在实际使用之前类型就已经确定了,不需要强制类型转换。
泛型既可以修饰类,也可以修饰方法和接口。这里举个例子。
public class Test1<T>{
public void testMethod(T t){
System.out.println(t.getClass().getName());
}
public <E> E testMethod1(E e){
return e;
}
public static void main(String[] args) {
Test1<String> t = new Test1();
t.testMethod("generic");
Integer i = t.testMethod1(new Integer(1));
}
}
上面代码中,Test1<T> 是泛型类,testMethod 是泛型类中的普通方法,而 testMethod1 是一个泛型方法。而泛型类中的类型参数与泛型方法中的类型参数是没有相应的联系的,泛型方法始终以自己定义的类型参数为准。
之前写过一篇C++的模板https://www.jianshu.com/writer#/notebooks/15612794/notes/16915027,其实是同种意思,这里不再赘述。
类型擦除
先来一道有意思的题
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
泛型是 Java 1.5 版本才引进的概念,在这之前是没有泛型的概念的,为了与以前的代码兼容,java使用了类型擦除。泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。
所以上面的题的结果是true, List<String> 和 List<Integer> 在 jvm 中的 Class 都是 List.class。类型 String 和 Integer被替换成 Object 类型。
下面将使用反射来进行验证。
package com.frank.test;
public class Erasure <T>{
T object;
public Erasure(T object) {
this.object = object;
}
}
import com.frank.test.Erasure;
public class Test {
public static void main(String[] args) {
Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is:" + eclz.getName());
Field[] fs = eclz.getDeclaredFields();
for ( Field f : fs) {
System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
}
}
}
打印结果
erasure class is:com.frank.test.Erasure
Field name object type:java.lang.Object
通配符
类型被擦除了,就不能用父类类型来表示子类了。但是,在现实编码中,确实有这样的需求,希望泛型能够处理某一范围内的数据类型,比如某个类和它的子类,对此 Java 引入了通配符这个概念。
通配符有 3 种形式。
<?> 被称作无限定的通配符。
<? extends T> 被称作有上限的通配符。
<? super T> 被称作有下限的通配符。
<?>:只保留了所有类型的通用功能。比如下面代码
List<?> wildlist = new ArrayList<String>();
wildlist.add("123");// 编译不通过
wildlist丧失了add功能,它不管装载在这个容器内的元素是什么类型,它只关心元素的数量、容器是否为空。
<? extends T> :使用父类T来表示它的子类类型,上界<? extends T>影响写,但是不影响读。编译器只知道容器内是T或者它的派生类,但具体是什么类型不知道,而是标上一个占位符CAP#1。往容器里面写时,T的子类通常要会比父类多加方法和变量等,要比T粒度要大, 类编译器都不知道能不能和这个CAP#1匹配,所以就都不允许。
<? super T> :使用T的父类类型。下界<? super T>不影响写,但读只能放在Object对象里。因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是T的父类,那往里存粒度比T小的都可以。但往外读取元素就费劲了,只有所有类的基类Object对象才能装下。但这样的话,元素的类型信息就全部丢失。
image.png
参考:
[1]. https://blog.csdn.net/briblue/article/details/76736356
[2]. https://www.cnblogs.com/zhaoyibing/p/9051428.html
网友评论