JDK源码分析(2)之 Array 相关
在深入了解 Array 之前,一直以为 Array 比较简单,但是深入了解后才发现其实挺复杂的。所以我把重要的写在最前面,但凡遇到和语言本身相关的问题,都可以查阅 Java Language and Virtual Machine Specifications
一、Array 是一个是对象吗?
首先可以肯定的是,数组是一个对象;但是在推导的过程中还是有些难以理解的问题,比如对于任意一个引用对象A
,
- 数组是协变的,所以
Object[]
是A[]
的父类,即Object[] o = A[]
; - 数组是一个对象,所以数组的父类是
Object
,即Object oo = o
; - 那么
A[]
、Object[]
和Object
是什么关系呢?
是这样吗?
我们可以通过反射来观察一下:
private static void test05() {Object[] o = new String[2];System.out.println(o.getClass().getName());System.out.println(o.getClass().getSuperclass().getName());String[] s = (String[]) o;System.out.println(s.getClass().getSuperclass().getName());Object oo = s;
}打印:
[Ljava.lang.String;
java.lang.Object
java.lang.Object
可以看见A[]
和Object[]
的直接父类都是Object
,所以他们之间的关系也一定不是上图中的多继承关系,那么数组协变产生的关系一定不同于extends
关键字产生的关系;
extends
关键字产生的继承关系是怎么定义呢?
这里我们可以从《Virtual Machine Specifications》中找到答案:
// ClassFile 结构
ClassFile {u4 magic;u2 minor_version;u2 major_version;u2 constant_pool_count;cp_info constant_pool[constant_pool_count-1];u2 access_flags;u2 this_class;u2 super_class;u2 interfaces_count;u2 interfaces[interfaces_count];u2 fields_count;field_info fields[fields_count];u2 methods_count;method_info methods[methods_count];u2 attribute_count;attributes_info; attributes[attributes_count];
}
可以看到extends
关键字产生的继承关系是记录在class文件中的super_class
里面的。我这里还没有在 JDK 源码里面找到数组协变关系的产生,但是可以猜想这个应该是后来加的类似语法关系的结构。这里先留着以后看源码的时候确认吧。
二、Array 的 length 域相关
在准备看Array源码的时候,我直接就点开了java.lang.reflect.Array
,后来才知道这根本不是Array的源码,看包名就知道,这是使用反射操作数组的一些方法。Array
的class是在运行过程中动态生成的。
那么在Array
的class中到底包含了什么呢?在很多的资料中都写了,Array
中有类似public final int length
的成员变量。但是在《Java Language Specifications》10.1. Array Types
中明确写了,length不是类型的一部分;
- An array's length is not part of its type.
private static void test06() {String[] s = new String[2];System.out.println(s.length);System.out.println(s.getClass().getDeclaredFields().length);try {System.out.println(s.getClass().getDeclaredField("length"));} catch (NoSuchFieldException e) {System.out.println(e.toString());}
}打印:
2
0
java.lang.NoSuchFieldException: length
可以看到length
并不是Array
的成员变量,那么length
是从哪里来的呢?
同样我们可以从ClassFile
结构中找打答案;
可以看到Array
的length
信息是记录在对象头中的,而读取length
信息的时候,是使用的arraylength
字节码指令来读取的。
三、Array 的创建流程
// 数组创建的几种形式
String[] s = {"a", "b", "c"}; // 初始化器
String[] s1 = new String[3]; // 有维度表达式
String[] s2 = (String[]) Array.newInstance(String.class, 3); // 有维度表达式
数组创建流程
是否有维度表达式:无:创建的时候每个元素递归深入初始化,失败则退出变量类型检查 -> 与数组类型不兼容 -> 编译错误不是可具化类型(如:null) -> 编译错误 空间不足 -> OutOfMemoryError有:创建的时候,从左向右地计算,任意维度表达式计算失败则退出检查所有维度值,有小于0 -> NegativeArraySizeException 分配空间,若空间不足 -> OutOfMemoryError只有一个维度表达式,创建一维数组,每个元素初始化化为初始值有n个维度表达式,执行深度为n-1的循环
四、协变数组
1. 逆变与协变
逆变与协变用来描述类型转换(type transformation)后的继承关系
如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类)
- f(⋅)是逆变的,当A≤B时有f(B)≤f(A)成立;
- f(⋅)是协变的,当A≤B时有f(A)≤f(B)成立;
- f(⋅)是不变的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。
正因为数组是协变的,所以Object[] o = new A[];
2. 为什么要设计为协变数组
有种看法认为这是在泛型产生之前的妥协产物,比如在 JDK5 之前还没有泛型,但是很多地方需要用泛型来解决,比如:
// java.util.Arrays
public static boolean equals(Object[] a, Object[] a2) {if (a==a2)return true;if (a==null || a2==null)return false;int length = a.length;if (a2.length != length)return false;for (int i=0; i<length; i++) {Object o1 = a[i];Object o2 = a2[i];if (!(o1==null ? o2==null : o1.equals(o2)))return false;}return true;
}
最后调用的是Object.equals()
方法,但是不想全部都重写equals
,这里最简单的就是让数组实现协变的特性;
3. 为什么不能使用泛型数组
这里简单的讲是因为泛型是不变的,而数组是协变的,所以不能使用泛型数组;
// 如果泛型也是协变的
private static void test07() {List<Object> list = new ArrayList<String>(); // 原本会编译出错list.add(123);List<String> list1 = list;String s = list1.get(0); // 类型错误
}
可以看到如果泛型也是协变的,那么Collection 在存取数据的时候,就会产生类型转换错误;
4. 为什么数组可以是协变的
private static void test07() {Object[] o = new String[2];o[0] = 123;
}运行时:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
可以看到数组,在存数据的时候,还会检查数据类型是否兼容,所以数组可以是协变的。
五、数组在 java 和 c++ 中的区别
- C++ 中的数组只是一个指针,java 中的数组是一个对象
- java 中访问数组会有额外的范围检查
- java 中会确保数组被初始化
六、Array 和 ArrayList的效率对比
private static final int SIZE = 50000;
private static final Random RANDOM = new Random();private static void test_array() {System.out.println("Array:");long start = System.currentTimeMillis();String[] s = new String[SIZE];for (int i = 0; i < SIZE; i++) {s[i] = i + "";}System.out.println("insert:" + (System.currentTimeMillis() - start)); start = System.currentTimeMillis();for (int i = 0, len = SIZE * 10; i < len; i++) {String ss = s[RANDOM.nextInt(SIZE)];}System.out.println("get:" + (System.currentTimeMillis() - start));
}private static void test_list() {System.out.println("ArrayList:");long start = System.currentTimeMillis();List<String> list = new ArrayList<>(SIZE);for (int i = 0; i < SIZE; i++) {list.add(i + "");}System.out.println("insert:" + (System.currentTimeMillis() - start));start = System.currentTimeMillis();for (int i = 0, len = SIZE * 10; i < len; i++) {String s = list.get(RANDOM.nextInt(SIZE));}System.out.println("get:" + (System.currentTimeMillis() - start));
}打印:
Array:
insert:13
get:10
ArrayList:
insert:7
get:22
对比可以看到,数组的插入和随机访问效率都要比ArrayList高,但是一般建议优先使用列表,只有在优先考虑效率的时候才考虑使用数组,因为
- 数组是协变的不能使用泛型
- 数组是具体化的,只有在运行时才知道元素的类型
七、总结
在看数组的时候,因为class是动态创建的,所以看了很久,但是根据数组的特性,基本可以认为数组的域和方法,类似于:
class A<T> implements Cloneable, java.io.Serializable {public final int length = X;public T[] clone() {try {return (T[]) super.clone();} catch (CloneNotSupportedException e) {throw new InternalError(e.getMessage());}}
}
转载于:https://www.cnblogs.com/sanzao/p/10098974.html
JDK源码分析(2)之 Array 相关相关推荐
- 【JDK】JDK源码分析-HashMap(1)
概述 HashMap 是 Java 开发中最常用的容器类之一,也是面试的常客.它其实就是前文「数据结构与算法笔记(二)」中「散列表」的实现,处理散列冲突用的是"链表法",并且在 J ...
- 【JDK】JDK源码分析-CountDownLatch
概述 CountDownLatch 是并发包中的一个工具类,它的典型应用场景为:一个线程等待几个线程执行,待这几个线程结束后,该线程再继续执行. 简单起见,可以把它理解为一个倒数的计数器:初始值为线程 ...
- StringBuffer类【JDK源码分析】
StringBuffer类[JDK源码分析] 前言 推荐 说明 StringBuffer类 基本信息 属性 构造方法 部分方法 length capacity append insert revers ...
- JDK源码分析(2)LinkedList
JDK版本 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedList 实现 ...
- LIRE原理与源码分析(二)——相关接口
1. LIRE原理与源码分析(二)-- 代码结构 2. LIRE原理与源码分析(二)-- 相关接口 上一篇文章介绍了LIRE的基本内容和源码的代码结构.本文针对LIRE中主要的三个接口(LireFea ...
- JDK源码分析 NIO实现
总列表:http://hg.openjdk.java.net/ 小版本:http://hg.openjdk.java.net/jdk8u jdk:http://hg.openjdk.java.net/ ...
- JDK源码分析-Integer
Integer是平时开发中最常用的类之一,但是如果没有研究过源码很多特性和坑可能就不知道,下面深入源码来分析一下Integer的设计和实现. Integer: 继承结构: -java.lang.Obj ...
- jdk源码分析书籍 pdf_如何阅读源码?
点击上方"IT牧场",选择"设为星标" 技术干货每日送达! 阅读源码是每个优秀开发工程师的必经之路,那么这篇文章就来讲解下为什么要阅读源码以及如何阅读源码. 首 ...
- jdk源码分析书籍 pdf_什么?Spring5 AOP 默认使用Cglib?从现象到源码深度分析
推荐阅读: 阿里工作十年拿下P8,多亏了这些PDF陪我成长(Spring全家桶+源码解析+Redis实战等)zhuanlan.zhihu.com 从入门到熟悉,一步一步带你了解 MySQL 中的「索 ...
- JDK源码分析 FutureTask源码分析
文章目录 前言 一.Callable接口 二.Future接口 三.FutureTask源码分析 3.1 Future继承结构图 3.2 参数介绍 3.3 构造函数 3.4. FutureTask的A ...
最新文章
- vue中使用markdown富文本,并在html页面中展示
- ​网页图表Highcharts实践教程之图表代码构成
- 【译】用Fragment创建动态的界面布局(附Android示例代码)
- .net webapi 接收参数_FastReport.Net报表设计器如何连接到SQLCe
- javaee实验报告心得_准大四学生七月青软实训总结
- 2018-2019 ICPC Northwestern European Regional Programming Contest (NWERC 2018)
- 新手开车 驾驶小秘诀要牢记
- 轻触开源(一)-Java泛型Type类型的应用和实践
- Intel Skylake (Server) 架构/微架构/流水线 (2) - 体系结构新特性
- python中的ix是啥_pandas中ix的使用详细讲解
- VS2015常用快捷键总结
- .NET 实现异步处理的集中方式
- mysql sys cpu_MySQL SYS CPU高的案例分析(一)
- python __file__怎么实现_python怎么实现文件上传界面
- (6)二进制文件方式部署Kubernetes高可用集群----------安装Docker Engine
- 信号检测与估计-理论与应用_信号R-实时应用
- matlab如何用二分法求函数零点,用二分法求函数零点的步骤.PPT
- 了解uni-app只需这一篇就足够了
- Excel的Sumif、Sumifs求和结果错误
- html 手动添加thead,HTML DOM Table createTHead() 方法