在前面的文章中我们简要介绍了泛型的概念以及泛型类和泛型方法的使用。在介绍泛型时我们说过在在java中一般用E、T、K、V、N、?这几个字母和符号来表示泛型,对于前面的几个字符它们的使用没有区别,只要注意它们所代表的类型就好。而对于最后一个?号,它表示的是通配符,通配符一般用在类型不确定的情况之下,并且我们可以设定通配符的范围使得通配符所代表的类型在一定范围之内。
通配符的使用格式其余的泛型类没有区别,只是要特别注意使用的情况。一下用一个例子来说明通配符的使用。在之前我们曾建立了一个泛型类,在这个泛型类中定义了一个泛型属性flag,现在我们再定义一个普通类ShowMsg,在这个类中我们定义一个方法showFlag,用于打印泛型类Generic中的属性flag。
接下来建立一个测试类用于测试方法showFlag。由于showFlag方法是类ShowMsg中的方法,所以首先要实例化类ShowMsg,其次由于在方法showFlag中要传入一个泛型对象,所以也要实例化泛型对象。在这里,要注意我们在定义方法showFlag时已经确定了泛型的类型,比如我们在这个方发中将泛型定义为Integer,这时在实例化泛型对象的时候我们必须让泛型的类型也是Integer,不然我们就无法将实例化的对象传入showFlag方法中。就像如下的两张图片所示的结果一样,只要和方法中指定的泛型不同,那么就无法作为正确的参数传入。
此外或许有人想到了一点,那就时java中存在子类和父类的关系,而我们之前也说过Integer类的父类是Number类,那么如果我们将方法中的泛型定义为Number,作为子类的Integer能否作为参数传入方法showFlag呢?答案是不能,这样定义时如果实例化泛型类时泛型为Number,那么实例化的对象能够传入showFlag方法,但是作为子类的Integer仍然不能作为参数传入该方法,这是因为泛型只是作为占位符在类和方法中扮演类型的身份,它没有识别子类和父类关系的功能,就像下面的图片展示的一样,当我们修改方法中的泛型为Number后,Number的子类Integer和String类的地位是一样的,都无法作为正确的参数传入方法showFlag。
那么这是否意味着对于同一个方法如果要传入不同类型的参数时我都要修改方法中作为参数传入的泛型呢?这就是通配符存在的意义,我们在定义泛型方法的时候并不能确定在调用该方法时传入的参数是什么类型,而在调用方法的时候又回去修改方法中要传入的泛型的话方法作为java中的封装体显然就失去了它的作用,这时就可以采用通配符来代表泛型。通配符在没有规定界限的时候能够表示所有的泛型,就像在以下的演示代码中,我们在方法showFlag中定义传入的对象类型为通配符,这时在调用方法时,无论对象是什么类型,都可以作为参数正常传入。
不过这样做也存在一些问题,那么就是如果什么样的了类型都可以传入方法那么这个方法就失去了针对性,比如我们定义了一个方法来对学类的年龄来进行处理,这时即使我们传入一个另一个类比如猫,方法也不会报错,且能正常运行,这和我们的初衷相违背,因此我们还需要对通配符进行限定。
在java中通配符的上限用extends 类型1来表示,它代表通配符?所代表的类型最高只能是类型1或者类型1的子类,如果输入了类型1的父类,那么代码报错。统配符的下限用 super 类型表示,它代表通配符? 所代表的类型只能是类型2或者类型2的父类,如果传入了类型2的子类,那么代码报错。在通配符的范围限定中要注意虽然只是替换了关键词,但是它们二者之间仍然存在区别——通配符的上限限定ectends可以适用到所用泛型中,在定义泛型时可以用同样的语法结构规定它的上限,但是通配符的下限限定super只能用于通配符,无法应用到其他泛型符号中去。
package com.generic.demo;
/**
* 泛型类
*/
public class Generic<T> {
private T flag;
public T getFlag() {
return flag;
}
public void setFlag(T flag) {
this.flag = flag;
}
}
package com.generic.demo;
public class ShowMsg {
public void showFlag(Generic<? /*super Number*/ /*extends Number*/> generic){
//通配符的下限限定不适用于泛型类
System.out.println(generic.getFlag());
}
}
package com.generic.demo;
public class TestShowMsg {
public static void main(String[] args) {
ShowMsg showMsg = new ShowMsg();
Generic<Integer> generic = new Generic<>();
generic.setFlag(20);
showMsg.showFlag(generic);
System.out.println("_________________");
Generic<Number> generic1 = new Generic<>();
generic1.setFlag(50);
showMsg.showFlag(generic1);
System.out.println("________________________");
Generic<String> generic2 = new Generic<>();
generic2.setFlag("30");
showMsg.showFlag(generic2);
}
}