泛型数组列表
在Java中,允许在运行时确定数组的大小。
int a=....;
Employee[] staff=new Employee[a];
当然,这段代码没有解决运行时动态更改数组的问题。一旦,确定了数组的大小,改变它就不容易。在java中,解决这个问题最简单的方法是使用Java中另外一个被称为ArrayList的类。它使用起来有点像数组,但在添加或者删除元素时,具有自动调节数组容量的功能,而不需要为此编写任何代码。
ArrayList是一个采用类型参数的泛型类。为了指定数组列表保存的元素对象类型,需要用一对尖括号将类名括起来加在后面。例如:ArrayList<Employee>.
下面声明和构造一个保存Employee对象的数组列表:
ArrayList<Employee> staff = new ArrayList<Employee>();
在Java SE 7中,可以省去右边的类型参数:
ArrayList<Employee> staff = new ArrayList<>();
这被称为“菱形”语法。
使用add方法可以将元素添加到数组列表中。如下:
staff.add(new Employee("aaa"),...);
staff.add(new Employee("bbb"),...);
数组列表管理着对象引用的一个内部数组。最终,数组的全部空间有可能被耗尽。这时,如果调用a
dd且内部数组已经满了,数组列表就将自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。
如果已经清楚或能够估计出数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity方法:
staff.ensureCapacity(100);
另外,可以将初始容量传递给ArrayList的构造器:
ArrayList<Employee> staff = new ArrayList<>(100);
数组列表的容量与数组的大小有一个非常重要的区别。如果为数组分配100个元素的存储空间,数组就有100个空位置可以使用。而容量为100个元素的数组列表只是拥有保存100个元素的潜力,事实上,重新分配空间的话,将会超过100,但是在最初,甚至在完成初始化构造之后,数组列表根本就不含任何元素。
size方法将返回数组列表中包含的实际元素数目。例如:
staff.size();
将返回staff数组列表的当前元素数量,它等价于数组a的a.length。
一旦能够确认数组列表的大小不再发生变化,就可以调用trimToSize方法。这个方法将存储区域的大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将多余的存储空间回收。
一旦整理了数组列表的大小,添加新元素就需要花时间再次移动储存块,所以应该在确认不会添加任何元素时,在调用trimToSize。
访问数组元素
数组列表自动扩展容量的便利增加了访问元素语法的复杂程度。其原因是ArrayList类并不是Java程序设计语言的一部分;它只是一个由某些人编写且被放在标准库中的一个实用类。
使用get、set方法实现访问或改变数组元素的操作,而不使用人们喜爱[ ]语法格式。
例如,要设置第i个元素,可以使用:
staff.set(i,harry);
它等价于对数组a的元素赋值
a[i]=harry;
需要注意的是,只有i小于或等于数组列表的大小时,才能够调用list.set(i,x)。使用add方法为数组添加新元素,而不要使用set方法,它只能替换数组中已经存在的元素内容。
同样的,获得数组列表的元素如下:
Employee e=staff.get[i];
等价于:
Employee e=a[i];
在编程时,我们可以用一个技巧,即可以灵活地扩展数组,又可以方便地访问数组元素。
首先,创建一个数组,并添加所有元素。
ArrayList<X> staff = new ArrayList<>(100);
while(...){
x=...;
list.add(x);
}
执行完上述操作之后,使用toArray方法将数组元素拷贝到一个数组中。
x[] a=new X[list.size()];
list.toArray(a);
除了在数组列表的尾部追加元素之外,还可以在数组列表的中间插入元素,使用带索引参数的add方法。
int n=staff.size()/2;
staff.add(n,e);
为了插入一个新元素,位于n之后的所有元素都要向后移动一个位置。如果插入新元素后,数组列表的大小超过了容量,数组列表就会重新分配存储空间。
同理,删除操作的原理一样:
Employee e-staff.remove(n);
位于该元素后面的全部元素都向前移动一个位置,并且数组的大小减一。
对数组实施插入与删除元素的操作效率低下。对于小型数组来说,这一点无所谓。但是,对于大型数组,我们需要使用链表。
可以使用foreach循环遍历数组列表:
for(Employee e:staff){
......;
}
我们把前面用数组写的程序,改成用ArrayList的:
public class qwe {
public static void main(String[] args) {
ArrayList<Employee> staff = new ArrayList<Employee>();
staff.add(new Employee("aaa",7500,1998,12,12));
staff.add(new Employee("bbb", 2500, 1979, 6, 4));
staff.add(new Employee("ccc", 5500, 1988, 2, 7));
for (Employee employee : staff) {
employee.raiseSalary(10);
}
for (Employee employee : staff) {
System.out.println(employee.toString());
}
}
}
类型化与原始数组列表的兼容性
在我们的代码中,我们可能更愿意使用类型参数来增加安全性。下面,我们说说如何与贸易使用类型参数的遗留代码交互操作。
假设有下面这个遗留下来的类:
public class EmployeeDB(){
public void uodate(ArrayList list){....}
public ArrayList find(String query){.....}
}
可以将一个类型化的数组列表传递给update方法,并不需要进行任何的类型转换。
ArrayList<Employee> staff=...;
EmployeeDB.update(staff);
也可以将staff对象传递给update方法。
相反的,将一个原始的ArrayList赋给一个类型化ArrayList会得到一个警告。
ArrayList<Employee> result=EmployeeDB.find(query); //it is warining
但是,使用类型转换也不能避免出现警告。