数组初始化
在C中初始化数组极易出错,而且相当麻烦。C++通过“集合初始化”使其安全。Java则没有像C++那样的“集合”概念,因为Java中的所有东西都是对象。但它确实有自己的数组,通过数组初始化来提供支持。
数组代表一系列对象或者基本数据类型,定义数组,有两种方式
int[] a1;
int a1[];
后一种方式与C和C++习惯的格式是一致的,但是,最“通顺”的还是前一种语法,因为它指出类型是“一个int数组”。
知道数组的定义有两种方式即可,但自己在定义的时候选择第一种,忽略第二中方式!
在定义数组的过程中,编译器不允许我们告诉它有多大。此时,我们拥有的一切就是指向数组的一个句柄(引用,句柄这个词来自《编程思想》这本书,其实就是引用的意思!
),而且尚未给数组分配任何空间。为了给数组创建相应的存储空间,必须编写一个初始化表达式。
对于数组,初始化工作可在代码的任何地方出现,但也可以使用一种特殊的初始化表达式,它必须在数组创建的地方出现。这种特殊的初始化实际上就是静态初始化:
int[] a1={1,2,3,4,5};
存储空间的分配(等价于使用new)将由编译器在这种情况下进行。
//:Arrays.java
//Arrays of primitives.
public class Arrays{
public static void main(String[] args){
int[] a1={1,2,3,4,5};
int[] a2;
a2=a1;
for(int i=0;i<a2.length;i++){
a2[i]++;
prt("a2["+i+"]="+a2[i]);
}
for(int i=0;i<a1.length;i++){
prt("a1["+i+"]="+a1[i]);
}
static void prt(String s){
System.out.println(s);
}
}
}
结果为:
a2[0]=2
a2[1]=3
a2[2]=4
a2[3]=5
a2[4]=6
a1[0]=2
a1[1]=3
a1[2]=4
a1[3]=5
a1[4]=6
可以看到,a1获得了一个初始值,而a2没有;a2将在以后赋值----这种情况下是赋给另一个数组。所有数组都有一个本质成员(无论它们是对象数组还是基本类型数组),可对其进行查询—但不是改变,从而获知数组内包含了多少个元素。这个成员就是length。与C 和C++类似,由于Java 数组从元素0 开始计数,所以能索引的最大元素编号是“length-1”。如超出边界,C 和C++会“默默”地接受,并允许我们胡乱使用自己的内存,这正是许多程序错误的根源。然而,Java 可保留我们这受这一问题的损害,方法是一旦超过边界,就生成一个运行期错误(即一个“违例”)。当然,由于需要检查每个数组的访问,所以会消耗一定的时间和多余的代码量,而且没有办法把它关闭。这意味着数组访问可能成为程序效率低下的重要原因——如果它们在关键的场合进行。但考虑到因特网访问的安全,以及程序员的编程效率,Java 设计人员还是应该把它看作是值得的。
程序编写期间,如果不知道在自己的数组里需要多少元素,那么又该怎么办呢?此时,只需简单地用new 在数组里创建元素。在这里,即使准备创建的是一个基本数据类型的数组,new 也能正常地工作(new 不会创建非数组的基本类型);
这里,通过new进行数组初始化,是一种动态初始化;
动态初始化也分以下几类:
Object[] arr3 = new Object[]{"a","b","c"};
Object[] arr2 = new Object[3];
arr2[1] = "a";
arr2[2] = "b";
arr2[3] = "c";
//先确认元素个数,一般情况下习惯使用动态创建方式 比较灵活 可以先规定元素个数 后对每个元素进行赋值
Object[] arr4 = null;
arr4 = new Object[3];
arr4[1] = "a";
arr4[2] = "b";
arr4[3] = "c";
//直接给arr4初始化为null,然后要使用的时候再创建一个新的数组new Object[3],让arr4指向新数组的地址,然后再依次赋值
了解的数组的两种初始化方式,在看下面这个程序:
//:ArrayNew.java
//Creating arrays with new.
import java.util.*;
public class ArrayNew{
static Random rand=new Random();
static int pRand(int mod){
return Math.abs(rand.nextInt())%mod+1;
}
public static void main(String[] args){
int[] a;
a=new int[pRand(20)];//先初始化,在动态分配内存
prt("length of a="+a.length);
for(int i=0;i<a.length;i++){
prt("a["+i+"]="+a[i]);
}
}
static void prt(String s){
System.out.println(s);
}
}
数组的定义是在编译期间确定的,一般来说,数组的大小也是编译期间确定的,但是这个程序中,数组的大小由一个方法提供,该数组的大小也在运行期间确定,对于基本数据类型,JVM会自动初始化成“空”值(对于数值,空值就是零;对于char,它是null ;而对于boolean,它却是false)。
C语言中,数组的长度是在编译时确定,这样就能为它分配内寸,但是不会初始化.
若操作的是一个非基本类型对象的数组,那么无论如何都要使用new。在这里,我们会再一次遇到句柄问题,因为我们创建的是一个句柄数组。请大家观察封装器类型Integer,它是一个类,而非基本数据类型:
//:ArrayClassObj.java
//Creating an arrays of non-primitive objects.
import java.util.*;
public class ArrayClassObj{
static Random rand=new Random();
static int pRand(int mod){
return Math.abs(rand.nextInt())%mod+1;
}
public static void main(String[] args){
Integer[] a=new Integer[pFr];
a=new int[pRand(20)];//先初始化,在动态分配内存
prt("length of a="+a.length);
for(int i=0;i<a.length;i++){
prt("a["+i+"]="+a[i]);
}
}
static void prt(String s){
System.out.println(s);
}
}
在这儿,甚至在new 调用后才开始创建数组:
Integer[] a = new Integer[pRand(20)];
它只是一个句柄数组,而且除非通过创建一个新的Integer 对象,从而初始化了对象句柄,否则初始化进程不会结束:
a[i] = new Integer(pRand(500));
但若忘记创建对象,就会在运行期试图读取空数组位置时获得一个“违例”错误。
下面让我们看看打印语句中String 对象的构成情况。大家可看到指向Integer 对象的句柄会自动转换,从而产生一个String,它代表着位于对象内部的值。
亦可用花括号封闭列表来初始化对象数组。可采用两种形式,第一种是Java 1.0 允许的唯一形式。第二种(等价)形式自Java 1.1 才开始提供支持:
//: ArrayInit.java
// Array initialization
public class ArrayInit {
public static void main(String[] args) {
Integer[] a = {
new Integer(1),
new Integer(2),
new Integer(3),
};
// Java 1.1 only:
Integer[] b = new Integer[] {
new Integer(1),
new Integer(2),
new Integer(3),
};
}
} ///:~
这种做法大多数时候都很有用,但限制也是最大的,因为数组的大小是在编译期间决定的。初始化列表的最后一个逗号是可选的(这一特性使长列表的维护变得更加容易)。
数组初始化的第二种形式(Java 1.1 开始支持)提供了一种更简便的语法,可创建和调用方法,获得与C 的“变量参数列表”(C 通常把它简称为“变参表”)一致的效果。这些效果包括未知的参数(自变量)数量
以及未知的类型(如果这样选择的话)。由于所有类最终都是从通用的根类Object 中继承的,所以能创建一个方法,令其获取一个Object 数组,并象下面这样调用它:
//: VarArgs.java
// Using the Java 1.1 array syntax to create
// variable argument lists
class A { int i; }
public class VarArgs {
static void f(Object[] x) {
for(int i = 0; i < x.length; i++)
System.out.println(x[i]);
}
public static void main(String[] args) {
f(new Object[] {
new Integer(47), new VarArgs(),
new Float(3.14), new Double(11.11) });
f(new Object[] {"one", "two", "three" });
f(new Object[] {new A(), new A(), new A()});
}
} ///:~
问题1
首先因为数组初始化要使用的时候必须确定数组的长度,也就是说数组的长度是不可变的。
在JAVA中创建数组有两种方式 :
(1)静态创建 如
Object[] arr1 = {"a","b","c"};
(2)动态创建 如
1
Object[] arr3 = new Object[]{"a","b","c"};
2
Object[] arr2 = new Object[3];
arr2[1] = "a";
arr2[2] = "b";
arr2[3] = "c";
//先确认元素个数,一般情况下习惯使用动态创建方式 比较灵活 可以先规定元素个数 后对每个元素进行赋值
3
Object[] arr4 = null;
arr4 = new Object[3];
arr4[1] = "a";
arr4[2] = "b";
arr4[3] = "c";
//直接给arr4初始化为null,然后要使用的时候再创建一个新的数组new Object[3],让arr4指向新数组的地址,然后再依次赋值
这种方法:
Object[] params = null;
params = {1, 2, 3, 4};
错误一:这是直接创建一个空数组params,也就是他就是空了,数组的长度不可改变,你这时候往里面加数据,一个空数组自然加不了任何东西,所以编译时就会出错。你应该跟我上面所说的第二种方法一样,重新创建一个长度为4的数组,把新数组的地址给params,也就是Object[] params = null; params = new Object[4];这样就可以创建。
错误二:数组的初始化只有在定义的时候可以连续添加,定义完
Object[] params = null;
params = new Object[4];
之后,你要向里面添加数据,就只能对着数组下标,一个一个添加。
params[1] = 1;
params[2] = 2;
params[23 = 3;
params[4] = 4;
而不能直接params = {1, 2, 3, 4};
问题2
if (array == null || 0 == array.length) {...} // 这种写法正确,因为执行到 “0 == array.length”则说明数组不为空,不会产生空指针异常。
if (0 == array.length || array == null) {...} // 这种写法可能会产生空指针异常。
问题分析:
int[] n; //只声明了一数组变量;
int[] nil = null; //声明一数组变量,并赋值 null,nil是一个数组类型的空引用,不指向任何对象;
int[] zero = new int[0]; //声明并创建一数组对象, zero是一个长度为0的数组,我们称之为“空数组”,空数组也是一个对象,只是包含元素个数为0;
对于上面三条语句,一个比一个做的动作多,系统占用也是后面的多:
语句一变量还没初始化,打印 n 会出错:“可能尚未初始化变量 n”;
语句二虽已初始化,打印“nil.length”会出现异常:NullPointerException;
语句三打印“nil.length”是0,数组内还没有元素。
假设一个方法返回一个数组,如果它返回null,则调用方法必须先判断是否返回null,才能对放回数组进一步处理,而如果返回空数组,则无须null引用检查。鉴于此,返回数组的方法在没有结果时我们通常返回空数组,而不是null,这样做对于函数调用者的处理比较方便。