java 泛型是java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操做的数据类型被指定为一个参数。这种参数类型能够用在类、接口和方法的建立中,分别称为泛型类、泛型接口、泛型方法。java
泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持建立能够按类型进行参数化的类。能够把类型参数看做是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符同样。数组
泛型是在java SE 1.5中引入的,在这以前,若是要使用集合的话是怎么样子的呢?对于集合List而言,往里面添加数据的方法add的参数是一个Object类型,这也就意味着什么数据类型均可以往里面添加。安全
List list = new ArrayList();
list.add("abc");
list.add("cdf");
复制代码
然而,添加容易取出来就有点麻烦了。由于咱们取的时候须要进行强制转换:bash
String s = (String) list.get(0);
复制代码
有的时候,咱们忘了添加进去是什么类型了,或者咱们写的方法,类给别人使用的时候,这个时候就会很容易出错,由于咱们不知道是什么类型,所以在运行的时候就会出现异常。框架
List list = new ArrayList();
list.add("abc");
list.add("cdf");
list.add(12);
list.add(23.9);
for (int i = 0; i < list.size(); i++) {
String tempString = (String) list.get(i);
System.out.println(tempString);
}
复制代码
对于上述引起的问题,咱们能够经过限定传入数据的类型来解决,就是在add数据的时候咱们就将它限定为某一类型,其余类型的数据add不进去,这样的话,咱们就很简单的解决了该问题。这种方法就是下面须要说的泛型。ide
List<String> list = new ArrayList<>();
list.add("abc");
list.add("cdf");
复制代码
经过限定add时传入的类型是String,这样在编译时就能检测出来,这样就能避免了上述问题。学习
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。ui
泛型类提及来比较抽象,咱们经过一个示例来了解一下:this
/**
* 此处T能够随便写为任意标识,常见的如T、E、K、V等形式的参数经常使用于表示泛型
*
* @author admin
*
* @param <T>
*/
public class Generic<T> {
/**
* 这个成员变量的类型为T,T的类型由外部指定
*/
private T t;
/**
* 泛型构造方法形参t的类型也为T,T的类型由外部指定
*
* @param t
*/
public Generic(T t) {
super();
this.t = t;
}
/**
* 泛型方法getT的返回值类型为T,T的类型由外部指定
*
* @return
*/
public T getT() {
return t;
}
/**
* 泛型方法setT的参数类型为T,T的类型由外部指定
*
* @param t
*/
public void setT(T t) {
this.t = t;
}
}
复制代码
泛型类比较简单,须要指定一个参数类型T,而后由外部传入,咱们看一下这个泛型类它的使用方式:spa
//泛型参数T这里指定为String类型
Generic<String> generic = new Generic<String>("Hello");
System.out.println(generic.getT());
复制代码
打印结果以下:
Hello
复制代码
这里须要注意的是:泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
泛型接口与泛型类的定义及使用基本相同。咱们也经过示例来了解一下:
/**
* 定义一个泛型接口
*
* @author admin
*
* @param <T>
*/
public interface IGeneric<T> {
T result();
}
复制代码
定义一个泛型接口后,使用这个泛型接口有两种状况:
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一块儿加到类中
*
* @author admin
*
* @param <T>
*/
public class Generic1Test<T> implements IGeneric<T> {
@Override
public T result() {
return null;
}
}
复制代码
/**
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则全部使用泛型的地方都要替换成传入的实参类型
*
* @author admin
*
*/
public class Generic2Test implements IGeneric<String> {
@Override
public String result() {
return "";
}
}
复制代码
泛型方法,是在调用方法的时候指明泛型的具体类型 ,可是相对于泛型类而言,泛型方法就比较复杂了,咱们来看一个具体示例:
/**
* 声明一个泛型方法,该泛型方法中带一个T类型形参,
*
* @param <T>
* @param a
* @param c
*/
static <T> void fromArrayToCollection(T[] a, List<T> c) {
for (T o : a) {
c.add(o);
}
}
复制代码
申明一个泛型方法时,首先在public与返回值之间的必不可少,这代表这是一个泛型方法,而且声明了一个泛型T,这个T能够出如今这个泛型方法的任意位置,泛型的数量也能够为任意多个。
对于泛型参数的申明比较复杂的多,后面须要专门的讲解。
咱们在定义泛型类,泛型方法,泛型接口的时候常常会遇见不少不一样的通配符,好比 T,E,K,V ,?等等,这些通配符又都是什么意思呢?
其实,这些不一样的通配符没什么本质上的区别,只不过在Java开发过程当中咱们约定了这些不一样的通配符所表达的意思不同:
对于通配符咱们能够理解为无界,就是, 好比List,通配符的主要做用就是让泛型可以接受未知类型的数据。若是泛型的类型只在方法声明中出现一次,就能够用通配符<?>取代它。
咱们说明一点:List<?> list和List list的区别:
注意:List<?> list能够add(null),由于null是任何引用数据类型都具备的元素。
用 extends 关键字声明,表示参数化的类型多是所指定的类型,或者是此类型的子类。
用 super 进行声明,表示参数化的类型多是所指定的类型,或者是此类型的父类型,直至 Object。
Java的泛型是伪泛型,这是由于Java在编译期间,全部的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
譬如: 对于List类型,在编译后会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽量的发现可能出错的地方。
咱们举个例子来讲明泛型擦除:
public static void main(String[] args) {
List<String> strList = new ArrayList<String>();
strList.add("AAAAA");
List<Integer> intList = new ArrayList<>();
intList.add(123);
System.out.println("编译时期类型是否相同: " + (strList.getClass() == intList.getClass()));
}
复制代码
打印结果以下:
从上面的例子看出,咱们定义了两个ArrayList数组,不过一个是ArrayList泛型类型的,只能存储字符串;一个是ArrayList泛型类型的,只能存储整数,最后,咱们经过list1对象和list2对象的getClass()方法获取他们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下原始类型。
Java 语言中引入泛型是一个较大的功能加强。不只语言、类型系统和编译器有了较大的变化,以支持泛型,并且类库也进行了大翻修,因此许多重要的类,好比集合框架,都已经成为泛型化的了。
在上面所举的例子都是一些简单的示例并不具备实际的应用,只是为了简单说明泛型的概念。本篇文章只是做为泛型的简单阐述,对于开发过程当中的泛型还须要咱们深刻的探索,后期会出一遍深刻理解泛型的文章,敬请期待。
专一于 Android 开发多年,喜欢写 blog 记录总结学习经验,blog 同步更新于本人的公众号,欢迎你们关注,一块儿交流学习~