两种声明方法
type[] arrayName;
type arrayName[];
java数组是一种引用型的变量,所以,不要试图在声明时为数组指定大小。这点C++程序员要注意,因为C++的数组不加限定的化是在函数栈上分配的,而java则是在堆里分配的。
数组的创建
数组名 = new 数组元素的类型 [数组元素的个数]
如果需要在创建的时候为数组指定初始值,请使用{}
int[][][] b = {{{1,2,3},{1,2,3}},{{3,4,1},{2,3,4}}};
数组是对象吗?
java数组实际上是Array类的对象。Array类并没有public的构造方法,需要使用newInstance来构造新对象。如下两种写法是等价的
//way1
Object array = Array.newInstance(int.class,2);
//way2
int[] anArray ;
anArray = new int[2];
所以,数组就是对象,只不过是声明和创建方式不同的对象罢了,这种不同是为了复合过去的编程习惯而已。
java数组的实现方法
java的数组是真数组。这意味着元素是在内存地址里顺序摆放的。但是,java会对下标越界进行检查。当然对象数组里顺序摆放的只是引用。
java多维数组
如果确实有需要,也可以使用多维数组
int num1[][]; //二维数组声明
int num1[][][]; //三维数组声明
...
m = new int[4][4];//二维数组创建
java二维数组实际上是创建了一个数组的数组,三维数组就是一个数组的数组的数组…
聪明的你一定立即会想到这里面有浅拷贝和深拷贝问题。在这里该问题不再赘述,只是总结三种深拷贝的方法
- (1)使用for循环,将数组的每个元素复制(需要将每个对象调clone方法,才能实现真正的复制)
- (2) 使用clone方法,得到数组的值,而不是引用
- (3)使用System.arraycopy方法。
第一种方法最好理解,自己实现而已。第二第三种方法则只能实现一层的数组拷贝。也就是说他们设计上只是拷贝一维数组的。所以,你需要在每个一维数组使用该方法。一般认为arraycopy方法的效率最高。for循环效率最慢。这是因为arraycopy调用的是JNI方法。
数组与范型
//下面这句代码无法通过编译
Object[] objArray = new ArrayList<String>[10];
//下面这句话则可以
List<Integer>[] genericArray = (List<Integer>[])new ArrayList[10];
之所以禁止范型数组,是因为类型擦除后,编译器 ArrayList《String》 被擦除成为ArrayList《Object》,此时
如果你使用上述的第二种方式,那么如果你使用下的语句,就只有在实际运行时才能抛出异常了。
genericArray[0] = new ArrayList<String>(Arrays.asList(new String[]{"Hello"}));
数组的协变
下面的代码是可以通过编译的。但是运行期抛出异常。(ArrayStoreException)
Object[] objArray = new String[10];
objArray[0] = Integer.valueOf(1);
System.out.println(objArray[0]);
运行期,数组是记得自己内部元素的类型的。如果加入的元素不符合,就会抛出异常。但是编译器为什么允许这个代码通过编译呢?这被称为允许协变。effective java的作者明确说过,允许协变是java的一个败笔。不过,败笔也有败笔的理由。可以大胆猜测,java作者在构思java的时候受到了C/C++的影响,把数组设计成只能允许一种数据类型存储的结果(字节码中创建数组必须指定一个数据类型)。然而,等到他写比较两个数组中的元素是否相等时,发现了一个问题。你当然不能为每一个类型都实现对应的Arrays.equals方法!当时还没有范型,唯一的写法似乎就是如下
public static boolean equals(Object[] a, Object[] 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++) {
Object o1 = a[i];
Object o2 = a2[i];
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return true;
}
最简单的做法就是把所有对象数组类的父类都是Object[]类。这时候回想我们的里氏替换原则,所有子类实例都必须能替换父类实例,Object[]可以接受一切Object,凭啥你的Integer[]就要敢不接受Float呢?Integer[]肯定从Object[]那里继承了添加Object的方法嘛。
这么多年后,我们觉得最好解决问题的方式是范型。
public static <T extends Object> boolean equals(T[] a, T[] 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++) {
T o1 = a[i];
T o2 = a2[i];
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return true;
}
但是,在那个没有范型年代,这样的设计也实属无奈。而考虑到Arrays.equals这个方法好多年了,突然说原有调用方式不支持了,有多少代码(包含JDK代码)都需要重写。所以,这个问题也成为了java永远的痛。
数组与范型
下面的语句不能通过编译。
List<String> a[] = new LinkedList<String>[10]
下面的代码反而可以通过编译
List<String> a[];
a = new List[10];
a[0] = new ArrayList<String>();
System.out.println("finished");
为什么要这么设计的。。。我也不知道。