Java基础-泛型(上)
来源:互联网 发布:d3.min.js 下载 编辑:程序博客网 时间:2024/06/06 13:13
泛型由来的动机
通过Object转型问题引入.早期的Object类型可以接收任意的对象类型,但是在实际的使用中,会有类型转换的问题。也就存在这隐患,所以Java提供了泛型来解决这个安全问题。
好处
1.提高安全性(将运行期的错误转换到编译期)
2.省去强转的麻烦
未使用泛型
public static void main(String[] args) {ArrayList list = new ArrayList();list.add(123);//自动装箱list.add(456);list.add("789");//可以添加任何Object类型,存在安全隐患Iterator iterator = list.iterator();while (iterator.hasNext()) {Integer integer = (Integer) iterator.next();//需强制转换System.out.println(integer);}}/*outPut: * 123 * 456 * Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer */
使用泛型后
public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();list.add(123);//自动装箱list.add(456);//list.add("789"); 编译错误Iterator<Integer> iterator = list.iterator();while (iterator.hasNext()) {Integer integer = iterator.next();//无需强转System.out.println(integer);}}/*outPut: * 123 * 456 * 456 */
很明显,泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换操作:编译器保证了这些类型转换的绝对无误。
相对于依赖程序员来记住对象类型、执行类型转换——这会导致程序运行时的失败,很难调试和解决,而编译器能够帮助程序员在编译时强制进行大量的类型检查,发现其中的错误。
注意事项:
泛型类型必须是引用类型
前后的泛型必须一致,或者后面的泛型可以省略不写(1.7的新特性菱形泛型)
泛型的构成
由泛型的构成引出了一个类型变量的概念。根据Java语言规范,类型变量是一种没有限制的标志符,产生于以下几种情况:
泛型类声明
泛型接口声明
泛型方法声明
泛型构造器(constructor)声明
泛型类和接口
如果一个类或接口上有一个或多个类型变量,那它就是泛型。类型变量由尖括号界定,放在类或接口名的后面:
public interface List<T> extends Collection<T> {//TODO}
简单的说,类型变量扮演的角色就如同一个参数,它提供给编译器用来类型检查的信息。
Java类库里的很多类,例如整个Collection框架都做了泛型化的修改。例如,最开始使用的ArrayList就是一个泛型类。在那段代码里,list是一个ArrayList<Integer>对象,它是一个带有一个Integer类型变量的ArrayList的类实现的实例。编译器使用这个类型变量参数在get方法被调用、返回一个Integer对象时自动对其进行类型转换。
泛型方法和构造器
非常的相似,如果方法和构造器上声明了一个或多个类型变量,它们也可以泛型化
public static <T> T get(List<T> list) {//TODO}
这个方法将会接受一个List<T>类型的参数,返回一个T类型的对象。
泛型的子类
class Fruit {}class Apple extends Fruit {}class Strawberry extends Fruit {}class FujiApple extends Apple {}
看上面的继承体系,我们可以知道:FujiApple(富士苹果)是Apple的子类型,Apple是Fruit(水果)的子类型,那么FujiApple(富士苹果)当然也是Fruit(水果)的子类型
//编译通过Apple apple = new FujiApple();Fruit fruit = apple;
那么如果是泛型呢?
ArrayList<Apple> apples = new ArrayList<>();ArrayList<Fruit> fruits = apples; //编译错误
很遗憾,编译错误。答案可能会出乎你的意料:没有任何关系。用更通俗的话,泛型类型跟其是否子类型没有任何关系。
为什么一个苹果是一个水果,为什么一箱苹果不能是一箱水果?
在某些事情上,这种说法可以成立,但在类型(类)封装的状态和操作上不成立。如果把一箱苹果当成一箱水果会发生什么情况?
ArrayList<Apple> apples = new ArrayList<>();ArrayList<Fruit> fruits = apples; //编译错误fruits.add(new Strawberry());
如果可以这样的话,我们就可以在list里装入各种不同的水果子类型,这是绝对不允许的。
另外一种方式会让你有更直观的理解:一箱水果不是一箱苹果,因为它有可能是一箱另外一种水果,比如草莓(子类型)。
看到这里,你是否想起了数组的使用
//编译通过Apple[] apples = new Apple[5];Fruit[] fruits = apples;
数组和泛型类型上用法的竟然不一致?那么我们往里加入strawberrie(草莓)对象试试
public static void main(String[] args) {//编译通过Apple[] apples = new Apple[5];Fruit[] fruits = apples;fruits[0] = new Strawberry();}/*outPut: * Exception in thread "main" java.lang.ArrayStoreException: Strawberry */
真的可以编译通过,但是在运行时抛出ArrayStoreException异常(试图将错误类型的对象存储到一个对象数组时抛出的异常)。因为数组的这特点,在存储数据的操作上,Java运行时需要检查类型的兼容性。这种检查,很显然,会带来一定的性能问题,你需要明白这一点。
重申一下,泛型使用起来更安全,能“纠正”Java数组中这种类型上的缺陷。
也许你会感到很奇怪,为什么在数组上会有这种类型和子类型的关系?如果它们不相关,你就没有办法把一个未知类型的对象数组传入一个方法里(不经过每次都封装成Object[])
public void sort(Object[] obj) {}
泛型出现后,数组的这个个性已经不再有使用上的必要了(下面的通配符),实际上是应该避免使用。
通配符
关键字说明
? 通配符类型
<?extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类
<? super T> 表示类型的下界(超类型限定),表示参数化类型是T或T的超类型(父类型),直至Object
extends示例:
ArrayList<Apple> apples = new ArrayList<>();ArrayList<? extends Fruit> fruits = apples; //编译通过fruits.add(new Strawberry()); //编译错误fruits.add(new Apple()); //编译错误fruits.add(new Fruit()); //编译错误fruits.add(null); //编译通过
Java编译器会阻止你往一个Fruit list里加入strawberry。在编译时我们就能检测到错误,在运行时就不需要进行检查来确保往列表里加入不兼容的类型了。即使你往list里加入Fruit对象也不行。
为什么呢?
这个? extends T 通配符告诉编译器我们在处理一个类型T的子类型,但我们不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,我们就不允许往里面加入任何这种类型的数据。可以添加null,因为null 可以表示任何类型
另一方面,因为我们知道,不论它是什么类型,它总是类型T的子类型,当我们在读取数据时,能确保得到的数据是一个T类型的实例
Fruit fruit = fruits.get(0); //编译通过Apple apple = fruits.get(0); //编译不通过
super示例:
ArrayList<Fruit> fruits = new ArrayList<>(); //编译通过ArrayList<? super Apple> apples = fruits;//编译通过apples.add(new FujiApple()); //编译通过apples.add(new Apple()); //编译通过apples.add(new Fruit()); //编译不通过apples.add(new Object()); //编译不通过apples.add(null); //编译通过
fruits指向的是一个装有Apple的某种超类(supertype)的List。ArrayList<? super Apple> 表示具有任何Apple超类型的列表”,列表的类型至少是一个 Apple类型,因此可以安全的向其中添加Apple及其子类型。如果往里面加入Apple的超类,编译器就会警告你,因为我们不知道它是怎样的超类,所有这样的实例就不允许加入。
从这种形式的类型里获取数据又是怎么样的呢?
Apple apple = apples.get(0); //编译不通过Fruit fruit = apples.get(0); //编译不通过Object obj = apples.get(0); //编译通过
结果表明,你只能取出Object实例:因为我们不知道超类究竟是什么,编译器唯一能保证的只是它是个Object,因为Object是任何Java类型的超类。
存取原则
如果你想从一个数据类型里获取数据,使用 ? extends 通配符
如果你想把对象写入一个数据结构里,使用 ? super 通配符
如果你既想存,又想取,那就别用通配符
同样的有如下总结:
extends 可用于的返回类型限定,不能用于参数类型限定。
super 可用于参数类型限定,不能用于返回类型限定。
带有super超类型限定的通配符可以向泛型对易用写入,带有extends子类型限定的通配符可以向泛型对象读取
- Java基础-泛型(上)
- java基础--继承上
- Java基础(上)
- java基础语法上
- Java基础:数据库(上)
- JAVA编程基础(上)
- Java编程基础(上)
- Java基础语法(上)
- java基础知识点一(上)
- java基础知识点二(上)
- Java基础---IO流(上)
- Java 反射基础(上)
- Java基础知识点(上)
- JAVA的基础语法-上
- Java编程基础(上)
- Java语言基础组成(上)
- Java基础整理-多线程基础(上)
- java面向对象上:java基础语法
- 视频框架Vitamio学习
- 主流脚本编程语言大比拼
- Spark2.0 Pipelines,Java版
- Java代码优化总结
- android MVC,MVP,MVVM概论
- Java基础-泛型(上)
- Android之时间戳转今天、明天、后天、周几
- WIFI的状态判断
- 观察者模式(Observer Pattern)
- Android数据存储五种方式总结
- C语言基础及指针⑩预编译及jni.h分析
- 20160825音频开发之AVAudioRecoder
- gradle关于依赖module编译问题
- ArcGIS runtime SDK for android的学习笔记(序:写在开始之前的话)