一、函数参数与泛型比较
泛型(generics),从字面的意思理解就是泛化的类型,即参数化类型。泛型的作用是什么,这里与函数参数做一个比较:
无参数的函数:
public int[] newIntArray(){ return new int[6]; }
函数功能即返回一个大小为6的数组,但是这个函数只能返回固定大小为6的数组,如果想返回不同size的数组则还要重新编写函数。解决方法就是使用函数参数,传入一个代表数组大小的size。
有参数的函数:我们在写一个函数时,往往需要向函数传入一些参数,使得函数具有一定的通用性,完成某些特定的功能,如:
public int[] newIntArray(int size){ return new int[size]; }
这是一个简单的函数,传入参数size,函数会生成相应大小的整型数组返回给我们。但是这里有一个限制,我们只能得到整型的数组,这就大大限制了这个函数的功能和可重用性,那么当我们想要得到一个浮点型数组时又要编写一个非常类似的函数。这里的解决办法就是使用泛型:
有参数且使用泛型的函数
@SuppressWarnings("unchecked") public static <T> T[] newArray(int size) { Object array = new Object[size]; return (T[])array; }
在函数内部我们使用了Object来新建数组,而不是直接用类型参数T直接声明(由于Java泛型的擦除所致,见【Java心得总结四】Java泛型下——万恶的擦除)。通过这个函数,我们就能够得到“任意大小任意类型”的数组。
总结:比较上面三个示例,我们就能理解为什么要有泛型这个概念了。在上面的示例中,每一级的例子都比前一级的例子功能上更加扩展,可重用性更加的高。第一个示例我们只能得到固定大小的数组;为了更加泛化,我们使用参数传入size来得到不同大小的数组;同样为了得到不同类型的数组,我们甚至将类型也作为参数传递入函数,从而得到任意大小任意类型的数组。
综上,我们利用泛型来解耦类或方法与所使用的类型之间的约束,将类型也作为一种参数传入,使得类或方法的可重用性大大提高。
二、泛型类
声明格式:
public class Generics<K, V> { K k; V v; public Generics(K k, V v) { this.k = k; this.v = v; } }
泛型类的声明一般放在类名之后,可以有多个泛型参数,用尖括号括起来形成类型参数列表。
应用:
泛型类应用最广泛的就是我们在平时Java编程中最最常用到的容器类(参见博文【Java心得总结五】Java容器上——容器初探),举一个容器的简单例子:
import java.util.ArrayList; import java.util.Date; import java.util.Random; public class RandomList<T> { private ArrayList<T> storage = new ArrayList<T>(); private Random rand = new Random(new Date().getTime()); public void add(T item) { storage.add(item); } public T select() { return storage.get(rand.nextInt(storage.size())); } }
这段代码将原有的ArrayList容器进行了封装,调用select函数会随机的返回一个列表中的元素。
三、泛型接口
声明格式:
public interface Generator<T> { public T next(); }
同泛型类的声明类似,在接口名之后,用尖括号将所有类型参数括起来。注:这里声明的是一个工厂设计模式常用的生成器接口。
应用:
我们平时编程最常见的泛型接口就是Iterable接口,即迭代器接口(参见博文【Java心得总结五】Java容器上——容器初探),举一个简单的例子:
import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.Random; public class RandomList<T> implements Iterable<T> { private ArrayList<T> storage = new ArrayList<T>(); private Random rand = new Random(new Date().getTime()); public void add(T item) { storage.add(item); } public T select() { return storage.get(rand.nextInt(storage.size())); } /* 实现Iterable接口 */ public Iterator<T> iterator() { return storage.iterator(); } }
还是上面泛型类举得容器例子,这次我们给RandomList类部署了Iterable接口,这样我们就可以利用foreach语句对RandomList中的元素进行遍历。
四、泛型方法
声明格式:
public <T> T genericMethod(T t){ return t; }
泛型方法的声明和泛型类的声明略有不同,它是在返回类型之前用尖括号列出类型参数列表,而函数传入的形参类型可以利用泛型来表示。
应用:
见文章开头的例子。
五、Java泛型的局限性
1.基本类型(【Java心得总结一】Java基本类型和包装类型解析)无法作为类型参数即ArrayList<int>这样的代码是不允许的,如果为我们想要使用必须使用基本类型对应的包装器类型ArrayList<Integer>
2.在泛型代码内部,无法获得任何有关泛型参数类型的信息(见【Java心得总结四】Java泛型下——万恶的擦除)。换句话说,如果传入的类型参数为T,即你在泛型代码内部你不知道T有什么方法,属性,关于T的一切信息都丢失了(类型信息,博文后续)。
3.注,在能够使用泛型方法的时候,尽量避免使整个类泛化。