CoreJava读书笔记--继承(三)--泛型数组列表、自动装箱与拆箱

泛型数组列表

在许多程序语言中,特别是C++,必须在编译时就确定整个数组的大小,一旦确定,想要改变就不那么容易。而在Java中,有一个ArrayList类可以解决这个问题。它使用起来有点像数组,但在添加或删除元素时,具有自动调节数组容量的功能。

ArrayList是一个采用类型参数(type parameter)的泛型类(generic class)。为了指定数组列表保存的元素对象类型,需要用一对尖括号将类名括起来加在后面,例如:

ArrayList<Employee> staff = new ArrayList<Employee>();

使用add方法就可以将元素添加到数组中:

staff.add(new Employee("Harry Hacker",....));
staff.add(new Student("Bob",15,...));

如果调用add且内部数组已经满了,数组列表就会创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。如果我们在已经清楚或能够估计数组大小的情况下,就可以在填充数组之前调用ensureCapacity方法来分配数组大小。

staff.ensureCapacity(100);//分配一个包含100个对象的内部数组,然后调用100次add方法,而不用重新分配空间
new ArrayList<>(100);//capacity is 100
new Employee[100];//size is 100

注意:上面这两行代码是有区别的,指定数组的size为100时,那么数组就有100个空位置可以使用,而capacity为100,只是代表它拥有保存100个元素的能力。staff.size()方法将返回数组列表中包含实际元素数目

一旦能够确认数组列表的大小不再发生变化,就可以调用trimToSize方法,这个方法将根据实际所需调整存储区域的大小,垃圾回收器将回收多余的存储空间。如果使用了该方法后,还要再添加元素,就需要花时间再次移动存储块,所以应该在确定不会添加任何元素时,再调用trimToSize。

常用方法:

ArrayList<E>();//构造一个空数组列表
ArrayList<E>(int initialCapacity);//用指定容量构造一个空数组列表,initialCapacity是数组列表最初容量
boolean add(E obj);//在数组列表尾端添加一个元素,永远返回true。
int size();//返回存储在数组列表中的当前元素数量。
void ensureCapacity(int capacity);//确保数组列表不重新分配存储空间的情况下就能够保存给定数量的元素
void trimToSize();//将数组列表的存储容量削减到当前尺寸

(一)访问数组列表元素

虽然数组列表能自动扩展容量,但是也给访问其中元素带来了麻烦。使用get和set方法实现访问或改变数组元素的操作。例如要设置第i个元素,可以使用:

staff.set(i,harry);

注意:数组列表从1开始,而不是从0开始。要想添加新的元素只能用add方法,set方法是用来改变已有的元素,而不能用作添加新元素。

下面这段代码既可以灵活扩展数组,又可以方便地访问数组元素:

ArrayList<X> list = new ArrayList<X>();
while(...){
  x=...;
  list.add(x);
}
X[] a = new X[list.size()];
list.toArray(a);

add()方式是在数组列表的尾部添加元素,我们也可以使用带索引参数的add方法:

int n = staff.size()/2;
staff.add(n,e);

向数组列表的中间插入元素,位于n之后的元素都要向后移动一个位置,如果超过了数组列表的大小,就会重新分配空间。同理删除一个元素用staff.remove(n),那么n之后的元素都要向前移动一个位置。

对象包装器与自动装箱

有时需要将int这样的基本类型转换为对象,所有的基本类型都有一个与之对应的类。例如:int对应Integer ,通常这些类称为包装器(wrapper)。基本类型都有对应的对象包装器:Integer,Float,Long,Double,Short,Byte,Character,Void,Boolean。

对象包装器类是不可变得,同时也是final修饰的,即不能定义子类。

从下面看看自动装箱(autoboxing):

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(3);
//3是基本类型,我们看到应该往list里面添加的是Integer对象,其实编译器自动帮我们进行了装箱操作
//list.add(Integer.valueOf(3));

再来看看自动拆箱操作:

int n = list.get(i);
//翻译成 int n = list.get(i).intValue();

关于自动装箱有两点说明:

①由于包装器类可以引用null,所以自动装箱有可能会抛出一个NullPointerException

②如果在一个条件表达式中混合使用了Integer和Double类型,Integer值会拆箱,提升为double,再装箱为Double:

Integer n = 1;
Double x = 2.0;
System.out.println(true?n:x);//print 1.0

参数数量可变的方法

例如我们前面见到的printf方法:

System.out.printf("%d",n);

System.out.printf("%d %s",n,"widgets");

在上面两行代码中,我们可以看到printf方法一个有两个参数,另一个有三个参数。我们看看printf方法是如何定义的:

public class PrintStream{
    public PrintStream printf(String fmt,Object... args){
        return format(fmt,args);
    }

}

这里的省略号...是Java代码的一部分,它表明这个方法可以接收任意数量的对象。

我们也可以自定义一个参数数量可变的方法,并将参数指定为任意类型或者是基本类型。例如:

public static double max(double... values){
    double largest  = Double.NEGATIVE_INFINITY;
    for(double v : values){
        if(v>largest){
            largest=v;
        }
    }
    return largest;
}

猜你喜欢

转载自blog.csdn.net/zxyy2627/article/details/82621216