读Java编程思想随笔の数组
数组与其他种类的容器之间区别有三:效率、类型和保存基本类型的能力。在Java中,数组是一种最高的存储和随机访问对象引用序列的方式。数组就是一个简单的线性序列,这使得元素访问非常快速。但是为这种速度所付出的代价是数组对象的大小被固定,并且在其生命周期中不可改变。你可以能会建议使用ArrayList,它可以通过创建一个新的实例,然后把旧实例中所有引用移到新实例上来,从而实现更多空间的自动分配。尽管应当首先ArrayList而不是数组,但是这种弹性需要开销,因此,ArrayList的效率比数组要低很多。
数组和容器都可以保证你不滥用它们。无论你是使用数组还是容器,如果越界,都会得到一个表示编程错误的runtimeException。
在泛型之前,其他的容器类在处理对象时,都将它们视作没有任何具体类型。也就是说,它们将这些对象都当Java中所有类的根类Object处理。数组之所有优于泛型之前的容器,就是因为你可以创建一个数组去持有某种具体类型。这意味着你可以通过编译期检查,来防止插入错误类型和抽取不当类型。当然,无论是在编译时还是运行时,Java都会阻止你发送不恰当的消息。所以,并不是说哪种方式更安全,只是如果编译时就可以指出错误,会显得更加优雅,也减少了程序的使用者被异常吓着的可能性。
数组可以持有基本类型,而泛型之前的容器则不能。但是有了泛型,容器就可以指定并检查它们所持有对象的类型,并且有了自动包装机制,容器看起来还能够持有基本类型。下面是比较数组和泛型容器。
1 public class ContainerComparison { 2 public static void main(String[] args) { 3 BerylliumSphere [] spheres = new BerylliumSphere[10]; 4 for (int i=0;i<5;i++){ 5 spheres[i]=new BerylliumSphere(); 6 } 7 System.out.println(Arrays.toString(spheres)); 8 System.out.println(spheres[4]); 9 List<BerylliumSphere> sphereList = new ArrayList<BerylliumSphere>(); 10 for (int i=0;i<5;i++){ 11 sphereList.add(new BerylliumSphere()); 12 } 13 System.out.println(sphereList); 14 System.out.println(sphereList.get(4)); 15 16 int [] integers = {0,1,2,3,4,5}; 17 System.out.println(Arrays.toString(integers)); 18 System.out.println(integers[4]); 19 List<Integer> intList = new ArrayList<Integer>(Arrays.asList(0,1,2,3,4,5)); 20 intList.add(97); 21 System.out.println(intList); 22 System.out.println(intList.get(4)); 23 } 24 } 25 class BerylliumSphere{ 26 private static long counter; 27 private final long id = counter++; 28 @Override 29 public String toString() { 30 return "Sphere "+id; 31 } 32 } 33 //[Sphere 0, Sphere 1, Sphere 2, Sphere 3, Sphere 4, null, null, null, null, null] 34 //Sphere 4 35 //[Sphere 5, Sphere 6, Sphere 7, Sphere 8, Sphere 9] 36 //Sphere 9 37 //[0, 1, 2, 3, 4, 5] 38 //4 39 //[0, 1, 2, 3, 4, 5, 97] 40 //4
随着自动包装机制的出现,容器已经可以与数组几乎一样方便地用于基本类型中了。数组硕果仅存的优点就是效率。然而,如果要解决更一般化的问题,那数组就可能会受到过多的限制,因此在这些情形下你还是会使用容器。
数组是第一级对象
无论使用哪种类型的数组,数组标识符其实只是一个引用,指向在堆中创建的一个真实对象,这个对象用以保存指向其他对象的引用。可以作为数组初始化语法的一部分隐式地创建此对象,或者用new表达式显式的创建。只读成员length是数组对象的一部分(事实上,这是唯一一个可以访问的字段或方法),表示此数组对象可以存储多少个元素。“[]”语法是访问数组对象的唯一方式。
下例总结了初始化数组的各种方式,以及如何对指向数组的引用赋值,使之指向另一个数组对象。例外,也说明对象数组和基本类型数组在使用上几乎是相同的,唯一区别就是对象数组保存的是引用,基本类型数组直接保存基本类型的值。
1 public class ArrayOptions { 2 public static void main(String[] args) { 3 BerylliumSpere [] a; 4 BerylliumSpere [] b = new BerylliumSpere[5]; 5 6 System.out.println("b:"+ Arrays.toString(b)); 7 BerylliumSpere [] c = new BerylliumSpere[4]; 8 for (int i=0;i<c.length;i++){ 9 if (c[i]==null){ 10 c[i] = new BerylliumSpere(); 11 } 12 } 13 //Aggregate initialization 聚集初始化 14 BerylliumSpere [] d = {new BerylliumSpere(),new BerylliumSpere(),new BerylliumSpere()}; 15 //Dynamic aggregate initialization 动态聚集初始化 16 a = new BerylliumSpere[]{new BerylliumSpere(),new BerylliumSpere()}; 17 18 System.out.println(a.length); 19 System.out.println(b.length); 20 System.out.println(c.length); 21 System.out.println(d.length); 22 a=d; 23 System.out.println("++++++"); 24 System.out.println(a.length); 25 26 int [] e; 27 int [] f = new int[5]; 28 29 System.out.println("f:"+Arrays.toString(f)); 30 int [] g= new int[4]; 31 for (int i=0;i<g.length;i++){ 32 g[i] = i*i; 33 } 34 int [] h = {11,47,39}; 35 System.out.println(f.length); 36 System.out.println(g.length); 37 System.out.println(h.length); 38 e=h; 39 System.out.println("++++++"); 40 System.out.println(e.length); 41 e = new int[]{1,2}; 42 System.out.println("------"); 43 System.out.println(e.length); 44 45 boolean [] j= new boolean[5]; 46 System.out.println(j[0]); 47 48 49 } 50 } 51 //b:[null, null, null, null, null] 52 //2 53 //5 54 //4 55 //3 56 //++++++ 57 //3 58 //f:[0, 0, 0, 0, 0] 59 //5 60 //4 61 //3 62 //++++++ 63 //3 64 //------ 65 //2 66 //false
多维数组
数组中构成矩阵的每个向量都可以具有任意的长度,也被称为粗糙数组。
public class RaggedArray { public static void main(String[] args) { Random rand = new Random(47); int [][][] a= new int[rand.nextInt(7)][][]; for (int i=0;i<a.length;i++){ a[i] = new int[rand.nextInt(5)][]; for (int j=0;j<a[i].length;j++){ a[i][j] = new int[rand.nextInt(5)]; } } System.out.println(Arrays.deepToString(a)); } }
数组与泛型
通常,数组与泛型不能很好的结合。你不能实例化具有参数化类型的数组。
1 Peel<Banana> [] peels = new Peel<Banana>[10];//Illegal
但是,你可以参数化数组本身的类型
1 public class ParameterizedArrayType { 2 public static void main(String[] args) { 3 Integer [] ints = {1,2,3,4,5}; 4 Double [] doubles = {1.1,2.2,3.3,4.4,5.5}; 5 Integer [] ints2 = new ClassParameter<Integer>().f(ints); 6 Double [] doubles2 = new ClassParameter<Double>().f(doubles); 7 ints2 = MethodParameter.f(ints); 8 doubles2 = MethodParameter.f(doubles); 9 } 10 } 11 12 /** 13 * 参数化类 14 * @param <T> 15 */ 16 class ClassParameter<T>{ 17 public T[] f(T[] arg){return arg;} 18 } 19 /** 20 * 参数化方法 21 */ 22 class MethodParameter{ 23 public static <T> T[] f(T[] arg){return arg;} 24 }
使用参数化方法而不使用参数化类的方便之处在于,你不必为需要应用的每种不同的类型都使用一个参数去实例化这个类,
并且你可以将其定义为静态的。当然,你不能总是选择参数化方法,但是它应该成为首选。
正如上例所证明的那样,不能创建泛型数组这一说法并不十分准确。诚然,编译器确实不让你实例化泛型数组,但是它允许你创建对这种数组的引用。
List<String> [] ls;
这条语句可以顺利地通过编译器而不报任何错误。而且,尽管你不能创建实际的持有泛型的数组对象,但是你可以创建非泛型的数组,然后将其转型。
1 public class ArrayOfGenerics { 2 @SuppressWarnings("unchecked") 3 public static void main(String[] args) { 4 List<String> [] ls;//ls存放的是String集合类型的数据 5 List[] la = new List[10]; 6 ls = (List<String> [])la; 7 ls[0] = new ArrayList<String>(); 8 9 Object [] objects = ls; 10 objects[1] = new ArrayList<Integer>();//数组是协变类型 11 12 List<BerylliumSpere> [] spheres = (List<BerylliumSpere> [])new List[10]; 13 for (int i = 0;i<spheres.length;i++){ 14 spheres[i] = new ArrayList<BerylliumSpere>(); 15 } 16 } 17 18 }
一旦拥有了对List<String>的引用,你就会看到你将得到某些编译器检查。问题是数组是协变类型的,因此List<String> []也是一个Object[],并且你可以利用这一点,将一个ArrayList<Integer>赋值到你的数组中,而不会有任何编译期或运行时错误。
Arrays实用功能
在讨论Arrays的方法之前,我们先看看另一个不属于Arrays但很有用的方法。
复制数组
Java类库提供了又static方法System.arraycope(),用它复制数组比用for循环复制要快很多。System.arraycope()针对所有类型做了重载。
1 public class CopyingArrays { 2 public static void main(String[] args) { 3 int[] i = new int[7]; 4 int[] j = new int[10]; 5 Arrays.fill(i, 47); 6 Arrays.fill(j, 99); 7 System.out.println("i = " + Arrays.toString(i)); 8 System.out.println("j = " + Arrays.toString(j)); 9 System.arraycopy(i, 0, j, 0, i.length); 10 System.out.println("j = " + Arrays.toString(j)); 11 int[] k = new int[5]; 12 Arrays.fill(k, 103); 13 System.arraycopy(i, 0, k, 0, k.length); 14 System.out.println("k = " + Arrays.toString(k)); 15 Arrays.fill(k, 103); 16 System.arraycopy(k, 0, i, 0, k.length); 17 System.out.println("i = " + Arrays.toString(i)); 18 // Objects: 19 Integer[] u = new Integer[10]; 20 Integer[] v = new Integer[5]; 21 Arrays.fill(u, new Integer(47)); 22 Arrays.fill(v, new Integer(99)); 23 System.out.println("u = " + Arrays.toString(u)); 24 System.out.println("v = " + Arrays.toString(v)); 25 System.arraycopy(v, 0, u, u.length/2, v.length); 26 System.out.println("u = " + Arrays.toString(u)); 27 } 28 }
arraycopy()需要的参数有:源数组,表示从源数组中的什么位置开始复制的偏移量,以及需要复制的元素的个数。当然,对数组的任何越界操作都会导致异常。
这个例子说明基本类型数组与对象数组都可以复制。然而,如果复制对象数组,那么只是复制了对象的引用--而不是对象本身的拷贝。这被称作浅复制。
System.arraycope()不会执行自动包装盒自动拆包,两个数组必须具有相同的确切类型。
Arrays类提供了重载后的equals()方法,用来比较整个数组。同样,此方法针对所有基本类型与Object都做了重载。数组相等的条件是元素个数必须相等,并且对应的位置的元素也相等,这可以通过对每一个元素使用equals()作比较来判断。(对于基本类型,需要使用基本类型的包装器的equals()方法)
1 public class ComparingArrays { 2 public static void main(String[] args) { 3 int[] a1 = new int[10]; 4 int[] a2 = new int[10]; 5 Arrays.fill(a1, 47); 6 Arrays.fill(a2, 47); 7 System.out.println(Arrays.equals(a1, a2)); 8 a2[3] = 11; 9 System.out.println(Arrays.equals(a1, a2)); 10 String[] s1 = new String[4]; 11 Arrays.fill(s1, "Hi"); 12 String[] s2 = { new String("Hi"), new String("Hi"), 13 new String("Hi"), new String("Hi") }; 14 System.out.println(Arrays.equals(s1, s2)); 15 } 16 } /* Output: 17 true 18 false 19 true 20 *///:~
数组元素的比较
排序必须根据对象的实际类型执行比较操作。一种自然的解决方案是为每种不同的类型各编写一个不同的排序算法,但是这样的代码难以被新的类型所复用。
程序设计的基本目标是“将保持不变的事物与会发生改变的事物相分离”,而这里,不变的是通用的排序算法,变化的是各种对象相互比较的方式。因此,不是将进行比较的代码编写成不同的子程序,而是使用策略设计模式。通过使用策略,可以将“会发生变化的代码”封装在单独的类中(策略对象),你能够用不同的对象来表示不同的比较方式,然后将他们传递给相同的排序代码。
下面的类实现了Comparable接口,并且使用Java标准类库的方法Arrays.sort()演示了比较的效果
1 public class CompType implements Comparable<CompType> { 2 int i; 3 int j; 4 private static int count = 1; 5 public CompType(int n1, int n2) { 6 i = n1; 7 j = n2; 8 } 9 public String toString() { 10 String result = "[i = " + i + ", j = " + j + "]"; 11 if(count++ % 3 == 0) 12 result += "\n"; 13 return result; 14 } 15 public int compareTo(CompType rv) { 16 return (i < rv.i ? -1 : (i == rv.i ? 0 : 1)); 17 } 18 private static Random r = new Random(47); 19 public static Generator<CompType> generator() { 20 return new Generator<CompType>() { 21 public CompType next() { 22 return new CompType(r.nextInt(100),r.nextInt(100)); 23 } 24 }; 25 } 26 public static void main(String[] args) { 27 CompType[] a = 28 Generated.array(new CompType[12], generator()); 29 System.out.println("before sorting:"); 30 System.out.println(Arrays.toString(a)); 31 Arrays.sort(a); 32 System.out.println("after sorting:"); 33 System.out.println(Arrays.toString(a)); 34 } 35 } /* Output: 36 before sorting: 37 [[i = 58, j = 55], [i = 93, j = 61], [i = 61, j = 29] 38 , [i = 68, j = 0], [i = 22, j = 7], [i = 88, j = 28] 39 , [i = 51, j = 89], [i = 9, j = 78], [i = 98, j = 61] 40 , [i = 20, j = 58], [i = 16, j = 40], [i = 11, j = 22] 41 ] 42 after sorting: 43 [[i = 9, j = 78], [i = 11, j = 22], [i = 16, j = 40] 44 , [i = 20, j = 58], [i = 22, j = 7], [i = 51, j = 89] 45 , [i = 58, j = 55], [i = 61, j = 29], [i = 68, j = 0] 46 , [i = 88, j = 28], [i = 93, j = 61], [i = 98, j = 61] 47 ] 48 *///:~
Collections类包含一个reverseOrder()方法,该方法可以产生一个Comparator,它可以反转自然的排序顺序。
1 public class Reverse { 2 public static void main(String[] args) { 3 CompType[] a = Generated.array( 4 new CompType[12], CompType.generator()); 5 System.out.println("before sorting:"); 6 System.out.println(Arrays.toString(a)); 7 Arrays.sort(a, Collections.reverseOrder()); 8 System.out.println("after sorting:"); 9 System.out.println(Arrays.toString(a)); 10 } 11 } /* Output: 12 before sorting: 13 [[i = 58, j = 55], [i = 93, j = 61], [i = 61, j = 29] 14 , [i = 68, j = 0], [i = 22, j = 7], [i = 88, j = 28] 15 , [i = 51, j = 89], [i = 9, j = 78], [i = 98, j = 61] 16 , [i = 20, j = 58], [i = 16, j = 40], [i = 11, j = 22] 17 ] 18 after sorting: 19 [[i = 98, j = 61], [i = 93, j = 61], [i = 88, j = 28] 20 , [i = 68, j = 0], [i = 61, j = 29], [i = 58, j = 55] 21 , [i = 51, j = 89], [i = 22, j = 7], [i = 20, j = 58] 22 , [i = 16, j = 40], [i = 11, j = 22], [i = 9, j = 78] 23 ] 24 *///:~
也可以编写自己的Comparator
class CompTypeComparator implements Comparator<CompType> { public int compare(CompType o1, CompType o2) { return (o1.j < o2.j ? -1 : (o1.j == o2.j ? 0 : 1)); } } public class ComparatorTest { public static void main(String[] args) { CompType[] a = Generated.array( new CompType[12], CompType.generator()); System.out.println("before sorting:"); System.out.println(Arrays.toString(a)); Arrays.sort(a, new CompTypeComparator()); System.out.println("after sorting:"); System.out.println(Arrays.toString(a)); } } /* Output: before sorting: [[i = 58, j = 55], [i = 93, j = 61], [i = 61, j = 29] , [i = 68, j = 0], [i = 22, j = 7], [i = 88, j = 28] , [i = 51, j = 89], [i = 9, j = 78], [i = 98, j = 61] , [i = 20, j = 58], [i = 16, j = 40], [i = 11, j = 22] ] after sorting: [[i = 68, j = 0], [i = 22, j = 7], [i = 11, j = 22] , [i = 88, j = 28], [i = 61, j = 29], [i = 16, j = 40] , [i = 58, j = 55], [i = 20, j = 58], [i = 93, j = 61] , [i = 98, j = 61], [i = 9, j = 78], [i = 51, j = 89] ] *///:~
数组排序
使用内置的排序方法,就可以对任意的基本类型数组排序;也可以对任意的对象数组进行排序,只要该对象实现了comparable接口或具有相关联的Comparator。
1 public class StringSorting { 2 public static void main(String[] args) { 3 String[] sa = Generated.array(new String[20], 4 new RandomGenerator.String(5)); 5 System.out.println("Before sort: " + Arrays.toString(sa)); 6 Arrays.sort(sa); 7 System.out.println("After sort: " + Arrays.toString(sa)); 8 Arrays.sort(sa, Collections.reverseOrder()); 9 System.out.println("Reverse sort: " + Arrays.toString(sa)); 10 Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER); 11 System.out.println("Case-insensitive sort: " + Arrays.toString(sa)); 12 } 13 } /* Output: 14 Before sort: [YNzbr, nyGcF, OWZnT, cQrGs, eGZMm, JMRoE, suEcU, OneOE, dLsmw, HLGEa, hKcxr, EqUCB, bkIna, Mesbt, WHkjU, rUkZP, gwsqP, zDyCy, RFJQA, HxxHv] 15 After sort: [EqUCB, HLGEa, HxxHv, JMRoE, Mesbt, OWZnT, OneOE, RFJQA, WHkjU, YNzbr, bkIna, cQrGs, dLsmw, eGZMm, gwsqP, hKcxr, nyGcF, rUkZP, suEcU, zDyCy] 16 Reverse sort: [zDyCy, suEcU, rUkZP, nyGcF, hKcxr, gwsqP, eGZMm, dLsmw, cQrGs, bkIna, YNzbr, WHkjU, RFJQA, OneOE, OWZnT, Mesbt, JMRoE, HxxHv, HLGEa, EqUCB] 17 Case-insensitive sort: [bkIna, cQrGs, dLsmw, eGZMm, EqUCB, gwsqP, hKcxr, HLGEa, HxxHv, JMRoE, Mesbt, nyGcF, OneOE, OWZnT, RFJQA, rUkZP, suEcU, WHkjU, YNzbr, zDyCy] 18 *///:~