数组是相同类型的、用一个标识符名称封装到一起的一个 对象序列或 基本类型数据序列。可以使用整型索引值访问它们的元素(“[]”语法是访问数组唯一的方式),并且它们的尺寸不能改变。
对象数组和基本类型数组在使用上几乎是相同的,唯一的区别就是对象数组保存的是引用,基本类型数组直接保存基本类型的值。
数组标识符其实只是个引用,指向在堆中创建的一个真实对象,这个(数组)对象用以保存指向其他对象的引用。
数组定义与初始化
数组定义
定义一个数组,只需在类型名后加上一对空方括号。
int[] a; // 更合理,表明类型是 “一个int型数组”
或者,方括号也可以放在标识符后面。
int a[];
现在拥有的只是对数组的一个引用(已经为该引用分配了足够的存储空间),并没有给数组对象本身分配任何空间。
为了给数组创建相应的存储空间,必须写初始化表达式。
数组初始化
1)花括号初始化
有一种特殊的初始化表达式,由一对花括号括起来的值组成,它必须在创建数组的地方出现(定义的同时进行初始化)
int[] a = {1,2,3,4};
2)new初始化
如果创建的是基本类型的数组,数组元素中的基本数据类型值会自动初始化成空值(对于数字/字符就是0,对于布尔型就是false)
Random rand = new Random(47); //Random.nextInt()方法会随机返回0到输入参数之间的一个值
int[] a = new int[rand.nextInt(20)]; //定义的同时进行初始化
如果创建的是非基本类型的数组,那就创建了一个引用数组。默认情况下其中所有的引用被自动初始化为null。之后可以通过创建对象,并把对象赋值给引用来完善初始化过程。
Random rand = new Random(47); //Random.nextInt()方法会随机返回0到输入参数之间的一个值
Integer[] a = new Integer[rand.nextInt(20)]; //整型的包装器类 Integer,它是一个类而不是基本类型
for (int i = 0; i < a.length; i++)
a[i] = rand.nextInt(500); //创建新的Integer对象,本例是通过自动包装机制创建的
也可以使用花括号括起来的列表来初始化对象数组。有两种形式:
Integer[] a = {new Integer(1), new Integer(2), 3,}; //初始化列表的最后一个逗号可选
Integer[] a = new Integer[] {new Integer(1), new Integer(2), 3,};
//Other.main(new String[]{"abc", "de"}); //String对象数组
可变参数列表
可变参数列表可以应用于参数个数或类型未知的场合。
由于所有的类都直接或间接继承于Object类,所以可以创建以Object数组为参数的方法。
这里,输出建立的类的对象,默认行为(如果没有定义toString()方法的话)打印出的内容是类的名称和对象的地址。
在Java SE5中,加入可变参数列表的特性,如下示:
有了可变参数,就不用再显式地编写数组语法了。当你指定参数时,编译器实际上会为你去填充数组,所以你获取的仍旧是一个数组。也就是从元素列表到数组的自动转换。如果本身就是个数组,编译器就不会在其上执行任何转换。
在可变参数列表中也可以使用Object之外类型的参数(也包括int等基本类型),如下面所有的可变参数都必须是String对象。
数组赋值
谨记:数组标识符其实只是个引用,指向在堆中创建的一个真实对象
数组赋值就是对指向数组的引用赋值,使之指向另一个数组对象。
多维数组
创建多维数组
可以使用花括号将每个向量分隔开:
int[][] a = {
{1, 2, 3, },
{4, 5, 6, },
};
也可以使用 new 来分配数组。在不进行显式初始化的情况下,数组的值会被自动初始化
int [][][] a = new int[2][2][4];
粗糙数组
数组中构成矩阵的每个向量都可以具有任意的长度
二维数组获取行数: a.length
二维数组获取列数: a[i].length
Arrays类实用功能
Arrays类是数组的操作类,定义在 java.util 包中,主要功能是实现数组元素的查找、数组内容填充、排序、比较等
填充数组
Java类库Arrays类提供 Arrays.fill(),用来填充数组。
Arrays.fill()方法作用十分有限,只能用同一个值填充各个位置,而针对对象而言,就是复制同一个引用进行填充。
使用Arrays.fill()可以填充整个数组,或者像示例最后一条语句所示,只填充数组的某个区域。
复制数组
Java - 数组拷贝的几种方式
Java类库System类提供 System.arraycopy(),用它复制数组比用for循环复制要快很多。
arraycopy() 需要的参数:源数组、从源数组什么位置开始复制的偏移量、目标数组、从目标数组什么位置开始复制的偏移量、需要复制的元素个数
对数组的任何越界操作都会导致异常
如果是复制对象数组,那么只是复制了对象的引用——而不是对象本身的拷贝
注:System.arraycopy() 不会执行自动包装和自动拆包,两个数组必须具有相同的确切类型。
数组的比较
Java类库Arrays类提供 Arrays.equals(),用来比较整个数组。
数组相等的条件是元素个数必须相等,并且对应位置的元素也相等。这相当于对每个元素使用equals()作比较来判断(对于基本类型,需要使用基本类型的包装器类的equals()方法。如,对于int类型使用 Integer.equals() 作比较)。
数组元素的比较
Java有两种方式来提供比较功能。
第一种是实现 java.lang.Comparable 接口,使类具有天生的比较能力。此接口很简单,只有 compareTo() 一个方法。此方法接收另一个Object为参数,如果当前对象小于参数则返回负值,如果相等则返回零,如果当前对象大于参数则返回正值。
第二种是创建一个实现了 Comparator 接口的单独的类。这个类有 compare() 和 equals() 两个方法。然而不一定要实现equals()方法,除非有特殊的性能需要,因为无论何时创建一个类,都是间接继承自Object,而Object带有equals()方法。所以只需用默认的Object的equals()方法就可以满足接口的要求了。
数组排序
使用 Arrays.sort() 方法,就可以对任意的基本类型数组排序;
也可以对任意的对象数组进行排序,只要 该对象实现了Comparable接口 或 具有相关联的Comparator。
注意,String排序算法依据词典编排顺序排序,所以大写字母开头的词都放在前面输出,然后才是小写字母开头的词。
数组与容器
总结
本章看到了Java对尺寸固定的低级数组提供了适度的支持。这种数组强调的是性能而不是灵活性
在Java的初始版本中,尺寸固定的低级数组绝对是必需的。因为Java早期版本中对容器的支持非常少。因此,选择包含数组总是合理的。
其后的Java版本对容器的支持得到了明显的改进,并且现在的容器在除了性能之外的各个方面都使得数组相形见绌。有了额外的自动包装机制和泛型,在容器中持有基本类型就变得易如反掌了,而这也进一步促使用容器来替换数组。因为泛型可以产生类型安全的容器,因此数组面对这一变化,已经变得毫无优势了。
最初有关效率的论点总是很吸引人,但随着时间的推移,我们看到了与这种思想背道而驰,向着使用像容器这类高级构件的方向的演化。
因此,容器几乎总是更好的选择。
当你使用最近的Java版本编程时,应该 “优先选择容器而不是数组”。只有在已证明性能成为问题,并且切换到数组对性能提高有所帮助时,你才应该将程序重构为使用数组。