小东吖 之 java 泛型

来源:互联网 发布:广电网络机顶盒wifi 编辑:程序博客网 时间:2024/05/18 19:23

一、泛型的基本概念

泛型的定义:泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。

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

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

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

使用泛型机制编写的程序代码要比那些杂乱的使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类来说尤其有用。

泛型程序设计(Generic Programming)意味着编写的代码可以被很多不同类型的对象所重用。

// 首先创建一个集合ArrayList string=new ArrayList();  //可以向数组中添加任何类型的对象         strings.add(1);         //获取值时必须强制转换             String str=(String) string.get(0);           //上述强制转型编译时不会出错,而运行时报异常java.lang.ClassCastException  

这样的实现面临两个问题:

1、当我们获取一个值的时候,必须进行强制类型转换。

2、假定我们预想的是利用string来存放String集合,因为ArrayList只是维护一个Object引用的数组,我们无法阻止将Integer类型(Object子类)的数据加入string。然而,当我们使用数据的时候,需要将获取的Object对象转换为我们期望的类型(String),如果向集合中添加了非预期的类型(如Integer),编译时我们不会收到任何的错误提示。但当我们运行程序时却会报异常:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Stringat generic.BeforeGeneric.main(BeforeGeneric.java:24)

这显然不是我们所期望的,如果程序有潜在的错误,我们更期望在编译时被告知错误,而不是在运行时报异常。

针对利用继承来实现通用程序设计所产生的问题,泛型提供了更好的解决方案:类型参数。例如,ArrayList类用一个类型参数来指出元素的类型。
例如:

//<String> 声明 集合中保存的元素是什么类型的        ArrayList<String> arrayList = new ArrayList<String>();        arrayList.add("a");        arrayList.add("b");        arrayList.add("c");        arrayList.add("d");        //编译错误  因为要保存的是String类型的        //泛型可以将 运行时的错误转到编译期        stringValues.add(1); 

二.泛型类的定义和使用

// 泛型类直接在类名<>声明// 因为在Work<T>中,T是一个无限定的类型变量public class Worker<T> {    private T t;    public T getT() {        return t;    }    public void setT(T t) {        this.t = t;    }}

现在我们就可以用这个泛型类了:

    public static void main(String[] args) {      // 泛型类的对象创建        Worker<String> worker = new Worker<>();        worker.setT("haha");        System.out.println(worker.getT());        }
EWorker类引入了一个类型变量T,用尖括号<>括起来,并放在类名的后面。泛型类可以有多个类型变量

三.泛型接口的定义和使用

定义泛型接口和泛型类差不多,看下面简单的例子:

泛型分配符

向下限定:?extends E:可以接收E类型或者E的子类型对象。
只能接收子类的元素

向上限定:?super E:可以接收E类型或者E的父类型对象。
接收父类以上的元素

// 定义接口// 直接在接口名后 声明泛型interface interA<T>{// 抽象方法    public abstract void show(T t);}// 实现类来实现接口class InterAImpl implements interA<String>{    @Override    public void show(String s) {        System.out.println(s);    } public static void main(String[] args) {        InterAImpl impl = new InterAImpl();      impl.show("你好");}

四.泛型方法的定义和使用

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

   要定义泛型方法,只需将泛型参数列表置于返回值之前,就像下面这样:
public class Worker<T> {    private T t;    public T getT() {        return t;    }    public void setT(T t) {        this.t = t;    }    // 泛型的成员方法    // 除了T类型 我这里可不可以写别的泛型    // 这里W没有被赋类型 所以报错    // 咱们需要标识出来 在调用这个方法 创建这个W类型    public<W> void sayHi(W w) {        System.out.println(w);    }    // 静态方法的泛型    // 泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数    // 这个T是静态方法自己的泛型    public static<E> void print(E t) {    }    public void work() {        System.out.println("我是工人 我很开心");    }}