系列文章链接
一、为什么要使用泛型
其实新技术的出现都是原有的技术不能够满足需求了,或者是原有技术有比较大的弊端,大家都用的不舒服,那泛型也是这样的。
老生常谈的ArrayList 在没有泛型之前是这样的
public class ArrayList
{
private Object[] elementData;
public Object get(int i) { . . , }
public void add(Object o) { . . . }
}
这样的设计主要有两个问题,第一是获取值得时候需要强制类型转换,容易出错,不能直接使用object吧,那也没啥可用的;第二是数组类型为object意味着无论什么类型都能放到ArrayList里面,那取出的时候可能不知道原本是什么类型的对象了。
现在是这样的
public class ArrayList<E>
{
transient Object[] elementData;
public E get(int index) {...};
public boolean add(E e) {...};
}
其实就是多了个E,这个E就是一种泛型,泛型可以理解为是对引用数据类型的一种抽象,代表现在我ArrayList不是什么都接收,我只接收类型为E的(子类的问题后面谈)
可以看到加入了泛型之后,list不能插入Person类型之外的类型了,这样第二个问题就算解决了。
要解答第一个问题,首先要知道java程序执行的三个阶段,这里不多介绍;要知道的就是泛型E在经过反编译成class文件(这里的class文件代码只是表达这么个意思,实际的不长这样)后,泛型被转换成Object了,这意味着程序运行的时候根本没有泛型这个概念,泛型只存在于程序员的眼中,
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("张三"));
Person person = list.get(0);
}
那如果从class文件的角度看,list.get(0)得到的不就是Object对象了吗?实际上在class文件中这句应该是Person person = (Person)list.get(0);编译器会帮助我们做强制类型转换,这样不用人为转换,也就降低了出错的可能性。
二、创建一个简单的泛型类
public class ShowName <T>{
private T first;
private T second;
public ShowName() {
}
public ShowName(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
public static void main(String[] args) {
ShowName<Person> showName = new ShowName<>();
showName.setFirst(new Person("first"));
showName.setSecond(new Person("second"));
System.out.println("第一个值:" + showName.getFirst() + "\n" + "第二个值:" + showName.getSecond() + "\n");
}
ShowName类引入了一个类型变量T,当然也可以以引入多个比如public class ShowName <K,V>,类型变量或者说类型参数可以在这个泛型类中域、局部变量和方法的返回类型中使用,泛型方法除外。
例子中我用的是Person类,其实也可以用String Integer这种类型,对应的set和get方法也必须和使用的类型匹配。这也是泛型最基本的作用。
注释:类型变量使用大写形式,且比较短,这是很常见的。在java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型,T(需要时还可以用U和S)表示任意类型。
三、泛型方法
泛型方法和泛型类是相似的,只是作用域不同,一个是作用于类,整个类可以使用定义的泛型,一个是只作用于定义的泛型方法内部,可以泛型类中有泛型方法,但是泛型方法的作用域会覆盖泛型类,也可以泛型方法在普通类之中。
我们可以看一下ArrayList的toArray方法
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
对于泛型方法,类型变量是放在修饰符后面,返回参数前面,很明显这个泛型方法中的T和ArrayList<E>中的E没有什么关联。
泛型方法一般来说在调用的时候,不用标明T的类型,编译器可以通过参数和返回值来判断T的具体类型,但是如果是多个参数输入,比如add(T ...a)输入 add(1,1.01,2)这种,那编译器会查找参数共同的父类,比如Number,或者是给出报错提示。