【6】Java泛型:泛型的基本概念和使用

现在开始深入学习Java的泛型了,以前一直只是在集合中简单的使用泛型,根本就不明白泛型的原理和作用。泛型在java中,是一个十分重要的特性,所以要好好的研究下。

一、泛型的基本概念

1、什么是泛型?

泛型的定义:泛型==参数化类型。
泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型。

2、什么是参数化类型?

参数可以简单分为:

  1. 形参
  2. 实参
  3. 类型形参
  4. 类型实参

参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。

3、为什么会出现泛型?

泛型思想早在C++语言的模板(Templates)中就开始生根发芽,在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。例如在哈希表的存取中,JDK 1.5之前使用HashMap的get()方法,返回值就是一个Object对象,由于Java语言里面所有的类型都继承于java.lang.Object,那Object转型为任何对象成都是有可能的。但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object到底是个什么类型的对象。在编译期间,编译器无法检查这个Object的强制转型是否成功,如果仅仅依赖程序员去保障这项操作的正确性,许多ClassCastException的风险就会被转嫁到程序运行期之中。

4、真实泛型

泛型技术在C#和Java之中的使用方式看似相同,但实现上却有着根本性的分歧,C#里面泛型无论在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,List与List就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为真实泛型。

5、伪泛型

Java语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原始类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList与ArrayList就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。(类型擦除在后面在学习);

泛型确实是在编译阶段起作用,在生成的class文件中就没有了泛型信息,但是运行时是怎么找到原本的类型信息的呢?其实还是强制类型转换,编译器将之前用到从泛型对象的地方自动添加了强制类型转换。以前是自己做,现在是编译器帮忙做了

6、引入泛型的意义

使用泛型机制编写的程序代码要比那些杂乱的使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类来说尤其有用。 泛型程序设计(Generic Programming)意味着编写的代码可以被很多不同类型的对象所重用。

7、泛型的基本术语

以ArrayList和ArrayList做简要介绍:

  1. 泛型类型:整个成为ArrayList泛型类型;
  2. 参数化的类型:整个ArrayList 称为参数化的类型;
  3. 类型参数:ArrayList中的 E称为类型变量或者类型参数;
  4. 实际类型参数:ArrayList中的integer称为类型参数的实例或者实际类型参数;
  5. typeof Integer:ArrayList中的称为typeof Integer;
  6. 原始类型:ArrayList称为原始类型;
    对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

二、泛型变量的类型限定

在上面,我们简单的学习了泛型类、泛型接口和泛型方法。我们都是直接使用这样的形式来完成泛型类型的声明。有的时候,类、接口或方法需要对类型变量加以约束。看下面的例子:
有这样一个简单的泛型方法:

public static  T get(T t1,T t2) {  
        if(t1.compareTo(t2)>=0);//编译错误  
        return t1;  
    } 

因为,在编译之前,也就是我们还在定义这个泛型方法的时候,我们并不知道这个泛型类型T,到底是什么类型,所以,只能默认T为原始类型Object。所以它只能调用来自于Object的那几个方法,而不能调用compareTo方法。

可我的本意就是要比较t1和t2,怎么办呢?这个时候,就要使用类型限定,对类型变量T设置限定(bound)来做到这一点。我们知道,所有实现Comparable接口的方法,都会有compareTo方法。所以,可以对做如下限定:

public static  T get(T t1,T t2) { //添加类型限定  
        if(t1.compareTo(t2)>=0);  
        return t1;  
    }  

类型限定在泛型类、泛型接口和泛型方法中都可以使用,不过要注意下面几点:
1、不管该限定是类还是接口,统一都使用关键字 extends
2、可以使用&符号给出多个限定,比如:

public static extends Comparable&Serializable> T get(T t1,T t2)  

3、如果限定既有接口也有类,那么类必须只有一个,并且放在首位置

public static extends Object&Comparable&Serializable> T get(T t1,T t2)  

三、通配符的使用

通配符有三种:

  1. 无限定通配符 形式

1、泛型中的?通配符

如果定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,如果这样写:

import java.util.ArrayList;  
import java.util.Collection;  
import java.util.List;  
publicclass GernericTest {  
    publicstaticvoid main(String[] args) throws Exception{  
        List listInteger =new ArrayList();  
        List listString =new ArrayList();  
        printCollection(listInteger);  
        printCollection(listString);      
    }   
    publicstaticvoid printCollection(Collection collection){  
               for(Object obj:collection){  
            System.out.println(obj);  
        }    
    }  
}   
  

运行报错:

Exception in thread "main" java.lang.Error: Unresolved compilation problems: 
    The method printCollection(Collection<Object>) in the type test4 is not applicable for the arguments (List<Integer>)
    The method printCollection(Collection<Object>) in the type test4 is not applicable for the arguments (List<String>)

    at test4.main(test4.java:12)

这是因为泛型的参数是不考虑继承关系就直接报错。
这就得用?通配符

 publicstaticvoid printCollection(Collection collection){  
               for(Object obj:collection){  
            System.out.println(obj);  
        }  
    }
在方法public static void printCollection(Collection collection){}
中不能出现与参数类型有关的方法比如collection.add();

因为程序调用这个方法的时候传入的参数不知道是什么类型的,但是可以调用与参数类型无关的方法比如collection.size();

总结:使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量的主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。

2、泛型中的?通配符的扩展

1:界定通配符的上边界
Vector1> x = new Vector<类型2>();
类型1指定一个数据类型,那么类型2就只能是类型1或者是类型1的子类
VectorNumber> x = new Vector();//这是正确的
VectorNumber> x = new Vector<String>();//这是错误的
2:界定通配符的下边界
Vector super 类型1> x = new Vector<类型2>();
类型1指定一个数据类型,那么类型2就只能是类型1或者是类型1的父类
Vector super Integer> x = new Vector();//这是正确的
Vector super Integer> x = new Vector();//这是错误的

提示:限定通配符总是包括自己

参考致谢:
1、java泛型(一)、泛型的基本介绍和使用
http://blog.csdn.net/lonelyroamer/article/details/7864531
2、Java 泛型通配符?解惑
http://blog.csdn.net/baple/article/details/25056169

你可能感兴趣的:(JAVA学习)