为什么

当传入对象不明确时,我们一般用多态实现根据传入对象来调用重写方法(以Animal为例,我们需要对需要操作的对象声明为它们的父类,如Animal的voice中定义形参为Animal类,这样可以传入Cat、Dog等子类,甚至要定义成共同父类Object)如:

//定义一个类,其属性为Object类

//实例化对象

这可能会导致:

  1. 装入数据的类型都被当作Object对待(往往不只是Animal类而是更宽容的Object类),从而“丢失”自己的实际类型
  2. 获取数据时需要强制类型转换,可能出错,因此还要手动类型检查(instanceof),效率低

是什么

泛型就是参数化类型,使用广泛的类型

有啥用

  1. 在编译的时候自动检查类型安全,比如存入的类型与实例化时指定的类型不同。
  2. 所有的强转都是自动和隐式的,提高代码的重用率。

分类

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

泛型类

不能使用基本类型,如:

Student //Integer不能写成int

声明类时使用泛型,实例化时指定类型(若不指定则可传入任意类型,但这样它就不会自动转换和检查类型,失去了泛型的意义)。

  • 一个最普通的泛型类:常用的占位符: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 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也与外部类的不同
     */
}

获取泛型参数的实际类型

  • 参考文章