博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
不该被忽视的CoreJava细节(四)
阅读量:6306 次
发布时间:2019-06-22

本文共 8160 字,大约阅读时间需要 27 分钟。

令人纳闷的数组初始化细节

这个细节问题我很久以前就想深入研究一下,但是一直没有能够抽出时间,借这系列文章的东风,尽量解决掉这个"心头病"。

下面以一维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()); // 1072840587        System.out.println(b.hashCode()); // 959045497        System.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 Array class provides static methods to dynamically create and 5  * access Java arrays. 6  * 7  * 

Array permits widening conversions to occur during a get or set 8 * operation, but throws an IllegalArgumentException if a narrowing 9 * conversion would occur.10 *11 * @author Nakul Saraiya12 */13 public final14 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@2b571dff                boolean[] bl = new boolean[2];        System.out.println(bl);  //[Z@64726693                short[] s = new short[2];        System.out.println(s);  //[S@12ac706a                int[] i = new int[2];        System.out.println(i);  //[I@770848b9                long[] l = new long[2];        System.out.println(l);  //[J@40dea6bc                char[] ch = new char[2];          System.out.println(ch); //两个方框        System.out.println(ch.getClass().getName());   //[C char[] ch = new char[2];        ch[0] = 'a';        ch[1] = 'b';        System.out.println(ch);  //ab                float[] f = new float[2];        System.out.println(f);  //[F@5994a1e9                double[] 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);    // 4System.out.println(Array.getLength(a));   // 4

 

转载于:https://www.cnblogs.com/forget406/p/5713419.html

你可能感兴趣的文章
为什么要跟别人比?
查看>>
app启动白屏
查看>>
Oracle 提高查询性能(基础)
查看>>
学习知识应该像织网一样去学习——“网状学习法”
查看>>
Hadoop集群完全分布式安装
查看>>
QString,char,string之间赋值
查看>>
我的友情链接
查看>>
Nginx+mysql+php-fpm负载均衡配置实例
查看>>
shell脚本操作mysql数据库 (部份参考)
查看>>
MySql之基于ssl安全连接的主从复制
查看>>
informix的逻辑日志和物理日志分析
查看>>
VMware.Workstation Linux与windows实现文件夹共享
查看>>
ARM inlinehook小结
查看>>
wordpress admin https + nginx反向代理配置
查看>>
管理/var/spool/clientmqueue/下的大文件
查看>>
HTML学习笔记1—HTML基础
查看>>
mysql dba系统学习(20)mysql存储引擎MyISAM
查看>>
centos 5.5 64 php imagick 模块错误处理记录
查看>>
apache中文url日志分析--php十六进制字符串转换
查看>>
Ansible--playbook介绍
查看>>