Java数组 java.util.Arrays实用功能
三种初始化方式
public class test {
static int[] returnIntarray() {
return new int[] {1,2,3};// Dynamic aggregate initialization
}
static void receiveIntarray(int [] pa){}
public static void main(String[] args) {
Integer[] a;//声明了,但没初始化
Integer[] b = new Integer[5];//平常的初始化
for(int i = 0; i < b.length; i++)
if(b[i] == null) // Can test for null reference
b[i] = Integer.valueOf(i);
// Aggregate initialization:
Integer[] d = { Integer.valueOf(1),
Integer.valueOf(2), Integer.valueOf(3),
};
// Dynamic aggregate initialization:
a = new Integer[]{
Integer.valueOf(1), Integer.valueOf(1),
};
receiveIntarray( new int[]{1,2,3} );
}
}
Integer[] a
声明了一个数组的引用,但没有初始化。Integer[] b = new Integer[5]
,这种就是数组平常的初始化方式,注意中括号里必须给一个数字。用了new关键字也说明了数组对象是一个存在于堆上的对象,我们使用的只是数组对象的引用。这种初始化方式会把每个数组元素一个默认值,由于Integer是引用类型,所以默认值是null;如果是int[] f = new int[5]
那么默认值就是int自身的默认值0。Integer[] d = { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), }
这种是聚集初始化,只能用在数组对象声明的语句里,比如这里。a = new Integer[]{ Integer.valueOf(1), Integer.valueOf(1), }
这种动态的聚集初始化,相比上条,这条可以出现在数组赋值的任意位置(不仅可以在声明语句,而且可以像这里的给引用赋值、或用在return语句return new int[] {1,2,3}
、或传递给形参类型为数组的方法作为实参receiveIntarray( new int[]{1,2,3} )
)- 注意,聚集初始化里面的元素,最后一个元素后面也可以有逗号。
- 注意,
Integer b[] = new Integer[5]
也可以通过编译的,但这样声明变量不能让人一眼看到b变量的类型为Integer[]
,所以坚决不推荐这样写。
多维数组
import java.util.Arrays;
public class test {
public static void main(String[] args) {
int[][] a = new int[2][3];
int[][] b = {
{1, 2, 3},
{4, 5, 6}
};
int[][] c = new int[][]{
{1, 2, 3},
{4, 5, 6}
};
System.out.println(Arrays.deepToString(a));
System.out.println(Arrays.deepToString(b));
System.out.println(Arrays.deepToString(c));
}
}/*output:
[[0, 0, 0], [0, 0, 0]]
[[1, 2, 3], [4, 5, 6]]
[[1, 2, 3], [4, 5, 6]]
*/
上例通过三种初始化方式来创建了二维数组。Arrays.deepToString()
静态方法专门用来打印多维数组。
粗糙数组
粗糙数组是指,多维数组里,同一层次的子数组,长度可以不相同。专业的说:数组中构成的矩阵的每个向量都可以具有任意的长度。C++里应该是没有粗糙数组的。
public class test {
public static void main(String[] args) {
int[][] c = new int[][]{
{1},
{2, 3},
{4, 5, 6},
};
}
}
上例使用聚集初始化的方式可以很方便初始化出一个粗糙数组。
//: arrays/RaggedArray.java
import java.util.*;
public class RaggedArray {
public static void main(String[] args) {
Random rand = new Random(47);
// 3-D array with varied-length vectors:
int[][][] a = new int[rand.nextInt(7)][][];
System.out.println(Arrays.deepToString(a));
for(int i = 0; i < a.length; i++) {
a[i] = new int[rand.nextInt(5)][];
//System.out.println(Arrays.deepToString(a));//单独取消此注释以更好地观察赋值的过程
for(int j = 0; j < a[i].length; j++)
a[i][j] = new int[rand.nextInt(5)];
//System.out.println(Arrays.deepToString(a));//单独取消此注释以更好地观察赋值的过程
}
System.out.println(Arrays.deepToString(a));
}
} /* Output:
[null, null, null, null, null, null]
[[], [[0], [0], [0, 0, 0, 0]], [[], [0, 0], [0, 0]], [[0, 0, 0], [0], [0, 0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0], []], [[0], [], [0]]]
*///:~
- 上例
int[][][] a = new int[rand.nextInt(7)][][]
使用了正常的初始化方式,但为了达到粗糙数组的效果,初始化只指定了第一维的大小,因为一旦第二维和第三维的大小在初始化时被确定,那么就不可能是粗糙数组了。当然,多维数组的第一维度的大小必须确定,不然会报错,一维数组也一样。 - 从打印结果
[null, null, null, null, null, null]
来看,int[][][] a = new int[rand.nextInt(7)][][]
给的第一维大小为6,但其每个元素(二维数组)都是null,说明还需要对a
的每个元素进行赋值。 - 循环里
a[i] = new int[rand.nextInt(5)][]
给二维数组赋值时,也是只确定第一维大小,大小用的也是随机数,为了把粗糙数组进行到底。 - 循环里
a[i][j] = new int[rand.nextInt(5)]
给一维数组赋值时,大小用的也是随机数。直到这里,第三维的大小才被确定。
java.util.Arrays实用功能
本章可能不会去讲使用方法,多看看有趣的源码和注释吧。
System.arraycopy()
本函数用来复制数组。看完源码的注释基本就懂了,什么情况下抛异常也跟你说了。
/* @param src the source array.
* @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
* @exception IndexOutOfBoundsException if copying would cause
* access of data outside array bounds.
* @exception ArrayStoreException if an element in the <code>src</code>
* array could not be stored into the <code>dest</code> array
* because of a type mismatch.
* @exception NullPointerException if either <code>src</code> or
* <code>dest</code> is <code>null</code>.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
从src源数组的srcPos位置开始复制length个元素过去,复制到dest目标数组的destPos位置到destPos+length-1位置。
Otherwise, if any of the following is true, an
ArrayStoreException
is thrown and the destination is not modified:
- The
src
argument refers to an object that is not an array.- The
dest
argument refers to an object that is not an array.- The
src
argument anddest
argument refer to arrays whose component types are different primitive types.- The
src
argument refers to an array with a primitive component type and thedest
argument refers to an array with a reference component type.- The
src
argument refers to an array with a reference component type and thedest
argument refers to an array with a primitive component type.
如上所述,两个数组的元素类型component type必须是相同的确切类型,即使自动拆装箱在这里也不好使,就算一个数组元素类型为int,另一个为Integer,也会报错ArrayStoreException。
另外需要注意,数组的元素类型如果是reference component type引用类型,那么复制过去的只是一个引用,即没有发生引用指向的对象的拷贝。
equals()
本函数用来比较两个数组是否相同,简单地说,只是当两个数组间拥有相同的元素,且二者的元素顺序也一样时,才返回真。此函数对所有基本类型[]
和Object[]
进行了重载,这样就能保证对所有数组都能执行。
public static boolean equals(int[] a, int[] 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++)
if (a[i] != a2[i])
return false;
return true;
}
if (a==a2) return true;
这里,如果两个引用指向的同一个数组,或者两个引用都为null,那么直接返回true。if (a==null || a2==null) return false;
如果只是有一个为null,那么返回false。- 剩下的逻辑就是依次判断长度,长度一样后,就依次判断各个元素是否一样。
但double[]
重载版本的equals()有点不一样:
for (int i=0; i<length; i++)
if (Double.doubleToLongBits(a[i])!=Double.doubleToLongBits(a2[i]))
return false;
Two doubles
d1
andd2
are considered equal if:new Double(d1).equals(new Double(d2))(Unlike the==
operator, this method considersNaN
equals to itself, and 0.0d unequal to -0.0d.)
注释讲了认为两个double相等,是用包装类Double的equals方法来判断的。Double的equals方法与==
操作不一样,因为System.out.println(Double.NaN==Double.NaN)
返回false,System.out.println(0.0d == -0.0d)
返回true。但源码还是用的Double.doubleToLongBits(应该是这个方法和Double的equals方法在比较方面是等价的),考虑浮点数底层实际都是IEEE浮点表示,那么这个方法就是浮点数的底层bit pattern转换成一个long再进行比较。
float[]
重载版本的equals()同理:
for (int i=0; i<length; i++)
if (Float.floatToIntBits(a[i])!=Float.floatToIntBits(a2[i]))
return false;
Object[]
重载版本的equals()代表的是引用类型,逻辑稍有不同,因为引用类型不像基本类型,它可以为null:
for (int i=0; i<length; i++) {
Object o1 = a[i];
Object o2 = a2[i];
if (!(o1==null ? o2==null : o1.equals(o2)))//先判断二者是否都为null
return false;
}
之所以,不把这几个重载版本的equals()写成一个泛型方法,我觉得是因为泛型方法虽然能把类型参数推断出来,但由于类型擦除,在运行时是获得不到具体类型的,进而,无法根据具体类型的不同作不同的处理(就是前面讲到的几种重载版本的不同逻辑)。
sort()
调用Arrays.sort()
分为三种情况:
- 数组元素为引用类型,且元素类型实现了
Comparable
接口。 - 数组元素为引用类型,但元素类型没有实现
Comparable
接口。所以需要第二个参数实现Comparator<T>
,Comparator被继承时T被指定为数组元素类型或其父类型。 - 数组元素为基本类型。所有基本类型都有被重载的版本。
- 引用类型调用的排序实现,肯定是稳定的排序。
Arrays.parallelSort()
也是分成这三种情况。
【1】数组元素为引用类型,且元素类型实现了Comparable
接口。
public static void sort(Object[] a) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a);
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}
上面方法针对与引用类型,加了System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
这句后,会让系统调用以前的归并排序legacyMergeSort(不过这个排序实现会被在以后的版本会被移除,所以慎用),否则系统调用ComparableTimSort.sort。
调用这个方法需要数组元素类型实现了Comparable接口,因为这两种排序的实现代码都会把数组元素强转成Comparable来进行比较。
【2】数组元素为引用类型,但元素类型没有实现Comparable
接口。
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
public interface Comparator<T> {
int compare(T o1, T o2);
//省略
}
这种版本不需要元素实现Comparable接口,因为很多时候,数组元素也不可能去实现Comparable接口的。你需要去实现Comparator泛型接口,在implement这个接口时,令类型参数T为数组元素类型即可。注意引用类型为Comparator<? super T>
,这样c
引用就只能使用泛型代码的入口了(即只能“写操作”,刚好compare方法就属于“写操作”);而且Comparator的类型参数可以是T的父类了,接受的Comparator更加灵活了。
【3】数组元素为基本类型。
public static void sort(int[] a) {
DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
也有重载基本类型比如int[]
的版本,这里调用的是DualPivotQuicksort.sort,其他的基本类型的版本也是调用此排序。因为基本类型不需要稳定性,所以用了快排。
binarySearch()
调用二分查找之前,必须保证数组已经有序,不然返回结果未定义。如果key在数组有多个,那么不能保证找到的是哪个key。
【1】基本类型。(有其他基本类型的重载版本)
public static int binarySearch(long[] a, long key) {
return binarySearch0(a, 0, a.length, key);
}
@param
a
the array to be searched @paramkey
the value to be searched for @return index of the search key, if it is contained in the array; otherwise,(-(insertion point) - 1)
. The insertion point is defined as the point at which the key would be inserted into the array: the index of the first element greater than the key, ora.length
if all elements in the array are less than the specified key. Note that this guarantees that the return value will be >= 0 if and only if the key is found.
如果key不存在于数组中,那么将返回(-(insertion point) - 1)
。插入点的插入,意思就是参数key应该放到索引insertion point
处,然后将原insertion point
索引元素和之后元素都往后移动。插入点就是代表key应该放在数组的哪个位置。它会是第一个大于key的元素的索引值;或者是a.length
这个不可能的索引值,当所有元素都小于key时。
【2】引用类型,且数组元素实现了Comparable接口。
public static int binarySearch(Object[] a, Object key) {
return binarySearch0(a, 0, a.length, key);
}
【3】引用类型,且数组元素没有实现Comparable接口。另一个参数为Comparator接口。
public static <T> int binarySearch(T[] a, T key, Comparator<? super T> c) {
return binarySearch0(a, 0, a.length, key, c);
}