java泛型

     JDK1.5中最显著变化之一就是添加对泛型类型的支持。所谓的泛型就是指在对象建立时不指定类中属性的具体类型,而由外部在声明及实例化对象时指定类型。

一、为什么要用泛型

     一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。

     在没有泛型之前,由于JAVA中的类都是源自java.long.Object,这意味着所有java对象能装换成Object。因此在此之前的JDK版本中,为了实现编程上的灵活性,很多时候是将我们传入的对象向上转型使用Object对象接受。但是在向上转型的过程中,无可避免的丢失掉了原有类型的身份信息(属性、方法),在后续的使用过程中,我们还面临着对象的身份确认和强制转型带来的风险。这就为我们开发过程中引入了类型安全问题,而要想解决以上问题就可以使用泛型技术。

      java泛型的核心内容,就是告诉编译器指定容器想要持有什么类型的对象,然后由编译器进行类型检查,保证类型的正确性。

      泛型的引入还带来了附带的好处,就是消除了源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错的机会。

二、泛型的应用

1、普通泛型类中的应用

      泛型可以解决数据类型的安全问题,其主要原理是在类声明时通过一个标识表示类中某个属性的类型或是某个方法的返回值及其参数类型。这样在类声明或实例化时只要制定好需要的类型即可。泛型类定义如下:

     [访问权限]   class 类名称<泛型类型标识1,泛型类型标识2,...,泛型类型标识N>{

              [访问权限]    泛型类型标识   变量名称;

              [访问权限]    泛型类型标识   方法名称(){};

              [访问权限]    返回值类型声明    方法名称(泛型类型标识  变量名称){};

     }

泛型对象定义:类名称<具体类>  对象名称 = new 类名称<具体类>();下面是一个具体事例:

java泛型
 注意:在泛型的指定中是无法指定基本数据类型的,必须设置成一个类,这样在设置一个基本类型时就必须使用包装类。

2、类中指定多个泛型类型

       如果一个类中有多个属性需要使用不同的泛型声明,则可以在声明类时指定多个泛型类型。具体的实现例子如下:

java泛型

3、泛型应用中的构造方法

      构造方法可以为类中的属性初始化,如果类中的属性通过泛型指定,而又需要通过构造设置属性内容时,构造函数的定义与之前并无不同,不需要声明类那样指定泛型,具体格式通过下面这个例子理解:

java泛型
4、泛型方法

      我们看到的上述例子,都是应用于整个类(或接口)上。但同样可以在类中包含参数化方法,而这个方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是否是泛型没有关系

      泛型方法使得该方法能够独立于类而产生变化。以下是一个基本的指导原则:无论何时,只要你能做到,你就应该尽量使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类泛型化,那么就应该只是用泛型方法,因为他可以使得事情更清楚明白。另外,对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。案例如下:

java泛型
输出结果为:java.lang.Integer     java.lang.String

注意:①当使用泛型类时,必须在创建对象时指定类型参数的值,而使用泛型方法时,通过不必指明参数类型,因为编译器会为我们找出具体的类型。这称为参型参数推断。

②类型推断只对赋值操作有效,其他时候并不起作用。如果你将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行类型推断。这这种情况下,编译器默认为:调用泛型方法后,其返回值被赋给一个Object类型的变量。

③在泛型方法中,可以显示的指明类型,不过这种语法很少使用。要显示的指明类型,必须在点操作符与方法名之间插入尖括号,然后把类型置于尖括号内。如果在定义该类方法的类的内部,必须要在点操作符之前使用this关键字,如果是使用static方法,必须在点操作符之前加上类名。

④可变参数与泛型方法的可变参数列表能很好的共存,如以下例子:

java泛型
 输出结果为:[a, b, c]。非常神奇吧!!!

三、受限类型的由来和使用

1、类型擦除深入探讨

     在这里必须残忍的告诉大家:在泛型代码内部,无法获得任何有关泛型参数类型信息。java的泛型是使用擦除实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此List<String>和List<Integer>在运行时实际上都是相同的类型,这两种形式都被擦除成为他们的“原生”类型,即List。

      由于有了擦除,java编译器无法将传递的泛型类型映射到使用泛型类的类的内部。使用泛型类的类在使用泛型的过程中,在其内部泛型占位的地方,就知道这是一个对象。其他有关泛型类的信息一无所知,泛型类对应类型的属性信息也就无法发挥任何作用了。

     为了解决类型擦除的问题,java引入了泛型边界。通过泛型边界辅助泛型类,以此告知编译器只能接受这个边界的类型,然后编译器就能够将该边界类型映射到泛型占位符,因此在类的内部可以使用此边界类型信息。

注意:泛型在使用的过程中,存在很多边界处理。这里讲到的泛型类型擦除实际影响到的是泛型的第一边界。如果在使用的过程中,应用了泛型边界限制,则边界类型会替换掉泛型实际类型,即用引入的第二边界(边界类)替换了第一边界(传递类)。如果是普通的泛型类(无边界限制),那么他们第二边界就是Object根类。

2、受限泛型

      上面讲述了受限泛型的由来,下面就具体看看受限泛型是如何操作的。

      在应用传递中,在泛型操作中也可以设置一个泛型对象的范围上限和范围下限。范围上限使用extends关键字声明,表示参数化的类型可能是所有指定的类型或者此类型的子类;而范围下限使用super进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类,或是Object类。

①类型的上限

设置方式为:[访问权限]   类名称<泛型标识  extends  类>{  }。现在假设一个方法中只能接受的泛型对象只能是数字(Byte、Short、Long、Integer、Float、Double)类型,此时在定义方法参数接受对象时,就必须指定泛型的上限。以为所有的数据包装类都是Number类的子类,具体实例如下:

java泛型
 以上代码Info类中泛型的范围就只能是所有的数字,如果此声明的泛型对象是Number的子类,则肯定不会有任何问题;如果声明的不是数字类型,则肯定会抛出异常。

②泛型的下限

设置方式为:[访问权限]   类名称<泛型标识  super   类>{  }。当使用的泛型只能是本类及其父类时,就必须使用泛型的范围下限进行配置。案例如下:

java泛型

以上fun()方法中,Info进行了下限的配置,所以只能接受泛型是String及Object类型的引用。所以,一旦此时传递了其他泛型类型的对象,编译时就会出错。

四、通配符

     以上程序中在操作时都设置了一个固定的类型,在泛型操作中可以通过通配符接收任意指定泛型类型的对象。

     在开发中对象的引用传递是最常见的,但是如果在泛型类的操作中,在进行引用传递时泛型类型必须匹配才可以传递,否则无法传递,如下面的代码:

java泛型
      以上程序尽管String是Object类的子类,但是在引用传递时也同样无法进行操作,如果此时让程序正确执行,可以将fun()方法中定义的Info<Object>修改为Info,即不指定泛型。虽然这样程序可以正常运行,但是有些不妥当,所以解决这个问题,java中引入了通配符“?”,表示可以接受此类型的任意泛型对象。以上代码可以修改如下:

java泛型

五、泛型与子类继承的限制

 一个类的子类可以通过对象多态性为其父类实例化,但是在泛型操作中,子类的泛型类型是无法使用父类的泛型类型接受的,例如:Info<String>不能使用Info<Object>接收。例如:

java泛型
java泛型
       以上错误提示的含义是,不匹配类型,即Info<String>无法转换为Info<Object>。虽然String是Object类的子类,但是在泛型操作中此概念无效,此时只能用?接收。

六、泛型接口

      在JDK1.5之后,不仅可以声明泛型类,还可以声明接口。声明泛型接口和声明泛型类语法相似,也是在接口名称后面加上<T>,例如:

java泛型
       泛型接口的实现有两种方式,一种是直接在子类后声明泛型,另一种是直接在子类实现的接口中声明地给出泛型类型。两种方法的实例如下:

①在程序泛型接口的子类声明了与泛型接口中同样的泛型标示符。这个并不难理解,虽然指定了泛型,但是依然可以使用对象的多态性通过一个子类为接口实例化。如下:

java泛型
 ②直接在接口中指定具体类型。这样子类已经明确的指定了具体类型。如下:

java泛型
七、泛型数组

     在泛型类中,不能创建泛型数组。一般的解决方案是在任何想要创建泛型数组的地方使用ArrayList。这是由于数组将跟踪他们的实际类型,而这个类型在数组被创建时确定的。

      在使用泛型方法时,也可以传递或返回一个泛型数组。但这里隐含了数组类型转换,即创建了一个确定类型的新数组,而在传递的过程中,进行了类型擦除。代码如下:

java泛型
      但是还是要谨记,我们依然不能声明 T[]  array = new T[size]。但是可以通过转型实现,这也是泛型数组唯一可以成功创建的方式。即 : T[]  array = (T[]) new  Object[size];这一点在泛型方法接受泛型数组时,就已经自动发生,完成了隐含转换。