Java中的泛型与数组平时开发用的很多,除了偶尔遇到"NullPointerException"和"IndexOutOfBoundsException"一般也不会遇到太大问题。可是如果深入研究,可能会发现这两种类型有很多奇怪的特点。我查了一些资料,发现包括《Java编程思想》在内对这些问题的解释都含糊其辞(不排除是本人理解能力有限)。因此在大量实践的基础上,我只能提出自己的对这些问题的理解并总结下来。

一、数组转型

准备工作

创建三个表示继承关系的类,供测试使用:

class Fruit {
} class Apple extends Fruit {
} class RedApple extends Apple {
}

数组能够转型吗?

// 测试:数组转型
public void arrayTransform() {
Fruit[] fruits = new Apple[10];
Fruit[] newFruits = new Fruit[10];
newFruits[0] = new Apple();
newFruits[1] = new Apple();
Apple[] apples = (Apple[]) newFruits; // 抛出异常java.lang.ClassCastException
}

编译期不报错,运行期抛异常。而且这个错误与newFruits所持有的实际对象无关。如果要强制转型只能遍历数组来完成。要理解这个问题本质,我们需要首先了解Java的数组内存模型。

数组的内存模型

数组转型,JVM只会检查它的直接类型。上面的例子中,对于Apple[]而言Fruit[]是它的父类,因此可以直接转型。而向下转型的时候,相当于Fruit转型Apple。因此在运行期报错。不过事情似乎不那么简单,Fruit[]真的是Apple[]的父类吗?

public void arraySuperclass() {
Fruit[] fruits = new Fruit[10];
Apple[] apples = new Apple[10];
System.out.println(fruits.getClass());
System.out.println(apples.getClass());
System.out.println(fruits.getClass().getSuperclass());
System.out.println(apples.getClass().getSuperclass());
}
/* Output:
class [LFruit;
class [LApple;
class java.lang.Object
class java.lang.Object
*/

从输出看,似乎数组之间并没有相互的继承关系。那么这样的转型到底是如何实现的呢?根据JVM的规范,数组类型是由虚拟机在运行时创建的,控制台输出的前两行即代表数组类型。同时JVM规范也定义了,数组的父类型为数组元素的父类型。对于数组,我们的总结如下:

  1. 数组类型只和声明类型相关与它的实际持有类型无关。
  2. 数组类的继承关系与声明类型的继承关系保持一致。

二、泛型通配符

extends的含义

public void genericExtends(List<? extends Fruit> ls) {
ls.add(new Apple()); // 编译报错
Fruit f = ls.get(0);
}

使用extends修饰的泛型容器只能调用get()方法,并且genericExtends方法可以接受List<Fruit>、List<Apple>和List<RedApple>类型的参数。

Java泛型是一种妥协的产物,实际在泛型内部无法知晓实际类型。Java为了实现泛型采用一种被称为“擦除”的机制,并且这种机制在编译期完成。这样做的目的是为了和老版本的.class文件保持兼容。extends本质是一种擦除的约束条件,表示List<? extends Fruit>对象将会被擦除成List<Fruit>,因此在方法内部开发人员可以调用在Fruit的所有方法。但是必须明确一点,List<? extends Fruit>与List<Fruit>绝不一样。这一点和数组类型相似,任何以Fruit的子类为泛型的容器类(如:List<Apple>)并不是Fruit容器类(如:List<Fruit>)的子类。如果必须要说它们之间存在什么共性的话就是以Fruit的子类为泛型的容器类可以在编译期被擦除为Fruit的容器类。要如何实现这一点呢?Java提出了在泛型中利用extends作为修饰符。因此,List<? extends Fruit>声明的本质是告诉编译期,请尝试把方法的参数(如:List<Apple>)擦除成List<Fruit>而不要直接擦除成List<Object>,如果成立则编译通过。

因此在genericExtends方法的内部,ls表示它可能为List<Fruit>、List<Apple>和List<RedApple>之中的任何一个类型。因此ls不能使用add方法想容器中添加元素。

super的含义

public void genericSuper(List<? super Apple> ls) {
ls.add(new Apple());
ls.add(new RedApple());
} public void methInvok() {
List<Fruit> fruits = new ArrayList<>();
this.genericSuper(fruits);
}

如果希望在方法内部可以向容器中添加元素,则推荐的做法是使用super修饰泛型。以它作为泛型修饰符的方法形参,从外部看表示:我可以接收以泛型声明类自身或所有父类为泛型的容器类,如(List<Apple>或List<Fruit>)。它相当于容器添加了一个下界。从内部看表示:参数ls能够接收所有泛型声明类自身或为基类的对象,如(Apple 或 RedApple)。

关于泛型通配符,我们总结如下:

  1. 包含通配符的泛型对象在编译期会被擦除,利用extends和super能够为为擦除行为增加约束。
  2. 通配符表示为某一种明确的类型,具体类型只有在运行期可知。

Java泛型与数组深入研究的更多相关文章

  1. 《徐徐道来话Java》(2):泛型和数组,以及Java是如何实现泛型的

    数组和泛型容器有什么区别 要区分数组和泛型容器的功能,这里先要理解三个概念:协变性(covariance).逆变性(contravariance)和无关性(invariant). 若类A是类B的子类, ...

  2. Java泛型数组

    文章来自http://blog.csdn.net/orzlzro/article/details/7017435 Java 不支持泛型数组.也就是说, List<String>[] ls ...

  3. Java 泛型数组

    Java 不支持泛型数组.也就是说, List<String>[] ls = new ArrayList<String>[10]; 是不支持的,而 List<String ...

  4. Java 泛型 泛型数组

    Java 泛型 泛型数组 @author ixenos 先给结论 不能(直接)创建泛型数组 泛型数组实际的运行时对象数组只能是原始类型( T[]为Object[],Pair<T>[]为Pa ...

  5. java 泛型详解(普通泛型、 通配符、 泛型接口,泛型数组,泛型方法,泛型嵌套)

    JDK1.5 令我们期待很久,可是当他发布的时候却更换版本号为5.0.这说明Java已经有大幅度的变化.本文将讲解JDK5.0支持的新功能-----Java的泛型.  1.Java泛型  其实Java ...

  6. Java 泛型 五:泛型与数组

    简介 上一篇文章介绍了泛型的基本用法以及类型擦除的问题,现在来看看泛型和数组的关系.数组相比于Java 类库中的容器类是比较特殊的,主要体现在三个方面: 数组创建后大小便固定,但效率更高 数组能追踪它 ...

  7. (转载)Java里新建数组及ArrayList java不允许泛型数组

    java中新建数组: String[] s;//定义的时候不需要设置大小 s = new String[5];//为数组分配空间时就要设置大小   对于ArrayList, ArrayList< ...

  8. Java泛型总结

    1. 什么是泛型?泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的 ...

  9. java泛型应用实例 - 自定义泛型类,方法

    近 短时间需要使用泛型,就研究了下,发现网上的问关于泛型的文章都是讲原理的, 很少有提到那里用泛型比较合适, 本文就泛型类和泛型方法的使用给出两 个典型应用场景. 例如一个toString的泛型方法, ...

随机推荐

  1. 给服务添加路由--ingress

    kind: NamespaceapiVersion: v1metadata:  name: demo-webshell  generateName: demo-webshell  labels:    ...

  2. 吴裕雄--天生自然TensorFlow2教程:单输出感知机及其梯度

    import tensorflow as tf x = tf.random.normal([1, 3]) w = tf.ones([3, 1]) b = tf.ones([1]) y = tf.con ...

  3. WLC-生成CSR操作

    1.生成CSR [req]req_extensions = v3_req[ v3_req ]# Extensions to add to a certificate requestbasicConst ...

  4. 【译】索引进阶(十七): SQL SERVER索引最佳实践

    [译注:此文为翻译,由于本人水平所限,疏漏在所难免,欢迎探讨指正] 原文链接:传送门. 在本章我们给出一些建议:贯穿本系列我们提取出了十四条基本指南,这些基本的指南将会帮助你为你的数据库创建最佳的索引 ...

  5. vue 和 jquery混合使用

    有时候只要想到要用的 vue.js 的时候就会惯性的想起用vue-cli手脚架搭建一个项目,但是有时候的业务场景并不适合用vue-cli手脚架,这个时候使用vue+jquery混合使用,把他们的优点结 ...

  6. git分支在项目中管理

    实际项目中如何使用Git做分支管理 2018年06月24日 18:08:24 ShuSheng007 阅读数:9241   版权声明: https://blog.csdn.net/ShuSheng00 ...

  7. ajax的XmlHttpRequest对象常用方法

    onreadystatechange用于检测readyState状态的改变,当readyState的状态发生改变的时候调用回调

  8. SVN安装不成功,提示Invalid driver H:

    本来我的SVN安装在H盘,后来我把包含H盘的硬盘下下来了,这样H盘就不存在了. 这时候我想重新安装SVN,点击安装包,结果提示Invalid driver H,怎么都不能安装成功. 这时候我去注册表里 ...

  9. letter-spacing 与 word-spacing 结合使用,造成文字反转

    文字未反转时,如图: 文字反转时,如图: 以上效果只是因为发现记录下来,目前并无实用,也许未来用得着它.

  10. php 安装 swoole

    1.安装swoole cd /usr/local/src wget https://github.com/swoole/swoole-src/archive/v1.9.1-stable.tar.gz ...