什么是泛型?
泛型的意思是 类型参数化。
到底什么是类型参数化呢?通过这一节内容,我们希望大家能够彻底弄懂什么是泛型,以及如何在开发中使用泛型。
Java泛型应用是java核心基础之一,从java5开始引入泛型概念。如果你曾经使用过java中的collection相关的类,那么就算你已经接触过泛型了。在java的Collection中使用泛型是一件很简单的事情,可泛型还具有许多你想不到的作用。在深入了解泛型之前,首先来了解一下泛型的一些基本概念与原理。
Java泛型的应用可以提高代码的复用性,同时泛型提供了类型检查,减少了数据的类型转换,同时保证了类型的安全。下面来看一下,泛型如何保证了类型的安全:
List list = new ArrayList(); list.add("abc"); list.add(new Integer(1)); //可以通过编译 for (Object object : list) { System.out.println((String)object);//抛出ClassCastException异常 } |
上面的代码会在运行时抛出ClassCastException,因为它尝试将一个Integer转换为String。接着,来看一下从java5开始,Collection的用法:
List list.add("abc"); //list.add(new Integer(1)); //编译错误 for (String string : list) { System.out.println(string);//无需任何强制类型转换 } |
注意到,List的创建增加了类型参数String,因此只能向list添加String类型对象,添加其他对象会抛出编译异常;同样可以注意到,foreach循环不需要再添加任何强制类型转换,也就移除了运行时的ClassCastException异常。
既然是学泛型,自然就要知道如何去使用泛型定义自己的类和接口。同时为了加深理解泛型的作用,我们先定义一个不适用泛型的类:
public class Gen { private Object obj; public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } public static void main(String[] args) { Gen gen = new Gen(); gen.setObj("abc"); String str = (String) gen.getObj();//类型转换,可能会引起运行时ClassCastException } } |
原始类的定义,容易引发ClassCastException,因为在使用的时候我们无法知道具体的类型到底是什么。现在来看一下泛型类来重新定义Gen — 使用<>指定泛型参数,如下:
public class Gen T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } public static void main(String[] args) { Gen gen.setObj("abc"); // gen.setObj(10); //无法通过编译 String str = gen.getObj(); //无需类型转换 //----------------------------- Gen gen2 = new Gen();//raw type原始类型 gen2.setObj("abc"); gen2.setObj(10); //可以通过编译,自动装箱将10转化为Integer对象 Integer num = (Integer) gen2.getObj();//使用了强制类型转换 } } |
细心的你会发现在main()方法里是使用泛型类型Gen
ps:可以使用@SuppressWarnings("rawtypes")来忽略编译器弹出警告。
接口的泛型应用和类的泛型应用非常类似:
public interface List void add(E x); Iterator }
public interface Iterator E next(); boolean hasNext(); } |
另外,在定义泛型类和泛型接口的时候,我们也可以定义多个泛型化参数。例如Java中的Map
在使用泛型类的时候,我们就需要将泛型参数具体化,例如:
public static void main(String[] args) { ArrayList list.add("ABC"); list.add(123);//报错!由于泛型已经具体化成String类型,就不能使用整数类型了 } |
需要注意的是,当我们将泛型参数具体化成String类型之后,原本ArrayList中的add(E e)方法的参数类型就会变成String类型,这时候在调用add()方法的时候,就只能传入String类型的参数了。
另外,在Java中明确的规定了泛型参数在具体化的时候只能使用引用类型,所以是有的泛型参数必须使用Object类型及其子类类型,如果使用基本类型,就会出错:
//这里不能使用基本类型来具体化泛型参数 ArrayList<int> list = new ArrayList<>(); |
泛型类的继承和泛型接口的实现
我们在实现泛型接口的时候,也必须要定义泛型类去实现泛型接口。例如:
public class ArrayList
} |
为了更好地去理解泛型,我们也需要去理解java泛型的命名规范。为了与java关键字区别开来,java泛型参数只是使用一个大写字母来定义。各种常用泛型参数的意义如下:
E — Element,常用在java Collection里,如:List
K,V — Key,Value,代表Map的键值对
N — Number,数字
T — Type,类型,如String,Integer等等
S,U,V etc. - 2nd, 3rd, 4th 类型,和T的用法一样。
当然,你如果硬是要标新立异,使用其它的字母或单词,也是可以使用的,只是这样,代码的规范度就大大下降了。
有些时候,我们可能并不希望将整个类都泛型化。这个时候我们就可以只在某个方法上定义泛型,构造方法也是一样。例如:
public class GenMethod { public static for (T t : a) { c.add(t); } } public static void main(String[] args) { Object[] oa = new Object[100]; Collectionco = new ArrayList<>(); GenMethod.fromArrayToCollection(oa, co); } } |
GenMethod 的代码不多,不过需要注意的地方却不少。第一、定义方法所用的泛型参数需要在修饰符之后添加,如上面的,public static
public static for (T t : a) { c.add(t); c.add(new Object()); } } |
泛型构造方法的定义和泛型方法类似:
public class Gen { public
} } |
如果两个类之间有继承被被继承的关系,那么我们就可以将一个类的对象赋值类另外一个类的对象,比如:
String str = new String(); Object obj = new Object(); obj = str; |
这种关系同样适用于泛型。比如我们将泛型参数设置为Number,那么在随后的调用中,就只需要传入一个Number类型或者是Number的子类类型的对象就行了,比如Integer,Float,Double都可以:
ArrayList list.add(new Integer(1)); list.add(new Float(1.0)); list.add(new Double(1.0)); |
但是有一种情况是我们需要特别注意的,比如我们定义一个如下的方法:
public void someMethod(ArrayList } |
这个方法能接受什么样类型的参数类?
ArrayList
ArrayList
ArrayList
显然这个方法接受ArrayList
在泛型里也存在子类型,前提是其泛型参数的限制并没有发生改变,或者说泛型没有改变,其实就是从原来的类或接口来判断泛型的子类型。比如ArrayLIst
有时候,你会希望泛型类型只能是某一部分类型,比如在操作数据的时候,你希望是Number或其子类类型。这个通常的做法就是给泛型参数添加一个参数。其定义的形式为:
|
这个定义表示T应该是Numner的子类型,T和Parant可以是类,也可以是接口,注意此处的extends表示的是ParentType的子类型,和继承是有区别的。
public class Box private T t; public void set(T t) { this.t = t; } public T get() { return t; } public extends Number> void inspect(U u) { System.out.println("T: " + t.getClass().getName()); System.out.println("U: " + u.getClass().getName()); } public static void main(String[] args) { Box integerBox.set("abc"); //能通过编译,因为T指定为String类型 // integerBox.inspect("abc");//不能通过编译,因为U必须是Number类型或其子类 integerBox.inspect(new Integer(10)); } } |