泛型的定义和使用

来源:互联网 发布:apache tomcat安装教程 编辑:程序博客网 时间:2024/06/05 15:07

一、泛型

1、编译期确定类型安全——泛型(Generics)

泛型是提供给Javac编译器使用的。可以限定集合中输入的类型,让编译器在编译期间避免原始程序的非法输入,编译器编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样,由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法添加自己不同的对象即可,这样便可以实现动态内容变化化的数组。

ArrayList类定义和ArrayList类引用中涉及如下术语:

泛型类型: 整个ArrayList<E>

类型参数(类型变量): E

参数化的类型: 整个ArrayList<Integer>

类型参数的实例: ArrayList中的Integer

typeof: ArrayList<Integer>中的<Integer>

原始类型: ArrayList

2、参数化类型与原始类型的兼容性

参数化类型可以引用一个原始类型的对象,编译报告警告,例如,   

Collection<String> c = new Vector ();

原始类型可以引用一个参数化类型的对象,编译报告警告,例如,

Collection c = new Vector<String>();

参数化类型不考虑类型参数的继承关系:

Vector<String> v = new Vector<Object>()    // 错误Vector<Object> v = new Vector<String>()    // 也错误

3、类型擦除

正确理解泛型概念的首要前提是理解类型擦除(type erasure)。 Java中的泛型类似于C++中的模板,但是这种相似性仅限于表面,Java中的泛型基本上都是在编译器这个层次来实现的。属于编译器执行类型检查和类型诊断,然后生成普通的非泛型的字节码,也就是在生成的Java字节代码中是不包含泛型中的类型信息的,使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这种实现技术称为类型擦除。如在代码中定义的List<Object>List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。

很多泛型的奇怪特性都与这个类型擦除的存在有关,包括:

泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class;

静态变量是被泛型类的所有实例所共享的。对于声明为MyClass的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。

泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException<String>MyException<Integer>的。对于JVM来说,它们都是MyException类型的。也就无法执行与异常对应的catch语句。

4、泛型的定义与使用

我们使用一个泛型首先要定义它,其次就是使用它进行泛型实例化。
一般来说我们有几种方式去确定泛型类别:

声明时确定

运行时延后赋值确定

泛型界限确定基本类型

/** 泛型界限*/static class Apple<T extends InputStream> {        T data;}/**正常的泛型*/  static class Orange<T>{        T data;}    /**     * 泛型的实例化 1:声明确定         2:运行时确定          3:泛型界限 + 运行时确定      *      * @throws IOException     */public static void test3() throws IOException {        /**         * 1:声明确定         */        Orange<InputStream> orange = new Orange<>();        //此处编译不通过,因为声明时泛型参数化不会区分继承关系,属于精确性的声明。        //Orange<InputStream> orange = new Orange<FileInputStream>();        //此处报错,因为String并不是InputStream类型        //orange.data = new String();        //虽然是创建赋值,但这里也没有报错。此处是由于继承关系,FileInputStream也是InputStream,而且自动向上转型为InputStream,但它与泛型界限并不相同,泛型界限限制了T只能是某一个类别的派生,但没有界限的继承则可以在声明的时候修改T至任何一个类别,继承自这些类别的类也都可以被向上转型赋值,它的范围要广很多。        orange.data = new FileInputStream(new File("123.txt"));        System.out.println("1、声明确定:"+orange.data.getClass().getName());        System.out.println();        /**         * 2:运行时确定         */        Orange orange2 = new Orange<>();        //此处编译通过,因为没有在构造的时候初始化泛型类型,因此泛型是由编译器决定(也就是根据赋值决定)。        orange2.data = new String();        System.out.println("2、运行时确定:" + orange2.data.getClass().getName());        orange2.data = new FileInputStream(new File("123.txt"));        System.out.println("2、运行时确定:" +orange2.data.getClass().getName());        System.out.println();        /**         * 3:泛型界限 + 运行时确定         */        Apple apple = new Apple<>();        // 报错,因为String不是继承自InputStream        // apple.data = new String();        // 编译通过,泛型在编译期确定类型,因为ObjectInputStream是继承自InputStream,因此赋值成功        apple.data = new BufferedInputStream(new FileInputStream("123.txt"));        System.out.println("3、泛型界限 + 运行时确定:" +apple.data.getClass().getName());        apple.data = new FileInputStream("123.txt");        System.out.println("3、泛型界限 + 运行时确定" +apple.data.getClass().getName());    }

程序输出结果:

1、声明确定:java.io.FileInputStream

2、运行时确定:java.lang.String
2、运行时确定:java.io.FileInputStream

3、泛型界限 + 运行时确定:java.io.BufferedInputStream
3、泛型界限 + 运行时确定java.io.FileInputStream
从上面的结果我们可以看出,泛型只是我们躲过编译器编译的一个手段,让我们的程序在运行时拥有更强大的灵活性,可以在编译期间对泛型,但它也意味着我们的程序很有可能在运行时出现错误(转型失败引起程序崩溃)。

转载
:http://blog.csdn.net/woshimalingyi/article/details/53171959#3%E7%B1%BB%E5%9E%8B%E6%93%A6%E9%99%A4

原创粉丝点击