泛型是一个使用<>(尖括号的位置在使用泛型类的类名后面,ClassName)引起来的参数类型,既然是参数,一方面起到占位作用,另一方面只在具体使用时才会确定具体的类型。需要注意,泛型的类型只能时引用数据类型,不能是基本数据类型。并且,只有这个引用类型不确定,而其他操作时确定的。
Jdk1.5前使用Object占位(代表所有要接收或返回的数据类型),之后使用泛型,即泛型替代Object的作用。
默认情况,集合可以存储任意类型的数据元素(底层Object类型数组),但这种方式方便存储,但不利于后续处理【接收数据和查询数据】。在实际编码实践中,集合中往往存储相同数据类型的元素。基于此目的,集合类设计为支持泛型,在声明时直接指定集合中要存储的数据类型,在编译阶段可检查集合中的数据元素是否正确,同时后续处理集合元素也相对方便。因此泛型主要有以下优势
-
类型检查,保证类型安全【接收数据】
使用泛型替代Object接收元素时,泛型被指定后,可以在编译阶段完成对对元素类型进行检查,保证类型安全。
-
消除强制类型转换【获取数据】
使用Object接收不同类型数据后,取值时需要进行强制类型转换。譬如某不使用泛型的集合中主要存储字符串,但也存了一个整型,在取值时需要对这个整型强制转换成字符串。
在使用时,jdk1.7之后泛型,等号右侧的尖括号内的类型可省略。即Arraylist<String> al = new ArrayList<>();
继承中的泛型
-
父类指定泛型
父类指定泛型类型时,此时子类可直接使用泛型类型。子类不必使用泛型语法。/** * 父类使用了泛型,但子类继承父类时,父类指定了泛型类型,即相当于给形参位置传入了实参 * 此时子类不必在使用泛型语法,直接使用即可。 */ class GenericSon1 extends GenericTest<String>{ }
-
父类不指定泛型
/** * 父类使用了泛型,但子类继承父类时,父类仍未指定了具体的泛型类型,即相当于传入了实参 * 此时子类需要使用泛型语法 */ class GenericSon2<E> extends GenericTest<E>{ }
泛型类/接口
-
同一个泛型类可以有多个泛型参数,即ClassName<A,B,C,D>
-
泛型类的构造器同一般类的构造器使用方法一致,不必在构造器的方法名后面添加尖括号
public ClassName(){} //正确写法 public ClassName<>(){} //错误写法。不必加尖括号
-
若多个泛型类的泛型类型不同时,这些泛型类不能相互赋值
ArrayList<String> a1 = null; ArrayList<Integer> a2 = null; a1 = a2; //编译不通过。底层定义的数组类型就不同。
-
在声明时,若泛型类不指定具体的泛型参数,则泛型会被擦除,泛型对应的类型为Object类型。
若某个类在定义时被定义为泛型类,即
ClassName<E>
,但在使用该类创建对象时,没有显示指定E对应的引用类型ClassName cn = new ClassName()
,那么该泛型类会被认为一般的类,即所谓泛型擦除。//ArrayList本身是一个泛型类,但此处未指定泛型类型, //底层的数组被认为存储Object类型,即可以存储任意类型 ArrayList l = new ArrayList(); //idea提示add方法的形参为 Object o l.add(1); l.add(true); l.add(1.23); //正确用法。执行泛型类型 ArrayList<Integer> l = new ArrayList<Integer>(); //idea提示add方法的形参为 Integer i l.add(1); l.add(2); l.add(true); //编译出错,只能添加Integer类型 l.add(1.23);//编译出错,只能添加Integer类型
-
泛型类中的静态方法不能使用类的泛型
类的泛型是一个形参,只有在创建该类时,指定具体的泛型类型(实参)后,才确定具体的数据元素的数据类型。而静态方法可以直接使用类名调用,不必创建对象调用。若在静态方法中使用泛型,此时泛型的引用类型尚未明确,不可能操作一个未明确的引用类型,逻辑不通。
-
不能使用泛型类型创建数组
E[] e = new E[10];//该方式错误。 E[] e = (E)new Object[10];//正确。变相方式创建泛型数组。
泛型方法
泛型方法的表示同样使用尖括号包裹一个大写字符,并且它位于方法访问修饰符和返回值之间,之后表示任意类型的T可以出现在泛型方法的任意地方。
/**
*泛型方法的标识<T>
*/
public <T> String test(){
return "泛型方法";
}
需要特别注意泛型方法和泛型类的一般方法的区别。
public class GenericTest<E>{
//虽然使用了E,但func1不是泛型方法
public void func1(E e){
XXX
}
//泛型方法。明显的泛型方法标识<T>
public <T> void func2(T t){
XXX
}
}
泛型类要在实例化的时候就指明类型,若在运行过程中需要换一种类型,需要重新创建泛型类。而泛型方法是在调用时指定泛型的具体类型。泛型方法进一步延迟指定类型的时机,减少硬编码,更加灵活。
public class GenericTest<E> {
public void a(E e){
};
//泛型方法可以使用static
public static <T> void b(T t){}
//泛型方法
public <T> void c(T t){}
public static void main(String[] args) {
GenericTest<String> o = new GenericTest<>();
//泛型类的一般方法
o.a("123");
//泛型方法可以接收任意类型
o.c(11);
o.c(12.11);
o.c(true);
}
}
泛型通配符
Father类和Son类存在父子关系,Son类可以继承Father类,基于多态,编译器允许程序将Son类的实例赋值给Father引用。但ClassName 和 ClassName 这两个泛型类不存在继承关系,他们两个是相互独立的,没有任何关系,因此编译器不允许将 ClassName实例赋值给ClassName引用。
Father f;
Son s = new Son();
f = s; //正确
ClassName<Father> ff;
ClassName<Son> fs = new ClassName<>();
ff = fs; //编译报错
泛型参数存在继承关系,但泛型类之间不存在继承关系,即List和List是完全不同的类型。当某一个方法的操作逻辑相同,当形参是不同的泛型时,需要重载多个方法,即需要写d1 d2 d3三个方法。此时可以使用泛型通配符,只需一个方法d4即可接收所有泛型类型。其基本语法为ClassName<?>
,此时可以认为 ClassName<?>是其他所有泛型类,如ClassName、ClassName等的父类。
//该方法只能接收List<Object>泛型类
public void d1(List<Object> l ){
操作逻辑
}
//该方法只能接收List<String>泛型类
public void d2(List<String> l ){
操作逻辑
}
//该方法只能接收List<Integer>泛型类
public void d3(List<Integer> l ){
操作逻辑
}
//该方法可以接收List<Object>、List<String>、List<Integer>等多种泛型类
//减少硬编码,实现解耦
public void d4(List<?> l ){
操作逻辑
}
使用泛型通配符的方法,内部遍历时使用Object接收每一个元素
public void d4(List<?> l ){
for(Object 0 : l){
XXX
}
}
泛型通配符实现接收数据的统一,但由此导致写入和读取操作有所限制。
public void d4(List<?> l ){
//1 接收元素或遍历集合时使用Object对象
for(Object 0 : l){
XXX
}
Object oo = l.get(0);
//2 无法确定通配符?代表的数据类型,不能随便添加元素
list.add(123);//编译出错
}
泛型受限
泛型通配符建立了泛型类(其泛型参数存在继承关系)的继承关系,但泛型通配符是全部匹配,而泛型受限进一步缩小匹配范围.
泛型上限
其语法为 ClasName<? extends ClassNamePara>,它可以认为是所有泛型参数为ClassNamePara类的子类的泛型类的父类,也是所有泛型参数是ClassNamePara类的泛型类的父类。类似数学中的 小于等于 关系。
//以下是三个独立的泛型类
//其泛型参数存在继承关系 son extends father; father extends Object
ClassName<Son> s = new ClassName<Son>();
ClassName<Father> f = new ClassName<Father>();
ClassName<Object> o = new ClassName<Object>();
//sx可以认为是所有泛型参数是Father子类的泛型类的父类
ClassName<? extends Father> sx = null;
//f实例对应的泛型类的泛型参数是Father,则sx是其父类
//基于多态,父类引用指向子类对象
sx = f; //编译通过
//s实例对应的泛型类的泛型参数是Son,Son是Father的子类,则sx可认为是s的父类
//基于多态,父类引用指向子类对象
sx = s;//编译通过
//sx 和 o 是不同的类实例
sx = o; //编译不通过。
泛型下限
其语法为 ClasName<? super ClassNamePara>,它可以认为是所有泛型参数为ClassNamePara类的父类的泛型类的父类,也是所有泛型参数是ClassNamePara类的泛型类的父类。类似数学中的 大于等于 关系。
//以下是三个独立的泛型类
//其泛型参数存在继承关系 son extends father; father extends Object
ClassName<Son> s = new ClassName<Son>();
ClassName<Father> f = new ClassName<Father>();
ClassName<Object> o = new ClassName<Object>();
//sx可以认为是所有泛型参数是Father子类的泛型类的父类
ClassName<? super Father> ss= null;
//f实例对应的泛型类的泛型参数是Father,则ss是其父类
//基于多态,父类引用指向子类对象
sx = f; //编译通过
//o实例对应的泛型类的泛型参数是Object,Object是Father的父类,则ss可认为是o的父类
//基于多态,父类引用指向子类对象
sx = o;//编译通过
//sx 和 o 是不同的类实例
sx = s; //编译不通过。
需要注意的是,泛型上下限是指泛型参数类型的上下限,extends 指向子类链路方向,此时泛型参数类型为类型上限。super指向父类链路方向,此时泛型参数类型为类型下限,其对应的泛型类始终是层级较高的父类。