Java中泛型的理解
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中泛型的理解相关的知识,希望对你有一定的参考价值。
Java中的泛型,本质上来说,就是是参数化类型,就是说所操作的数据类型被指定为一个参数,而不是确定的某种类型。这种数据类型可以用在类、接口和方法创建中。即泛型类、泛型接口、泛型方法。这样说可能不够生动,来举些例子说明一下。
例子一
我们通过 例子一 来简单看看泛型的应用场景。下面的情况中,没有使用泛型:
public class FanXingTest { public static void main(String[] args) { ArrayList arrayList = new ArrayList(); Integer integer1 = 1; arrayList.add(integer1); //你记得这个元素的类型,那么没问题,通过index取出来 Integer integer2 =(Integer) arrayList.get(0); System.out.println(integer2); //假如你记错了arraylist里面存的类型了,这时编译器也不会报错,但是运行时会报错 String string =(String) arrayList.get(0); System.out.println(string); } }
ArrayList里存放的是Object类。这样的好处是:通过对类型Object的引用来实现可以存放任意类(Java的多态机制),但也带来了缺点:需要进行强制类型转换。
可以看到,在没有使用泛型的情况下,你若记得 arrayList 里面存了什么类型的东西,那么最好不过了,可以直接通过index来取出来,并进行类型转换即可。
而假如你在不知道的情况下强制转换写错,编译器也不会提示错误,在运行时才会发现,会报一个错: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String 。实际情况中程序员不可能对每个元素都记得其类型,所以这样有明显的不合理性。
泛型的引入可以解决上述问题,先来看看针对上述情景使用泛型的情况:
public class FanXingTest { public static void main(String[] args) { ArrayList<Integer> arrayList = new ArrayList<Integer>(); Integer integer1 = 1; arrayList.add(integer1); //直接取出来,无需强制类型转换 Integer integer2 = arrayList.get(0); System.out.println(integer2); //编译器会检查出错误,根本不允许你把类型搞混了,下面两种情况编译器都直接提示不能类型转换 String string1 =(String) arrayList.get(0); String string2 = arrayList.get(0); } }
上面的代码中我们使用了泛型,就是在创建 arrayList 的时候,就限定了里面存放的只能是 Integer 类型的数据,那么编译时就可以检查类型安全,所有的强制类型转换是自动和隐式的,提高代码重用率。我们在取出数据的时候,就无需进行强制类型转换,而且你把类型搞错,编译器会提示错误。
例子二
那么泛型是如何实现的呢?自己如何创建泛型类?我们通过 例子二 来看一看。先自己来创建一个泛型类,如下所示:
//自己新建一个泛型类:MyFan class MyFan<T>{ private T t1; //构造函数 public MyFan(T t1){ this.t1=t1; } //测试泛型的操作数据类型 public void showInfo(){ System.out.println("所操作数据类型是:"+t1.getClass().getName()); //通过反射机制,可以知道T的各种成员变量和方法的信息 Method m[]=t1.getClass().getDeclaredMethods(); for (int i = 0; i < m.length; i++) { System.out.println(m[i].getName()); } } }
自己创建泛型类的时候,先假设这样一个处境:我希望我的这个类可以处理多种类型的数据。这就需要把类型"参数化",也就是当成一个"参数"传进来。所以定义类的时候,在类名的后面跟了一个<T>: class MyFan<T> ,这个T只是一个象征意义的符号,可以是任意符号。T 就是个类型"参数",我们之后可以传任意类型的类进来。上面的 showInfo() 方法中,我们可以利用反射机制,来得到传进来的这个数据类型是什么,也可以得到这个类里面有些个什么方法。接下来,我们利用我们自己创建的泛型类 MyFan<T> 来做个试验:
public class FanXingTest { public static void main(String[] args) { // TODO Auto-generated method stub MyFan<String> ft1=new MyFan<String>("黄黄"); ft1.showInfo(); MyFan<Integer> ft2=new MyFan<Integer>(1); ft2.showInfo(); //输出两个实例对象的类型,结果是一样的 System.out.println(ft1.getClass()); System.out.println(ft2.getClass()); } }
通过创建我们自己的泛型类的实例, ft1 里面的 t1 变量指向一个String类实例, ft2 里面的 t1 变量指向一个Integer类实例,那么上述程序的输出如下:
所操作数据类型是:java.lang.String
...String的各个成员方法略去
所操作数据类型是:java.lang.Integer
...Integer的各个成员方法略去
class MyFan
class MyFan
最后两行的输出,我们是想来通过 ft1/ft2.getClass() 来看看创建的这两个实例的类型会不会不一样,结果可以看到,两个都是属于 MyFan类的实例变量,并没有因为传入类型的不同而引起不同。
例子三
现在应该知道了泛型是如何运作的,我们在自己定义泛型类或者泛型接口的时候,<T>都是跟在类名的后面: class MyFan<T>{...} ,然后实例化的时候<T>也跟在类名的后边即可: MyFan<T> fan1 = new MyFan(); 。
有些时候我们还会遇到更为复杂的情况,这几天我在工具类Collections里面,看到了sort方法,如下:
1 public static <T extends Comparable<? super T>> void sort(List<T> list) { 2 list.sort(null); 3 }
我们需要知道extends后面跟的类型表示泛型的上限(含自己),super表示的是泛型的下限(含自己)。如<T extends 类/接口>,表示T必须是该指定类/接口的子类(实现了某接口或继承自某类,都算"子类")。
我们来看 <T extends Comparable<? super T>> ,首先规定了T必须是 Comparable<? super T> 的一个子类,也就是说,T 必须是实现了 Comparable<? super T> 这个接口的。然后<? super T>表示Comparable<>中的类型下限为T(就是至少得是T的父类,包括T)!我们可以通过 GregorianCalendar 类和 Calendar 类来加深理解。先看下两个类的定义:
//Calendar类实现了Comparable<Calendar>接口 public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>{ ... @Override public int compareTo(Calendar anotherCalendar) { return compareTo(getMillisOf(anotherCalendar)); } } //GregorianCalendar类继承自Calendar类 public class GregorianCalendar extends Calendar{...}
那我们现在假设有个ArrayList,调用上面的sort方法:
ArrayList<GregorianCalendar> arrayList = new ArrayList<GregorianCalendar>(); Collections.sort(arrayList);
此时不会报错,因为相当于 <GregorianCalendar extends Comparable<Calendar>> 是成立的,我们结合两个类的定义可以知道:Calendar为GregorianCalendar 的父类,并且Calendar实现了Comparable<Calendar>,于是GregorianCalendar 也实现了Comparable<Calendar>接口。所以泛型的限制范围 <T extends Comparable<? super T>> 是满足的。接下来我们自己定义一个sort函数:
public static <T extends Comparable<T>> void sort(ArrayList<T> list) { list.sort(null); }
此时调用我们的sort函数 sort(new ArrayList<GregorianCalendar>()); ,编译器就会报错,因为GregorianCalendar类只是实现了Comparable<Calendar>接口,并没有实现Comparable<GregorianCalendar>接口。于是传入的参数不满足泛型的限制范围,所以会报错
自己的一些思考
先看看下面的Collections类里面的两个sort方法
public static <T extends Comparable<? super T>> void sort(List<T> list) { list.sort(null); } public static <T> void sort(List<T> list, Comparator<? super T> c) { list.sort(c); }
注意到上面除了方法参数(比如List<T> list)之外,都是在返回类型(void)之前的标注(比如static <T extends Comparable<? super T>> void)。
其实泛型就是”可有可无”,它的存在就是限制了你将要使用的参数类是什么范围内,extends/super分别是泛型的上下界(含)。就比如上面第一个sort,限定了<T extends Comparable<? super T>>,第二个就是没限定,任意的<T>;但是还有参数这块,第一个的参数就是之前限定好的参数T即可;第二个的参数呢,在Comparator<? super T>这里限定了一个下届,得是T的父类。
再往下说,为什么是<T extends Comparable<? super T>>而不是<T extends Comparable<T>>呢。这其实考虑到了子类若是继承自父类,而父类已经实现了Comparable接口,子类可以拿来用,所以就有<T extends Comparable<? super T>>的效果,举个例子就是上面的GregorianCalendar类和Calendar类,等效于子类GregorianCalendar也实现了Comparable接口。这时候若是<T extends Comparable<T>>就不合适了。
其实一般自己实现的Comparable接口的话,都是 class MyTest implements Comparable<MyTest>这种,估计也不会class MyTest implements Comparable<MyTest的父类>,上面的<T extends Comparable<? super T>>只是考虑到了比如ManmanTest继承自MyTest,那么ManmanTest也就实现了Comparable<MyTest>接口的情况。
以上是关于Java中泛型的理解的主要内容,如果未能解决你的问题,请参考以下文章