“5.8 数组初始化”,该章节重点讲的是数组这一种数据类型的初始化方法。数组的定义就不去讲了,表示方法如下:
int[] a1;
也可以表示为:
int a1[];
上面定义了一个int型的数组。
首先要清楚一点就是int[] a1;在没有给a1数组做初始化的时候只是定义了一个数组引用,此时只有个引用地址,并没有给数组分配内存空间。如果需要为数组分配用于存储的内存空间,需要用以下方式进行初始化:
int[] a1 = { 1, 2, 3, 4, 5 };
那么,为什么还要在没有数组的时候定义一个数组引用呢?
int[] a2;
在java中可以将一个数组赋值给另一个数组,所以可以这样:
a2=a1;
其实真正做的只是复制了一个引用,就像下面演示的那样:
importstaticnet.mindview.util.Print.*; publicstaticvoidmain(String[] args) { int[] a1= { 1, 2, 3, 4, 5 }; int[] a2; a2=a1; for(inti=0; i<a2.length; i++) a2[i] =a2[i] +1; for(inti=0; i<a1.length; i++) print("a1["+i+"] = "+a1[i]); } }
/* Output:
a1[0] = 2
a1[1] = 3
a1[2] = 4
a1[3] = 5
a1[4] = 6
*///:~
从上面的例子可以看到a1赋值给了a2,在对a2进行修改以后,a1也发生了变化。因为a2和a1是相同数组的别名,拥有一样的引用地址,所以对a2的修改可以在a1中看到。
所有数组都有一个固有成员,可以通过它获知数组内包含了多少个元素,但是不能对其修改,这个成员就是length。如果在编写程序时,并不能确定在数组里需要多少个元素,那么该怎么办呢?可以直接使用new在数组里创建元素。
importjava.util.*; importstaticnet.mindview.util.Print.*; publicclassArrayNew { publicstaticvoidmain(String[] args) { int[] a; Randomrand=newRandom(47); a=newint[rand.nextInt(20)]; print("length of a = "+a.length); print(Arrays.toString(a)); } }
/* Output:
length of a = 18
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
*///:~
程序输出表明:数组元素中的基本数据类型值会自动初始化成空值(数字和字符就是0,布尔型的是false)。数组中的元素还可以是非基本类型的数据类型,比如String或者自定义类,下面一个例子展示了String类型的可变数组的使用方法:
//: initialization/DynamicArray.java // Array initialization. publicclassDynamicArray { publicstaticvoidmain(String[] args) { Other.main(newString[]{ "fiddle", "de", "dum" }); } } classOther { publicstaticvoidmain(String[] args) { for(Strings : args) System.out.print(s+" "); } } }
/* Output:
fiddle de dum
*///:~
上面的例子中一个类的main方法直接调用另一个类的main方法,并且将String类型的数组作为参数传递过去,可以看到,这里面的String数组并没有指定长度,这种写法使得数组的传递更加灵活通用。
“5.8.1 可变参数列表”提供了一种方便的语法来创建对象并调用方法,该方法使用Object数组作为参数,然后使用foreach语法遍历数组,因为Java中所有的类都直接或者间接继承于Object类,所以通过使用Object数组作为参数,可以接受其他任何类的数组作为入参。比如:
classA {} publicclassVarArgs { staticvoidprintArray(Object[] args) { for(Objectobj : args) System.out.print(obj+" "); System.out.println(); } publicstaticvoidmain(String[] args) { printArray(newObject[]{ newInteger(47), newFloat(3.14), newDouble(11.11) }); printArray(newObject[]{"one", "two", "three" }); printArray(newObject[]{newA(), newA(), newA()}); } }
/* Output: (Sample)
47 3.14 11.11
one two three
A@1a46e30 A@3e25a5 A@19821f
*///:
可以看到数值和字符串都输出的正确的结果,A类由于没有定义toString方法,所以数出来的是类名和对象的地址。
在Java SE5中提供了新的特性来定义可变参数列表:
//: initialization/NewVarArgs.java // Using array syntax to create variable argument lists. publicclassNewVarArgs { staticvoidprintArray(Object... args) { for(Objectobj : args) System.out.print(obj+" "); System.out.println(); } publicstaticvoidmain(String[] args) { // Can take individual elements: printArray(newInteger(47), newFloat(3.14), newDouble(11.11)); printArray(47, 3.14F, 11.11); printArray("one", "two", "three"); printArray(newA(), newA(), newA()); // Or an arrayprintArray((Object[])new Integer[]{ 1, 2, 3, 4 }); printArray(); // Empty list is OK } }
/* Output: (75% match)
47 3.14 11.11
47 3.14 11.11
one two three
A@1bab50a A@c3c749 A@150bd4d
1 2 3 4
*///:~
有了可变列表,就不用再显式的编写数组语法了,当你指定参数时,编译器实际上会为你填充数组,你获取的仍旧是一个数组。这就是为什么你可以使用foreach来迭代该可变列表的原因。
printArray(); // Empty list is OK一行表明将0个参数传递给可变参数列表是可行的。下面一个例子展示了可变参数列表变为数组的情形,并且如果在该列表中没有任何元素,那么转变成的数据的尺寸为0:
//: initialization/VarargType.java publicclassVarargType { staticvoidf(Character... args) { System.out.print(args.getClass()); System.out.println(" length "+args.length); } staticvoidg(int... args) { System.out.print(args.getClass()); System.out.println(" length "+args.length); } publicstaticvoidmain(String[] args) { f(‘a’); f(); g(1); g(); System.out.println("int[]: "+newint[0].getClass()); } }
/* Output:
class [Ljava.lang.Character; length 1
class [Ljava.lang.Character; length 0
class [I length 1
class [I length 0
int[]: class [I
*///:~
getClass()方法属于Object的一部分,我们将在后面章节做全面介绍。它将产生对象的类,并且在打印该类时,可以看到表示该类类型的编码字符串。前导的“[”表示这是一个后面紧随的类型的数组,而紧随的“I”表示基本类型int。为了进行双重检查,最后一行创建了一个int数组,并且打印了其类型。这样也就验证了使用可变参数列表不依赖于自动包装机制,而实际上使用的是基本类型。然而可变列表于自动包装机制可以和谐共处,例如:
//: initialization/AutoboxingVarargs.java publicclassAutoboxingVarargs { publicstaticvoidf(Integer... args) { for(Integeri : args) System.out.print(i+" "); System.out.println(); } publicstaticvoidmain(String[] args) { f(newInteger(1), newInteger(2)); f(4, 5, 6, 7, 8, 9); f(10, newInteger(11), 12); } }
/* Output:
1 2
4 5 6 7 8 9
10 11 12
*///:~
可变列表会使得重载过程变得复杂,尽快咋一看会显得足够安全:
//: initialization/OverloadingVarargs.java publicclassOverloadingVarargs { staticvoidf(Character... args) { System.out.print("first"); for(Characterc : args) System.out.print(" "+c); System.out.println(); } staticvoidf(Integer... args) { System.out.print("second"); for(Integeri : args) System.out.print(" "+i); System.out.println(); } staticvoidf(Long... args) { System.out.println("third"); } publicstaticvoidmain(String[] args) { f(‘a’, ‘b’, ‘c’); f(1); f(2, 1); f(0); f(0L); //! f(); // Won’t compile -- ambiguous } }
/* Output:
first a b c
second 1
second 2 1
second 0
third
*///:~
f()方法的调用会让编译器无法知道应该调用哪一个方法。当然你可以通过在方法中增加一个非可变参数来解决这个问题:
publicclassOverloadingVarargs3 { staticvoidf(floati, Character... args) { System.out.println("first"); } staticvoidf(charc, Character... args) { System.out.println("second"); } publicstaticvoidmain(String[] args) { f(1, ‘a’); f(‘a’, ‘b’); } }
/* Output:
first
second
*///:~
上面的写法虽然可行,但是为了标注不同的可变参数列表的写法会让代码看起来可读性很差,一般也不会这么去设计自己的方法参数。