不该被忽视的CoreJava细节(四)
令人纳闷的数组初始化细节
这个细节问题我很久以前就想深入研究一下,但是一直没有能够抽出时间,借这系列文章的东风,尽量解决掉这个"心头病"。
下面以一维int
数组为例,对数组初始化方式进行分类。
1) int[] a = new int[2]; a[0] = 1; a[1] = 2;
2) int[] a = new int[]{1, 2};
3) int[] a; a = new int[]{1, 2};
4) int[] a = {1, 2};
这四种初始化方式都是合理的。
但有的时候,题目中会来这么一手。
int[] a; a = {1, 2}; // 编译错误,不符合语法规则
虽然我们能够通过编译器测试其是否正确,但是你不觉得纳闷么?这难道是一个特例?
于是乎,对Java的数组的理解变得更加模糊。本文将深入探究Java数组的机制,解决这个1年内没时间思考的问题。
事实胜于雄辩
曾经我认为直接赋值数组是属于Java常量池技术范畴。通过一段对数组的测试代码,将会证明这是错误观点。
public class ArrayDemo {public static void main(String[] args) {int[] a = {1, 2, 3};int[] b = {1, 2, 3}; System.out.println(a.hashCode()); // 1072840587System.out.println(b.hashCode()); // 959045497System.out.println(a == b); // false} }
明确一点:对象hashCode
值一样,并不一定能说明这两个引用指向同一个对象;反过来,如果hashCode
值不一样,则引用指向的一定是不同的对象。
因此,引用变量a、b指向的是不同的对象。也就是说,Java并没有为直接赋值数组提供运行时常量池技术实现。
在eclipse中写int数组,先声明,后赋值形式,会报错:"数组常量仅能在初始化时使用",如下所示。
为什么在C语言中很容易理解的概念,在Java中却变得如此晦涩难懂?
在C语言中,这个问题的解释很容易。c[]代表一个int
数组,其中数组名c代表数组首地址,是一个常量,不能作为左值。
但是在Java语言规范(可以参考相应文献)中,明确将数组定义为一种引用数据类型,数组名是一个变量,储存数组对象的句柄值。但是,我们知道Java语言是一门高级编程语言,大致属于第三代编程语言(第一代汇编,第二代C,二代半C++,第三代Java,第四代R等),其底层实现依靠C++。建立在C++基础之上的Java很多底层细节都被屏蔽,导致使用者很难一窥究竟,这就是为什么我们可以理解C/C++的基本概念,但是对Java却一头雾水的原因。
这个例子中,似乎数组与字符串等别的对象确实有些不同。
当时想到,既然Collection
(接口)、Collections
(工具类),那么会不会有Array
(接口)、Arrays
(工具类)组合形式,毕竟都是一个没有s,一个有s。用eclipse查看源代码的时候,发现真的有Array
这个类,只不过Array
不是猜想的接口,而是一个普通类。
查看Array
类的源代码,发现其中的方法都是用native
修饰的,意味着Array
类很多内容都是从虚拟机底层实现的。
1 package java.lang.reflect; 2 3 /** 4 * The <code>Array</code> class provides static methods to dynamically create and 5 * access Java arrays. 6 * 7 * <p><code>Array</code> permits widening conversions to occur during a get or set 8 * operation, but throws an <code>IllegalArgumentException</code> if a narrowing 9 * conversion would occur. 10 * 11 * @author Nakul Saraiya 12 */ 13 public final 14 class Array { 15 16 private Array() {} 17 18 public static Object newInstance(Class<?> componentType, int length) 19 throws NegativeArraySizeException { 20 return newArray(componentType, length); 21 } 22 23 public static Object newInstance(Class<?> componentType, int... dimensions) 24 throws IllegalArgumentException, NegativeArraySizeException { 25 return multiNewArray(componentType, dimensions); 26 } 27 // 说明length
字段是在虚拟机底层维护的 28 public static native int getLength(Object array) 29 throws IllegalArgumentException; 30 31 public static native Object get(Object array, int index) 32 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 33 34 public static native boolean getBoolean(Object array, int index) 35 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 36 37 public static native byte getByte(Object array, int index) 38 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 39 40 public static native char getChar(Object array, int index) 41 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 42 43 public static native short getShort(Object array, int index) 44 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 45 46 public static native int getInt(Object array, int index) 47 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 48 49 public static native long getLong(Object array, int index) 50 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 51 52 public static native float getFloat(Object array, int index) 53 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 54 55 public static native double getDouble(Object array, int index) 56 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 57 58 public static native void set(Object array, int index, Object value) 59 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 60 61 public static native void setBoolean(Object array, int index, boolean z) 62 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 63 64 public static native void setByte(Object array, int index, byte b) 65 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 66 67 public static native void setChar(Object array, int index, char c) 68 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 69 70 public static native void setShort(Object array, int index, short s) 71 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 72 73 public static native void setInt(Object array, int index, int i) 74 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 75 76 public static native void setLong(Object array, int index, long l) 77 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 78 79 public static native void setFloat(Object array, int index, float f) 80 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 81 82 public static native void setDouble(Object array, int index, double d) 83 throws IllegalArgumentException, ArrayIndexOutOfBoundsException; 84 85 private static native Object newArray(Class componentType, int length) 86 throws NegativeArraySizeException; // length传入虚拟机底层,由本地方法处理,侧面说明length
字段是JVM底层维护的 87 88 private static native Object multiNewArray(Class componentType, 89 int[] dimensions) 90 throws IllegalArgumentException, NegativeArraySizeException; 91 }
粗略地阅读源代码,看到public static native int getLength();方法,其中有native
关键字,而且在Array
类中并没有length
字段。那么,可以判定Array
类的length
字段一定是JVM在运行时维护的。
不入虎穴,焉得虎子
接下来介绍一些比较细节的问题。如果我们打印数组引用变量中储存的值,会发现一个有趣的东西。
public class ArrayTest {public static void main(String[] args) {Object[] o = new Object[2];System.out.println(o); //[Ljava.lang.Object;@6154283a
String[] str = new String[2];System.out.println(str); //[Ljava.lang.String;@5c1d29c1
Throwable[] t = new Throwable[2];System.out.println(t); //[Ljava.lang.Throwable;@7ea06d25
@SuppressWarnings("rawtypes")Class[] c = new Class[2];System.out.println(c); //[Ljava.lang.Class;@565dd915-----------------------------------------以上均属于 Object[]
类型------------------------------------------
-----------------------------------------以下均属于 基本类型数组------------------------------------------ byte[] b = new byte[2];System.out.println(b); //[B@2b571dffboolean[] bl = new boolean[2];System.out.println(bl); //[Z@64726693short[] s = new short[2];System.out.println(s); //[S@12ac706aint[] i = new int[2];System.out.println(i); //[I@770848b9long[] l = new long[2];System.out.println(l); //[J@40dea6bcchar[] ch = new char[2]; System.out.println(ch); //两个方框System.out.println(ch.getClass().getName()); //[Cchar[] ch = new char[2];ch[0] = 'a';ch[1] = 'b';System.out.println(ch); //abfloat[] f = new float[2];System.out.println(f); //[F@5994a1e9double[] d = new double[2];System.out.println(d); //[D@2d11f5f1
}
}
除了char[]
比较特殊,主要是因为String
内部构造是由char[]
实现的,所以直接打印char[]
对象引用的值时会出现输出的是值和别的数组类型不一样。
为什么数组对象句柄是这种以"[字母@散列码"格式?查看JDK中的jni.h头文件(C++)时,终于明白了其中的缘由。
经过分析,可以得出这样的结论:
以上九种数组类型,数组引用变量存的内容都是符合:[字母@散列码";
以上五种非数组类型。Object
类型、Class
类型、Throwable
类型、String
类型、Array
类型,打印符合Java规范的常见格式:类限定名@散列码。
还有一个,曾经我在找数组的length
字段到底从哪儿来的?
分析的情况无非两种:1)从父类继承来的; 2)自己拥有的。
遗憾的是,并不能从任何地方找到有length
属性,这是判断倾向于自己拥有的。可是,好像又有什么地方不对劲,自己拥有的,在哪里?
看过jni.h源代码后,我才发现,原来直接赋值数组的length
字段是虚拟机运行时维护的。
真相到底是什么
绕了个大弯,我们还没有解决掉文章最初的那个问题。为什么直接赋值数组不能以如下方式初始化。
实际上,Java对数组的处理表面上是当成类似引用数据类型,在本质上(虚拟机底层)还是采用C++那套模型。
int[] a; a = {1, 2}; // 编译器认为 a 是一个常量,采用C++模型
int[] a = {1, 2}; // 编译器认为 a 是一个引用变量,采用C++模型
也就是说,Java直接赋值数组其实和C++直接赋值数组在本质上没有什么两样,都认为数组变量是常量。只不过,在Java语言为了面向对象的协调性而将数组看成特殊的引用数据类型而已。关于将Java将数组看成特殊的引用数据类型这一点,可以从引用数据类型的分类(数组、类、接口)中看出,数组作为一个特例被Java纳到对象的范畴。
后记
本来想尝试从字节码角度来分析的,这得花更多时间去研究,想想还是算了。以后有机会或许会从字节码角度再来看这个问题吧。
想补充一个知识点,以前我们获取数组的长度一般使用"ref.length
"。现在多了一种方法"Array.getLength(ref)
",举例如下。
int[] a = {1, 2, 3, 4}; System.out.println(a.length); // 4 System.out.println(Array.getLength(a)); // 4
转载于:https://www.cnblogs.com/forget406/p/5713419.html
不该被忽视的CoreJava细节(四)相关推荐
- 《初级前端开发人员经常容易忽视几个细节问题汇总》
<初级前端开发人员经常容易忽视几个细节问题汇总> 1.使用 变量.toString()的时候记得对变量进行判空 2.使用 字符串.indexOf()的时候记得对字符串变量进行判断是否为nu ...
- ASP.NET常被忽视的一些细节
原文:ASP.NET常被忽视的一些细节 前段时间碰到一个问题:为什么在ASP.NET程序中定时器有时候会不工作? 这个问题看起来很奇怪,代码好像也没错,但就是结果与预期不一致. 其实这里是ASP.NE ...
- java原生的编译软件_原生态Java 程序员容易忽视的编程细节
Java是Java程序设计语言和Java平台的总称,要想学好一门语言,打好基础最关键的,学习一种新的编程语言比学习新的口头语言要容易得多.然而,在这两种学习过程中,都要付出额外的努力去学习不带口音地说 ...
- PCB设计中容易忽视的小细节 一分钟帮你总结
PCB设计是一份严谨.仔细的工作.在PCB设计过程中有非常多的小细节,一些个小细节如果是没有注意好的话,极大可能会影响整个PCB的性能,乃至决定整个产品的成败. PCB布局规范细节 1.在开关电源高压 ...
- mysql blob 内容查看_这些被你忽视的MySQL细节,可能会让你丢饭碗!
我们在 MySQL 入门篇主要介绍了基本的 SQL 命令.数据类型和函数,在具备以上知识后,你就可以进行 MySQL 的开发工作了,但是如果要成为一个合格的开发人员,你还要具备一些更高级的技能,下面我 ...
- 更换mysql_这些被你忽视的MySQL细节,可能会让你丢饭碗!
我们在 MySQL 入门篇主要介绍了基本的 SQL 命令.数据类型和函数,在具备以上知识后,你就可以进行 MySQL 的开发工作了,但是如果要成为一个合格的开发人员,你还要具备一些更高级的技能,下面我 ...
- java如何忽略过程值_Java中容易被你忽略的细节(四)
1.在一个程序当中代码段访问了同一个对象从单独的并发的线程当中,那么这个代码段叫"临界区" 怎么解决呢:使用同步的机制对临界区进行保护 同步的两种方式:同步块和同步方法 对于同步来 ...
- html 文本显示蓝底蓝框,零元学Expression Blend 4 - Chapter 27 PathlistBox被Selected时的蓝底蓝框问题...
最近收到网友Cloud的来信,询问我有关放进PathlistBox的物件,被选取後会出现蓝底蓝框的问题 经由他的同意,我决定把这个实作上遇到的问题及解决的方式,用一篇文章来跟大家分享 ? 最近收到网友 ...
- 接受BBC专访的四个细节,印证了华为掌舵人任正非的智慧
华为掌舵人任正非是一个非常低调的人,一直以来都不愿意接受媒体的采访,但2019年才过一个半月,他已多次接受中外媒体的采访,近期还接受英国媒体BBC的专访.其实主动接受采访的目的:就是通过媒体宣传华为, ...
最新文章
- Python 之 Numpy (四)索引
- cluster maintain manager Software群集管理软件
- Linq 多表连接查询join
- java正则表达式空行_正则表达式删除空行
- maven 打包普通java配置_配置pom.xml用maven打包java工程的方法(推荐)
- 我们的小窝-情侣空间秀恩爱php源码
- Exchange 2013SP1和O365混合部署系列一
- 微型计算机杂志合订本,微型计算机(2008上半年合订本)(上下)(附光盘)
- mysql 1607_Windows下Mysql启动“服务名无效”及“系统错误1607”解决办法
- mysql乱码加的一段代码_mysql乱码的解决方法
- 易语言制作计算机按键指令,易语言键代码一览表
- iOS底层 - 符号解析(dSYM 系统符号)Go语言版本
- CrossOver Mac2022双系统虚拟机软件
- 平行四边形图案c语言,使用scratch绘制各种图案-平行四边形【解说】
- chrome浏览器打开网页排版错乱
- 数组之concat注意事项-不更改原数组
- 小程序二维码需要发布正式版后才能获取到_很意外!iOS 14.1正式版已出,修复多处问题...
- 深度学习100例-循环神经网络(RNN)实现股票预测第9天之二
- AndroidStudio模拟器进程被杀死的解决方案
- 中国蜗轮减速机市场趋势报告、技术动态创新及市场预测