Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。泛型提供了编译时类型安全检查机制,该机制允许开发者在编译时检测到非法类型。
下面我们通过一个集合的简单例子引入今天要讲的泛型:
package game;
import java.util.ArrayList;
import java.util.List;
public class text {
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
list.add("123");
list.add(123); //这里明显会报错
}
}
出错提示如下:
通过上面的例子我们可以看出,在代码编写阶段就已经报错了,我们不能向String类型的集合中添加int类型的数据。(在上个例子出现的<>即泛型的使用)。但其实如果我们把List集合当成普通类的时候,添加多个不同类型的数据,是不会出现如上图所示的错误:
package game;
import java.util.ArrayList;
import java.util.List;
public class text {
public static void main(String[] args) {
List list=new ArrayList();
list.add("123");
list.add(123); //这里不会出现问题
}
}
截图如下,不会报错,会出现安全警告:
通过以上代码,我们可以得知不定义泛型的话是可以往集合中添加多个不同类型的数据的,所以说泛型只是一种类型的规范,在代码编写阶段起一种限制。
下面通过另一个例子来看看泛型背后数据到底是什么类型:
首先定义泛型类如下:
package game;
public class text<T>{
T val;
public void setVal(T val) {
this.val=val;
}
public T getVal() {
return val;
}
}
上面定义了一个泛型的类(实际应用中泛型类的作用用于类被实例化后,传入具体的类型参数),接下来我们通过反射获取属性和getValue()方法返回的数据类型:
package game;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class text_1 {
public static void main(String[] args) {
//实例对象并赋值
text<String> text=new text<String>();
text.setVal("斯文败类");
try {
//获取属性上的泛型类型
Field fieldVal=text.getClass().getDeclaredField("val");
Class<?> type=fieldVal.getType();
String typeName=type.getTypeName();
System.out.println("type"+typeName);
//获取方法上的泛型类型
Method getVal=text.getClass().getDeclaredMethod("getVal");
Object objInvoke=getVal.invoke(text);
String methodName=objInvoke.getClass().getName();
System.out.println("methodName"+methodName);
}catch(Exception e) {
e.printStackTrace();
}
}
}
运行结果如下所示:
从上面的结果我们可以看出,通过反射机制获取到的属性是Object类型,在方法中返回的是String类型。其实它在getVal方法里面实际是做了一个强转的动作,将Object类型的val强转成String类型。泛型只是为了约束我们规范代码,而对于编译完之后的class交给虚拟机后,对于虚拟机是没有泛型的说法的,所有泛型在它看来都是Object类型。我们可以看下面的例子来理解一下,我把上面定义的text稍微修改:
package game;
public class text<T extends String>{
T val;
public void setVal(T val) {
this.val=val;
}
public T getVal() {
return val;
}
}
这里我们将泛型添加了个关键字extends,extends是约束了泛型是向下继承的,最后我们通过反射获取val的类型是String类型的,加上了extends关键字其实就是约束泛型是属于哪一类的。所以我们在编写代码的时候如果没有向下兼容类型,会警告报错。如下所示:
既然泛型其实对于JVM来说都是Object类型,那我们直接把类型定义成Object不就好了吗,这种做法是没有问题的,但是当拿到Object类型值之后还是要自己进行强转。定义泛型减少了我们的强转工作,它会将工作交给虚拟机。泛型的好处就是在于编译阶段时候能够检查类型安全,并且所有强制转换都是自动和隐式的。泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。
泛型用在哪
常见的泛型主要用在普通类,抽象类,接口,静态或非静态方法上。
泛型中的通配符
我们在定义泛型类、泛型方法、接口时候经常会碰到很多不同的通配符,比如T、E、K、V、?等等。本质上这些通配符没有太大区别,只不过是编码时候起的一种代码的约定作用。下面我们来看他们的约定:
?:表示不确定的java类型
T(type):表示具体的一个java类型,而且T完全可用A-Z之间任何一个字符替代
K、V:分别表示java键值中的key和value
E:代表Element
?无界通配符
通配符其实在声明局部变量时没有太多的意义,但当作为一个方法声明一个参数时,就有很重要的作用。举个例子如下,比如有一个父类Animal和两个子类Bird和Dog:
package game;
import java.util.ArrayList;
import java.util.List;
public class GenericTest {
//通配符用来定义变量是没有太多的意义
//下面两个方法演示
public static void test1(List<? extends Animal> animals) {
//方法略
}
public static void test2(List<Animal> animals) {
//方法略
}
public static void main(String[] args) {
List<Bird> bird=new ArrayList<Bird>();
GenericTest.test1(bird);
GenericTest.test2(bird);//报错
}
}
这是因为test2方法要求需要一个Animal的List,传递Bird的bird肯定报错。当我们使用无界通配符的时候,如test1,限定了上届,但是不关心具体类型是什么,即对所有Animal子类都可以支持且不报错。
上界通配符与下界通配符
上界通配符,其实即用extends关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类,用来限定泛型的上界。
下界通配符,就是用super进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直到Object,用来限定泛型的下界。
?和T的区别
简单总结如下:T是一个“确定的”类型,通常用于泛型类和泛型方法的定义。?是一个“不确定”的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。