一、泛型概述
泛型是JDK1.5以后出现的特性,为了解决元素存入集合时“编译时不检查类型异常”和元素存入集合后“丢失对象信息”,这将导致在使用集合的时候不得不靠人力来保证存入元素的信息,使得程序臃肿效率低下。
泛型:(Generic type或Generics)是对java语言的类型系统的一种个扩展,可以支持创建按照类型进行参数化的类,可以把类型参数看作是使用参数化类型时指定的类型占位符,就像方法 形式参数是运行时传递的值的占位符一样。
另外,需要重点指出的是,泛型只作用于编译期,而不存在运行期,等到运行的时候程序是不带泛型的,也就是泛型擦除;除此之外,使用泛型的时候,传入泛型占位符的必须是引用数据类型,而不能是基本数据类型。
二、泛型类
当我们定义一个工具类的时候,期望这个工具实例能够作用于多个类型对象,这时候对于工具类中共性方法可以用到Object来代替个性化,等到实际传入的时候再进行转型应用,但是这样判断方法实际作用的类型会很麻烦,这时候就可以用用泛型。
- 泛型类定义
要定义一个泛型类,需要在类声明的时候传入占位符,从而扩展类的使用,实例代码如下。
package generic;
public class GenericDemo<T> {
/*
* 属性
*/
private T t;
/*
* 构造方法
*/
public GenericDemo() {
// TODO Auto-generated constructor stub
}
public GenericDemo(T t){
this.t = t;
}
/*
* get/set方法
*/
public T getObj(){
return this.t;
}
public void setObj(T t){
this.t = t;
}
/*
* 希望应用于多个类型的普通方法
*/
public T getDifferentType(T t1, T t2){
/*
* 示例代码
*/
if(t2.hashCode() > t1.hashCode())
return t2;
return t1;
}
}
- 泛型类继承
如果需要继承一个定义了泛型的类,需要遵守以下两点:
1. 如果子类不带有泛型,则父类的泛型占位符必须传入实际类型(传入的实际类型只能是引用类型,而不能是基本类型,所有泛型使用都遵照这一规则),否则编译不通过;
2. 子类带继续有泛型,则子类和父类的泛型类型占位符必须保持一致,这表示子类和父类拥有相同的泛型类型,否则编译失败;
规则示例代码如下:
package generic;
/*
* 1. 子类带有泛型,则父类的泛型类型占位符必须传入实际类型,否则编译失败;
*/
public class GenericDemo_Son extends GenericDemo<Object>{
}
package generic;
/*
* 2. 子类带继续有泛型,则子类和父类的泛型类型占位符必须保持一致,这表示子类和父类拥有相同的泛型类型,否则编译失败;
*/
public class GenericDemo_Son<T> extends GenericDemo<T>{
}
三、泛型接口
泛型接口的定义方法和泛型类相同,一个类实现具有泛型类的接口也遵照泛型类继承的两个规则,实例代码如下。
package generic;
public interface GenericInterfaceDemo<T> {
public abstract Class<?> showType(T t);
}
package generic;
/*
* 1. 如果实现接口的类不带有泛型,则接口中的泛型占位符必须传入实际类型,否则编译不通过;
*/
public class GenericImpl implements GenericInterfaceDemo<String>{
@Override
public Class<?> showType(String t) {
// TODO Auto-generated method stub
return t.getClass();
}
public static void main(String[] args) {
System.out.println(new GenericImpl().showType("zhangsan"));
}
}
package generic;
/*
* 2. 如果实现接口的类继续带有泛型,则接口和实现类必须传入相同的泛型占位符,否则编译不通过;
*/
public class GenericImpl<T> implements GenericInterfaceDemo<T>{
@Override
public Class<?> showType(T t) {
// TODO Auto-generated method stub
return null;
}
}
四、泛型方法
泛型方法比泛型类或泛型接口更加精细、更加适用于更多的场景,泛型类的泛型约束了整个类,在实例化的时候就约束了对象的实际类型;但是泛型方法的泛型只约束了方法,包含泛型方法的类或泛型类在实例化之后依旧能够保持方法的泛型特性。
- 泛型方法的定义
定义泛型方法,只需要在方法返回值之前声明泛型,示例代码如下。
<span style="white-space:pre"> </span>/*
* 反省方法的定义:
* 只需要在方法返回之前声明泛型
*/
public <T> void showGF(T t){
System.out.println(t);
}
泛型方法比泛型类和泛型接口有更精细、更广泛的应用场景,如以下示例代码所示。
package generic;
public class GenericFunctionDemo<T> {
/*
* 泛型类的方法,在泛型类实例化的时候就失去了泛型特性
*/
public void showGC(T t){
System.out.println(t);
}
/*
* 泛型方法,就算泛型类实例化,依旧能保持泛型特性
*/
public <T> void showGF(T t){
System.out.println(t);
}
public static void main(String[] args) {
GenericFunctionDemo<String> gfd = new GenericFunctionDemo<>();
/*
* 调用泛型类方法showGC,此时随着泛型类的实例化只能传入String类型的参数
*/
gfd.showGC("okc");
/*
* 调用泛型方法showGF,就算泛型类实例化,依旧能保持泛型特性
*/
gfd.showGF(new Integer(9));
gfd.showGF("zhangsan");
gfd.showGF(new Object());
}
}
- 静态泛型方法
因为泛型类或泛型接口中的泛型会在实例化或被实现的时候才传入确定类型,因此泛型类或泛型接口的泛型不能应用于静态方法,示例代码如下。
package generic;
public class GenericFunctionDemo<T> {
/*
* 因为静态先于对象实例存在,因此当静态方法存在的时候,方法中的泛型还未被确定
* 因此该方法无法通过编译
* 结论:泛型类或泛型接口中的泛型不能应用于静态方法
*/
public static void show(T t){
System.out.println(t);
}
}
但是泛型方法的泛型在使用方法的时候就确定,因此静态方法也可以具有泛型,静态泛型方法的定义遵守反省方法定义的准则,即在方法返回值之前声明泛型,示例代码如下。
package generic;
public class GenericFunctionDemo<T> {
/*
* 静态反省方法:静态泛型方法的定义遵守反省方法定义的准则,即在方法返回值之前声明泛型
*/
public static <T> void show(T t){
System.out.println(t);
}
public static void main(String[] args) {
GenericFunctionDemo.show("zhangsan");
GenericFunctionDemo.show(new Integer(10));
}
}
- 泛型方法和通配符
泛型通配符主要应用于泛型上限下限中。当定义泛型的时候,如果不能确定泛型类型,则可以使用泛型通配符"?"来占位置,大多时候泛型通配符和反省方法都可以互换,当两者之间有一个鲜明的特点:泛型占位符和通配符都可以在声明的时候进行占位,但占位符在声明之后可以根据声明类型进行操作,而占位符则不可以,示例代码如下。
package generic;
import java.util.ArrayList;
import java.util.Iterator;
Iterator<?> it = list.iterator();</li></ul>public class GenericFunctionDemo<T> {
/*
* 使用泛型通配符
*/
public void myIterator(ArrayList<?> list){
while(it.hasNext())
System.out.println(it.next());
}
/*
* 通配符和占位符的区别
*/
public <T> void myIterator2(ArrayList<T> list){
Iterator<T> it = list.iterator();
while(it.hasNext()){
/*
* 使用占位符,则后续可以操作占位符所代表的数据类型
* 使用通配符,则不可以操作
*/
T t = it.next();
System.out.println(t);
}
}
}
五、泛型的上下限
- 为什么要泛型上下限
为什么需要泛型上下限?
查看这样一种情景,声明一个ArrayList<T>,则该ArrayList在实例化的时候泛型占位符将会传入实际类型,如果Man继承类Person,那么ArrayList<Man>是否是ArrayList<Person>的子类?答案是否定的,ArrayList<Man>并不是ArrayList<Person>的子类,而是和ArrayList<Person>同等地位,因为泛型只作用于编译期,在实际运行的时候会被编译器擦除,因此在编译器看来这是两个不相等、没有关系的ArrayList实例,因此ArrayList<Person>中并不能存入Man实例,只能存入Person实例。
那么,如何定义一个ArrayList的泛型,使得具有继承关系的类型都可以传入,就如上例中的,Man继承了Person,那么Man和Person的实例均可以传入,因为无论是Man的实例对象还是Person的实例对象,都是Person类型,这时候就需要泛型上下限。如以下代码所示,Man继承了Person。
package generic;
public class Person {
private String name;
public Person(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name;
}
}
package generic;
public class Man extends Person {
private String name;
public Man(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name;
}
}
- 泛型下限
1. 泛型下限的声明:ArrayList<? super T>;
2. 泛型下限的含义:表示该ArrayList<? super T>的声明可以接受的类型为new ArrayList<T及其父类类型>,即ArrayList<? super T> arrayList = new ArrayList<T及其父类>();,此时因为java是单继承,所以T只能有一个直接父类,故而这个ArrayList是允许放入数据的,但是存取数据的时候,字节码里面存放的checkcast指令类型是T,按照java数据类型转换的语法,T的子类可以自动转换成T,而父类需要强转。
所以,java泛型上下限要分布两部分开来,一是声明赋值部分,二是数据存取部分。
- 泛型上限
1. 泛型上限的声明:ArrayList<? extends T>;
2. 泛型上限的含义:表示该ArrayList<? extends T>的声明可以接受new ArrayList<T及其子类类型>,即ArrayList<? extends T> arrayList = new ArrayList<T及其子类类型>;,但是T可以有多个子类类型,而且这些子类数据类型相互是不能转换的,所以,这个arrayList里面可以放多个数据类型,这个java泛型最初类型限定的初衷相违背的,因此实际上这个arrayList不能进行存取操作,一旦发生存取操作,就会编译器异常。
如下例,Son继承了Man,Man继承了Person,<? extends Man>泛型上限(Man最高限制),只能传入Man的子类Son,<? super Man>泛型下线(Man是最低限制),只能传入Man的父类Person。
package generic;
public class Person {
private String name;
public Person(String name) {
// TODO Auto-generated constructor stub
this.name = name;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name;
}
}
package generic;
public class Man extends Person {
private String name;
public Man(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name;
}
}
package generic;
public class Son extends Man{
private String name;
public Son(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
// TODO Auto-generated method stub
return this.name;
}
}
package generic;
import java.util.ArrayList;
import java.util.Iterator;
public class GenericLimitDemo {
/*
* 泛型上限:只要是Man的子类都可以传入
*/
public void myUpperIterator(ArrayList<? extends Man> list){
Iterator<? extends Man> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next().getClass());
}
}
/*
* 泛型下线:只有Man的父类可以传入
*/
public void myLowerIterator(ArrayList<? super Man> list){
Iterator<? super Man> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next().getClass());
}
}
public static void main(String[] args) {
ArrayList<Son> sonList = new ArrayList<>();
sonList.add(new Son("son1"));
sonList.add(new Son("son2"));
ArrayList<Person> perList = new ArrayList<>();
perList.add(new Person("per1"));
perList.add(new Person("per2"));
GenericLimitDemo demo = new GenericLimitDemo();
demo.myUpperIterator(sonList);//上限,传子类
demo.myLowerIterator(perList);//下限,传父类
}
}
附注:
本文如有错漏,烦请不吝指正,谢谢!