什么是泛型
泛型(Generics )是把类型参数化,运用于类、接口、方法中,可以通过执行泛型类型调用分配一个类型,
将用分配的具体类型替换泛型类型。- 然后,所分配的类型将用于限制容器内使用的值,这样就无需进行类型转换,
还可以在编译时提供更强的类型检查。
泛型,即“参数化类型”。
一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。
那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,
此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
为什么需要泛型
- 首先,我们看下下面这段简短的代码:
publicclassGenericTest {
publicstaticvoidmain(String[] args) {
Listlist=newArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100);
for (inti=0; i < list.size(); i++) {
Stringname= (String) list.get(i); // 1
System.out.println("name:" + name);
}
}
}
定义了一个List类型的集合,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他编码原因,很容易出现类似于//1中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此,导致此类错误编码过程中不易发现。
在如上的编码过程中,我们发现主要存在两个问题:
1.当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,
改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。
2.因此,//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,
且很容易出现“java.lang.ClassCastException”异常。
那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,
运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。
泛型有什么用
泛型主要有两个好处:
(1)消除显示的强制类型转换,提高代码复用
(2)提供更强的类型检查,避免运行时的ClassCastException
什么时候使用泛型
只要类中操作的引用数据类型不确定,就可以定义泛型类。通过使用泛型类,可以省去强制类型转换和类型转化异常的麻烦。
泛型的使用
类型参数(又称类型变量)用作占位符,指示在运行时为类分配类型。
根据需要,可能有一个或多个类型参数,并且可以用于整个类。
根据惯例,类型参数是单个大写字母,该字母用于指示所定义的参数类型。
- 下面列出每个用例的标准类型参数:
E:元素
K:键
N:数字
T:类型
V:值
S、U、V 等:多参数情况中的第 2、3、4 个类型
? 表示不确定的java类型(无限制通配符类型)
- 泛型类
当一个类要操作的引用数据类型不确定的时候,可以给该类定义一个形参。
用到这个类的时候,通过传递类型参数的形式,来确定要操作的具体的对象类型。
在JDK1.5之前,为了提高代码的通用性,通常把类型定义为所有类的父类型:Object,
这样做有两大弊端:
1. 在具体操作的时候要进行强制类型转换;
2. 这样还是指定了类型,还是不灵活,对具体类型的方法未知且不安全。
泛型类的格式:在类名后面声明类型变量<E>,泛型类可以有多个类型变量, 如:
publicclassMyClass<K, V>
什么时候使用泛型类?
只要类中操作的引用数据类型不确定,就可以定义泛型类。通过使用泛型类,可以省去强制类型转换和类型转化异常的麻烦。
- 泛型方法
泛型方法也是为了提高代码的重用性和程序安全性。
编程原则:
尽量设计泛型方法解决问题,如果设计泛型方法可以取代泛型整个类,应该采用泛型方法。
泛型方法的格式:
类型变量放在修饰符后面和返回类型前面,
如:
publicstatic <E> E getMax(T... in)
public static <T, U extends DLRspBase> U getContent(String method, T t, Class<U> u)
throws InstantiationException, IllegalAccessException {}
- 定义泛型方法语法格式如下:
- 调用泛型方法语法格式如下:
- 说明一下,定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。
- Class<T>的作用就是指明泛型的具体类型,而Class<T>类型的变量c,可以用来创建泛型类的对象。
- 为什么要用变量c来创建对象呢?既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用变量c的newInstance方法去创建对象,也就是利用反射创建对象。
- 泛型方法要求的参数是Class<T>类型,而Class.forName()方法的返回值也是Class<T>,因此可以用Class.forName()作为参数。其中,forName()方法中的参数是何种类型,返回的Class<T>就是何种类型。在本例中,forName()方法中传入的是User类的完整路径,因此返回的是Class<User>类型的对象,因此调用泛型方法时,变量c的类型就是Class<User>,因此泛型方法中的泛型T就被指明为User,因此变量obj的类型为User。
- 当然,泛型方法不是仅仅可以有一个参数Class<T>,可以根据需要添加其他参数。
- 为什么要使用泛型方法呢?因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。
- 泛型接口
将泛型原理用于接口实现中,就是泛型接口。
泛型接口的格式:泛型接口格式类似于泛型类的格式,接口中的方法的格式类似于泛型方法的格式。
- 泛型接口例子:
- MyInterface.java
publicinterfaceMyInteface<T> {
public T read(T t);
}
- Generic2.java
publicclassGeneric2implementsMyInterface<String>{
publicstaticvoidmain(String[] args) {
Generic2g=newGeneric2();
System.out.println(g.read("hahaha"));
}
@Override
public String read(String str) {
return str;
}
}
泛型的限定
类型变量的限定
- 如果在方法前指定了<T>,那么就是说,方法的这个泛型类型变量和类定义时的泛型类型无关,这个特性让泛型方法可以定义在普通类中而不是泛型类中。
- 我们都知道,泛型中可以限定类型变量必须实现某几个接口或者继承某个类,多个限定类型用&分隔,类必须放在限定列表中所有接口的前面。
import java.io.Serializable;
/**
* ICE
* 2016/10/17001714:12
*/
publicclassDemo {
publicstaticvoidmain(String[] args) {
D<A> d = newD<>();
Aa=newA();
d.test1(a);
Bb=newB();
d.test1(b);
Cc=newC();
d.test1(c);
<span style="white-space:pre"> //test2泛型方法传入的类型是String,不是 D<A> d = new D<>();定义的A类型</span>
d.test2("test");
}
}
classAimplementsSerializable, Cloneable {
@Override
public String toString() {
return"A{}";
}
}
classBextendsA {
@Override
public String toString() {
return"B{}";
}
}
classCextendsA {
@Override
public String toString() {
return"C{}";
}
}
classD<T extendsA & Serializable & Cloneable> {
publicvoidtest1(T t) {
System.out.println(t);
}
public <T> voidtest2(T t) {
System.out.println(t);
}
}
- 输出:
A{}
B{}
C{}
test
通配符类型
类型通配符一般是使用 ? 代替具体的类型实参
通配符“?”同样可以对类型进行限定。可以分为子类型限定、超类型限定和无限定。
通配符不是类型变量,因此不能在代码中使用"?"作为一种类型。
<? extendsT>:是指 “ 上界通配符 (Upper Bounds Wildcards) ”
<? super T>:是指 “ 下界通配符 (Lower Bounds Wildcards) ”
子类型限定
表示类型的上界,类似泛型的类型变量限定,格式是:? extendsX。
作用:主要用来安全地访问数据,可以访问X及其子类型。
一个类型变量或通配符可以有多个限定,多个限定用“&”分隔开,且限定中最多有一个类,可以有多个接口;
如果有类限定,类限定必须放在限定列表的最前面。如:T extendsMyClass1 & MyInterface1 & MyInterface2
超类型限定
表示类型的下界,格式是:? super X。
特点:
1、限定为X和X的超类型,直至Object类,因为不知道具体是哪个超类型,因此方法返回的类型只能赋给Object。
2、因为X的子类型可以向上转型为X,所以作为方法的参数时,可以传递null,X以及X的子类型
作用:主要用来安全地写入数据,可以写入X及其子类型。
无限定
无限定不等于可以传任何值,相反,作为方法的参数时,只能传递null,作为方法的返回时,只能赋给Object。
通配符类型--总结:
如果频繁支持读取数据,不要求写数据,使用<? extendsT>。即生产者 使用 <? extendsT>
如果频繁支持写入数据,不特别要求读数据,使用<? super T>。即消费者 使用 <? super T>
如果都需要支持,使用<T>。
泛型擦除
Java的泛型在编译期间,所有的泛型信息都会被擦除掉。
Classc1=newArrayList<Integer>().getClass();
Classc2=newArrayList<Long>().getClass();
System.out.println(c1 == c2);
这就是 Java 泛型的类型擦除造成的,因为不管是 ArrayList<Integer> 还是 ArrayList<Long>,在编译时都会被编译器擦除成了 ArrayList。Java 之所以要避免在创建泛型实例时而创建新的类,从而避免运行时的过度消耗。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,
在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,
也就是说,成功编译过后的class文件中是不包含任何泛型信息的。
泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
泛型类型信息
- 那么,如果我们确实某些场景,如HTTP或RPC或jackson需要获取泛型进行序列化反序列化的时候,需要获取泛型类型信息。
- 可以参照如下:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 获取运行时的泛型类型信息
*
* @author Sven Augustus
*/
publicclassTest2 {
staticclassParameterizedTypeReference<T> {
protectedfinal Type type;
publicParameterizedTypeReference() {
TypesuperClass=this.getClass().getGenericSuperclass();
//if (superClass instanceof Class) {
// throw new IllegalArgumentException(
//"Internal error: TypeReference constructed without actual type information");
// } else {
this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
//}
}
public Type getType() {
return type;
}
}
publicstaticvoidmain(String[] args) {
// System.out.println(new ParameterizedTypeReference<String>().getType());
// java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
// 此处会输出报错,因此ParameterizedTypeReference 应不能直接实例化,可以考虑加abstract
System.out.println(newParameterizedTypeReference<String>() { }.getType());
// ParameterizedTypeReference 的匿名内部类,可以触发super(),
//即 ParameterizedTypeReference()的构造器逻辑,正常运行
}
}
注意一个关键点:
可以通过定义类的方式(通常为匿名内部类,因为我们创建这个类只是为了获得泛型信息)在运行时获得泛型参数。
泛型数组(可能导致类型不安全)
注意:Java中没有所谓的泛型数组一说
List<String>[] lsa = newArrayList<String>[10]; // error
如果可以的话,可能导致类型不安全。如:
Objecto= lsa;
Object []oa = (Object[])o;
List<Integer> li = newArrayList<Integer>();
li.add(newInteger(3));
oa[1] = li;
Strings= lsa[1].get(0); // runtime error
参考来源: http://mp.weixin.qq.com/s/v-h9w6TP99uYFihJ8slklQ
参考来源: http://www.cnblogs.com/lwbqqyumidi/p/3837629.html