泛型的定义和使用
来源:互联网 发布: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>.clas
s或是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
- 泛型的定义和使用
- 泛型定义和使用
- 全局变量的定义和使用
- 全局变量的定义和使用
- friend的定义 和 使用
- 常量的定义和使用
- 结构的定义和使用
- Arraylist的定义和使用
- 方法的定义和使用
- 包的定义和使用
- Block的定义和使用
- 指针的定义和使用
- 注解的定义和使用
- 枚举的定义和使用
- 类和对象的定义和使用
- 泛型的定义与使用
- 泛型方法的定义与使用
- 泛型接口的定义与使用
- 字典序(今日头条2017秋招真题)
- Metasploit与渗透测试简介
- 关于es启动的相关报错
- 垃圾回收机制
- spark stage的划分和task分配
- 泛型的定义和使用
- 进程与线程之间那些事儿:
- 简单的数据库操作与变量
- 【bzoj2789】[Poi2012]Letters
- effective C++ 条款二十六解读
- Java8源码-LinkedHashMap
- jackson annotations注解详解
- uVa1586 元素周期表
- shell命令