为什么
当传入对象不明确时,我们一般用多态实现根据传入对象来调用重写方法(以Animal为例,我们需要对需要操作的对象声明为它们的父类,如Animal的voice中定义形参为Animal类,这样可以传入Cat、Dog等子类,甚至要定义成共同父类Object)如:
//定义一个类,其属性为Object类
//实例化对象
这可能会导致:
- 装入数据的类型都被当作Object对待(往往不只是Animal类而是更宽容的Object类),从而“丢失”自己的实际类型
- 获取数据时需要强制类型转换,可能出错,因此还要手动类型检查(instanceof),效率低
是什么
泛型就是参数化类型,使用广泛的类型
有啥用
- 在编译的时候自动检查类型安全,比如存入的类型与实例化时指定的类型不同。
- 所有的强转都是自动和隐式的,提高代码的重用率。
分类
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
泛型类
不能使用基本类型,如:
Student
声明类时使用泛型,实例化时指定类型(若不指定则可传入任意类型,但这样它就不会自动转换和检查类型,失去了泛型的意义)。
一个最普通的泛型类:常用的占位符:T,任意类型Type,E,集合中任意元素Element,K、V,键值对中的key和value,N,Number数字。
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型;标识可以写多个
//在实例化泛型类时,必须指定T的具体类型/* 但是父类Genrator可以不指定类型,即其后面的可以不写,叫做泛型的擦除,这时统一用Object代替父类中出现的所有T类型
也可以子类、父类同时擦除
反正错误情况只有一种:子类擦除,父类不擦除 要么同时擦除,要么子类>=父类(只擦除子类实际上等同于子类的成员类型都被定死,是<=)
public class Generic{//key这个成员变量的类型为T,T的类型由外部指定 private T key; public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定 this.key = key; } public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外 部指定 return key; }
}
不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。
if(ex_num instanceof Generic) //可能是因为编译(甚至连运行)的时候JVM都无法识别泛型的具体类型,就像泛型数组一样
最典型的泛型类就是各种容器类,如:List、Set、Map。
泛型接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:
//定义一个泛型接口
public interface Generator {public T next();
}
- 就直接把class换成interface就行了
- T就只是个占位符,要叫什么都可以,有人习惯写Item
实现泛型接口的类可选择传入或不传入实参(指定或不指定类型)
不传入泛型实参
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中,必须跟它一样是T
* 即:class FruitGenerator implements Generator{
* 如果不声明泛型,如:class FruitGenerator implements Generator,编译器会报错:"Unknown class"
*
*/
class FruitGenerator implements Generator{
public T next() {
return null;
}
}
传入泛型实参
/**
* 传入泛型实参时:
*所有使用泛型的地方都要替换成传入的实参类型
* 即:Generator,public T next();中的的T都要替换成下面传入的String类型。
*/
public class FruitGenerator implements Generator {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
public String next() {
}
}
父类的原属性类型随父类而定,子类的新属性随子类而定
方法重写随父类而定
泛型通配符
同一种泛型可以对应多个版本(因为参数类型是不确定的),但是不同版本的泛型类实例是不兼容的。也即是传入泛型类的泛型实参为父类时,子类并不能传入。如:
public void showKeyValue1(Generic obj){
}
Generic gInteger = new Generic(123);
showKeyValue(gInteger);//编译器会为我们报错:Generic cannot be applied to Generic
因此我们需要一个在逻辑上可以表示同时是Generic和Generic父类的引用类型。由此类型通配符应运而生。
将上面的方法改一下:
public void showKeyValue1(Generic> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}
类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参。
此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。
可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。
不能在类上使用?只能在声明时使用?,不能在使用时使用。因为?表示声明时不定,使用时确定。
test(stu)编译报错:使用时不能用?型,使用时要确定
不能写Student> stu = new Student> ();//因为new就是使用时
泛型方法与Class泛型类class对象
泛型方法,是在调用方法的时候指明泛型的具体类型 。
public T genericMethod(Class tClass){ //public与返回值中间非常重要,
可以理解为声明此方法为泛型方法。T表明返回值为泛型T
T instance = tClass.newInstance();
return instance;
}
- 其中Class表示泛型类的class对象,如User.class。
public final class Class implements Serializable {
…………
}
当泛型方法出现在泛型类中时:
class GenerateTest{
//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
//由于泛型方法在声明的时候会声明泛型,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public void show_3(E t){
System.out.println(t.toString());
}
//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public void show_2(T t){
System.out.println(t.toString());
}
}
再看一个例子:
public class GenericFruit {
class Fruit{
}
class Apple extends Fruit{
}
class Person{
}
class GenerateTest{
//注意,没有声明为泛型方法,T还是上面从类传进来的T
public void show_1(T t){
}
//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
//由于泛型方法在声明的时候会声明泛型,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public void show_3(E t){
}
//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public void show_2(T t){
}
}
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
GenerateTest generateTest = new GenerateTest();//JDK1.7之后可以省略后一个而达到同样的效果
generateTest.show_1(apple);//apple是Fruit的子类,所以这里可以
//PS:这里apple是传给方法,而前面Integer是传给类,传给类是一种特例。
//上面将GenerateTest指定为Fruit类,那么show_1中应该传入Fruit类,而apple也属于Fruit类
generateTest.show_1(person);//编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
//使用以下两个方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
//使用以下两个方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
泛型方法与可变参数
public void printMsg( T... args){//T可以是某个类,...表示可以传入该类型的无数个参数;只能放在最后
for(T t : args){
Log.d("泛型测试","t is " + t);
}
}
printMsg("111",222,"aaaa","2323.4",55.55);
可变参数处理方法
静态方法与泛型
静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
public class StaticGenerator {
....
....
/**
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"
*/
public static void show(T t){
}
}
泛型方法总结
- 泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则:
无论何时,如果你能做到,你就该尽量使用泛型方法。
- 擦除后编译时不会类型检查
}
test(stu1)编译通过,因为擦除,不会类型检查
test(stu)编译报错
泛型上下边界
- 为泛型添加上边界,即传入的类型实参必须是指定类型的子类型
//改一下非泛型方法
public void showKeyValue1(Generic extends Number> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}
//改一下泛型类
public class Generic{
}
//改一下泛型方法
//必须在权限声明与返回值之间的上添加上下边界,即在泛型声明的时候添加
public T showKeyName(Generic container)//编译器会报错:"Unexpected bound"
public T showKeyName(Generic container){
}
由此可见,泛型的上下边界添加,必须与泛型的声明在一起 。
以上extends改为super则必须传入父类
报错,因为都是Student类,方法签名相同
泛型数组
不能创建一个确切的泛型类型的数组
也就是说下面的这两个例子是不可以的:
List[] ls = new ArrayList[10];
而使用通配符或者不指定是可以的:
List>[] ls = new ArrayList[10];
List[] ls = new ArrayList[10];
下面使用Sun的一篇文档的一个例子来说明这个问题:
List[] lsa = new List[10]; // Not really allowed.
此行编译后擦除类型信息,JVM陷入迷航
Object o = lsa;
Object[] oa = (Object[]) o;
List li = new ArrayList();
li.add(new Integer(3));
oa[1] = li; // 傻傻的让int型也放进来,编译时不报错.Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error: ClassCastException.悲剧了,运行的时候报错!
//而上面两种声明方式在取出时手动强转的时候,转错的话会在编译报错,总比运行才报错的好
- 那么对于元素为泛型类的数组,我们要怎么创建指定类型的数组呢?
List[] listArray=(List[])new List[5]; //先创建容量为5的泛型list数组,再强转为integer型的list数组,最后用integer型的list数组变量接收
泛型的嵌套
- 外部类与内部类的关系
public class Queue implements Iterable{
private T item;
/**
* 普通内部类:不声明泛型的话会自动继承外部类的泛型,声明泛型则即使同为T也与外部类的不同
*/
private class Node{
private T item1=item; //飘红:Incompatible types:required:T,found:T
}
/**
* 静态内部类:不声明不会自动继承外部类的泛型,
* 因为不需要有外部类对象就可以直接用外部类创建内部类对象,
* 此时具体类型还未指定,继承了也没用,所以不会自动继承;
* 声明泛型则即使同为T也与外部类的不同
*/
}
获取泛型参数的实际类型
- 参考文章