泛型是java 1.5的新特性,本质是参数化类型,就是将要操作的数据类型指定为一个参数。泛型可以使用在类,接口,方法中,分别叫做泛型类,泛型接口以及泛型方法。
比如ArrayList
就是一个泛型类。
为什么要使用泛型?
在没有泛型之前,将数据存入集合,是这样子操作的:
public class GenericTest {
public static void main(String[] args) {
ArrayList array = new ArrayList();
array.add("Hello");
array.add(123);
String str = (String)array.get(0);
int num = (int)array.get(1);
System.out.println(str);
System.out.println(num);
}
}
存入数据的时候是可以任意类型,取出来时需要做一个类型转换,这样子操作就存在一个问题,过了许久后可能会忘记了集合的第几个数据是什么类型,一旦搞错,就会产生一个异常java.lang.ClassCastException
引入泛型之后,代码就变成了这样子的:
public class GenericTest {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList();
array.add("Hello");
// array.add(123);
String str = array.get(0);
// int num = (int)array.get(1);
System.out.println(str);
// System.out.println(num);
}
}
在编译时就已经确定了参数类型为String,如果存入集合的数据不是String,在编译时就会报错,在取出数据时也不需要进行类型转换了,这样一来,使得在运行时可能发生的java.lang.ClassCastException异常在编译时期就能被编译器发现,从而从运行时错误变成了编译时期的错误,并且在使用的时候避免了类型转换的麻烦。
定义泛型类的格式
修饰符 class 类名<代表泛型的变量> { ... }
比如源码中的ArrayList的定义是这样的:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
....
}
泛型类的编写比普通类要稍微复杂一些,我们可以根据普通类的编写来进行改造从而完成泛型类的编写。
比如先创建一个普通类Person
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
然后使用T来标记需要指定的类型,这里是String,改写后的Person类如下:
public class Person<T> {
private T name;
public Person(T name) {
this.name = name;
}
public T getName() {
return this.name;
}
}
定义泛型方法的格式
修饰符 <代表泛型的变量> 返回值类型 方法名(参数) { ... }
比如可以在普通类中定义泛型方法:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public <T> void print (T msg) {
System.out.println(msg.getClass());
}
}
定义泛型接口的格式
修饰符 interface 接口名<代表泛型的变量> { ... }
比如:
public interface List<E> {
....
}
泛型类、泛型方法和泛型接口的确定时间
上面介绍了如何定义泛型,但是这个泛型在什么时候确定泛型类型呢?
对于泛型类,是在创建对象时候确定泛型类型的,比如:``ArrayList list = new ArrayList<>()`
对于泛型方法,是在方法被调用的时候确定泛型类型的,比如Person p = new Person(); p.print("hello java");
对于泛型接口,是有两种情况存在的:
第一种:在实现接口时候指定泛型类型。
第二种:如果实现接口的时候仍旧使用了泛型,则在创建接口实现类对象时候指定泛型类型。
泛型的定义需要注意的几点
泛型的类型只能是引用类型,不可以是基本类型
<代表泛型的变量> 尖括号里可以使用任意的字母,对编译器来说都是一样的,但是习惯上使用T,E,K,V等字母来表示(完全是因为程序员习惯)
其中主要是因为这些字母都代表了一个含义:
泛型有三种类型的通配符:
extends T>
:上边界通配符
意义:表示只能接受T类型或者T类型的子类型
缺陷:只能读取对象,不能添加任何对象(null除外)
super T>
:下边界通配符
意义:表示只能接受T类型或者T类型的父类型
缺陷:只能添加对象,不能读取对象(Object除外)
>
:无界通配符
等同于: extends Object>
比如有Object类,String类,Number类,Integer类,其中Number类是Integer类的父类
// 泛型的上边界,此时泛型?,必须是Number或者其子类
public static void getElement1(Collection<? extends Number> c) {}
// 泛型的下边界,此时泛型?,必须是Number或者其父类
public static void getElement2(Collection<? super Number> c) {}
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement1(list1);
getElement1(list2); // error
getElement1(list3);
getElement1(list4); // error
getElement2(list1); // error
getElement2(list2); // error
getElement2(list3);
getElement2(list4);
}