泛型的概念
泛型,就是可以在定义类、接口时,通过一个标识来表示类中的属性类型,或者是方法的返回值及参数类型。这个标识将在使用时确定。
不使用泛型带来的安全隐患
在JDK5之前,是没有泛型的。当我们设计一个集合时,不能确定里面装的是什么类型的对象,于是就设计成Object。当取出集合中的元素时,默认是Object的,使用时还需要强转成需要的类型,如果添加时添加了别的类型的对象,还会出现ClassCastException,带来安全隐患。
public void test(){
ArrayList list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
list.add("jack"); // 不小心加了个String类型的进去
for (int i = 0; i < list.size(); i++) {
Integer o = (Integer) list.get(i);
System.out.print(o + "\t");
}
从JDK5以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该List只能保存字符串类型的对象。
集合中使用泛型
泛型在集合中使用的较多,就以集合举例。
public void test2(){
ArrayList<String> list = new ArrayList<>();
list.add("Tom");
list.add("and");
list.add("Jerry");
// list.add(2); 编译期间就报错
for (int i = 0; i < list.size(); i++) {
String s = list.get(i); // 无需强转,代码更加健壮
System.out.print(s + "\t");
}
}
这里ArrayList的<>因为酷似菱形被称之为“菱形语法”。在JDK7之前,<>中的泛型必须补全:ArrayList list = new ArrayList();而在JDK7之后,后面的<>中的泛型类型可以不写,编译器自动推断。
自定义泛型
定义一个父类,并给父类指定多个泛型参数,它们用逗号隔开
public class Father<K,V> {
public K print(K k){
return k;
}
}
定义一个子类去继承父类,此时如果不去指定父类中两个泛型的参数,那么默认是Object,可以看到,重写的方法中,返回类型K变成了Object
public class Son extends Father {
@Override
public Object print(Object o) {
return o;
}
}
如若指定父类的全部泛型类型,则子类中从父类继承过来的所有的泛型都变为指定的类型
public class Son extends Father<String, Integer> {
@Override
public String print(String o) {
return o;
}
}
如果子类保留父类的全部泛型,则可以将泛型的指定延迟到子类中去指定泛型的类型。
public class Son<K,V> extends Father<K, V> {//父类中不指定K,V类型,则子类的泛型必须有K,V类型,由父类去指定
}
如果子类只指定父类的部分泛型,则父类一定要指明子类中不指定的泛型类型。
public class Son<K> extends Father<K, Integer> {// K被子类保留,但是V没有,则父类指定为Integer类型
}
子类还可以自己额外定义泛型
public class Son<K, V, T> extends Father<K, V> { //子类添加了K这种泛型类型
}
注意:下面这段代码,K,V是子类自己的泛型,而不是父类的。虽然父类中也是K,V,但是我们没有在Father后面指定,之前也讲过,没有指定,默认就是Object类型,相当于extends Father<Object,Objcet>
public class Son<K, V > extends Father{
@Override
public Object print(Object o) {
return o;
}
}
泛型方法
方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
语法格式:[访问权限] <泛型> 返回类型 方法名 ([ 泛型标识 参数名称] ) 抛出的异常
泛型方法中的泛型并非在类上定义的泛型,是独立出来的,且泛型方法与所在类是否为泛型类没有任何关系。在泛型方法中,无法使用类的泛型。(静态不能引用非静态)
举例:
import java.util.List;
public class GenericMethod<T> {
private T param;
public T getParam() { // 不是泛型方法,使用的是类的泛型
return this.param;
}
public void setParam(T param) { // 不是泛型方法,使用的是类的泛型
this.param = param;
}
/**
* 将集合中的内容拷贝到数组并返回
* 泛型方法中前面一定要加上 <泛型字母>,告诉编译器这是泛型方法,否则编译器会以为E是一个类
* 泛型方法中的泛型在调用是确定,并非在初始化对象时确定,所以可以是static的
* @param list 入参list
* @param <E> 泛型
* @return E数组
*/
public static <E> E[] printList(List<E> list) {
E[] arr = (E[]) new Object[list.size()];
for (int i = 0; i < list.size(); i++) {
arr[i] = list.get(i);
}
return arr;
}
}
泛型中的继承问题
如果类B是类A的子类,而G是具有泛型的类或者接口,那么G< B >并不是G< A >的子类型。
public static void main(String[] args) {
Object obj = new Object();
String str = new String();
obj = str; // 编译不报错
ArrayList<Object> objectList = new ArrayList<>();
ArrayList<String> stringList = new ArrayList<>();
objectList = stringList; // 编译报错
}
可以看到,即使String是Object的子类,但是ArrayList< String >并不是ArrayList< Object >的子类,他们之间没有多态性。但是这样就十分的麻烦,如果要接收不同泛型类型的集合,还需要重载多个方法。那么有没有一个泛型是所有泛型的父类,就向Object一样呢?答案是肯定的,就要用到通配符。
通配符的使用
泛型的类型通配符是英文输入法下的 ? ,比如 List<?> , Map<?,?>,Comparable<?>等。List<?>即是List< String >、List< Object >等所有泛型List的父类 。
使用的注意点,拿集合举例:
- 如果是?类型的泛型,那么则代表此类或接口的泛型不确定,但是我们可以通过集合的get()方法来获取集合中的元素,或者迭代器输出元素,即使类型不确定的类型,但是我们可以知道,它总是Object或者Object子类。
public void printList(List<?> list) {
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object obj= iterator.next();//遍历取出,使用Object接收
System.out.println(obj);
}
}
- 对于不确定的泛型类型 ? ,我们是无法向集合中写入元素的。因为我们无法确切的知道元素的类型。 除了null,因为泛型必须是引用类型,而所有引用类型的默认值都是null
public void test(){
ArrayList<?> arrayList = new ArrayList<>();
// arrayList.add(new Object()); // 编译不通过
arrayList.add(null);
System.out.println(arrayList.get(0));
}
通配符的使用
public void test2(){
ArrayList<Integer> integerList = new ArrayList<>();
ArrayList<String> stringList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
stringList.add("Jack");
stringList.add("And");
stringList.add("Rose");
printList(stringList);
System.out.println();
printList(integerList);
}
public static void printList(List<?> list) { // 使用?通配符,是所有泛型的父类
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object obj= iterator.next();//遍历取出,使用Object接收
System.out.print(obj + "\t");
}
}
通配符的上下限
在看源码时,经常看到 <? extends T > 或者 <? super T>这种泛型形式,这就是指定泛型的上下限。以集合为例。
- List<? extends Father> : 表示 <= Father ,即泛型类型必须是Father的子类或Father类型
- List<? super Father> :表示 >= Father,即泛型类型必须是Father的父类或Father类型
- List<? extends Comparable> : 表示泛型类型必须是实现了Comparable的子类
举例 ? extends Father:
public void test() {
Son<String, Integer> son = new Son<>();
List<Son> list = new ArrayList<>();
print(list); // 编译通过
List<Father> list1 = new ArrayList<>();
print(list1); // 编译通过
List<Object> list2 = new ArrayList<>();
print(list2); //编译不通过
}
public void print(Collection<? extends Father> list) { //
Iterator<? extends Father> iterator = list.iterator();
while (iterator.hasNext()) {
Father<String, Integer> f = iterator.next();
System.out.println(f);
}
}
举例 ? super Father
public void test() {
Son<String, Integer> son = new Son<>();
List<Son> list = new ArrayList<>();
print(list); // 编译不通过,要求起步是Father类型
List<Father> list1 = new ArrayList<>();
print(list1); // 编译通过
List<Object> list2 = new ArrayList<>();
print(list2); //编译通过
}
public void print(Collection<? super Father> list) {
Iterator<? super Father> iterator = list.iterator();
while (iterator.hasNext()) {
Object f = iterator.next();
System.out.println(f);
}
}
通配符上下限的添加问题
看一个例子
public void addTest(){
List<? extends Father> list1 = new ArrayList<>();
List<? super Father> list2 = new ArrayList<>();
list1.add(new Son()); // 编译不通过
list1.add(new Father()); // 编译不通过
list1.add(new String()); // 编译不通过
list2.add(new Father()); // 编译通过
list2.add(new Son()); // 编译通过
list2.add(new Object()); // 编译不通过
}
当我们向集合中添加元素时,如果指定泛型类型,则只能添加指定类型的元素。如果使用了通配符 ? ,则表示允许所有泛型的引用调用。如果集合中限制了通配符的上下限,那么看下限即可。
List<? extends Father> list1 = new ArrayList<>(); 集合中可以存放Father及Father的子类,相当于它的范围是(-∞,Father),相当于是无下限的。如果向集合中存放Father类型,那么万一?是比Father还小的类型就无法存放,子类可以赋值给父类,但是父类不能赋值给子类。就好比List< Son >,我们无法add(Father)。这里是没有下限的,理论上无论你添加什么值,总有小于你添加的类型。
List<? super Father> list2 = new ArrayList<>();集合中可以存放Father和Father的父类,相当于它的范围是(Father,+∞),相当于下限是Father。也就是说,该集合中能存放Father已经Father的子类类型,因为无论如何,我的起步点都是从Father开始,子类赋值给父类,是可以的。但是添加Object又不行了,因为在(Father,Object)之间,总能找到比你小的子类,父类无法赋值给子类,所以无法添加。
总结
- 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
- 泛型类的构造器是:public GenericClass(){},而非public GenericClass< E >(){};
- 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
- 泛型不同的引用不能相互赋值。
- 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
- 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
- jdk7,泛型的简化操作:ArrayList flist = new ArrayList<>();
- 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
- 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
- 异常类不能是泛型的
- 不能使用new E[]。但是可以:E[] arr= (E[])new Object[capacity];
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型。
- 如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,则G< B >并不是G< A >的子类型。
- 无法将通配符 ? 声明在泛型方法中。
public static <?> ?[] printList(List<?> list) {} // 错的离谱
- 无法将通配符 ?声明在类上面
public class GenericMethod<?> {} // 也是不允许的
- 无法将通配符 ? 用在创建对象上
ArrayList<?> list= new ArrayList<?>(); //右边的尖括号中无法使用 ?