第十一章 持有对象
Set对于每个值都只保存一个对象,map是允许你将某些对象与其他一些对象关联起来的关联数组,因此,与数组不同,在编程是,你可以将任意数量的对象放置到容器中,并且不需要担心容器应该设置为多大。容器类的基本类型是List,Set,Queue,Map。这些对象类型也成为集合类。
1.泛型类和类型安全的容器
使用javaSE之前的容器的一个主要问题就是编译器允许你向容器中插入不正确的类型。例如,考虑一个Apple对象的容器,我们使用最基本最可靠的容器ArrayList。现在,你可以把ArrayList仿作“可以自动扩充自身容器的数组”来看待。使用ArrayList相当简单:创建一个实例,用add()插入对象;然后用get()访问这些对象,此时需要使用索引,就像数组一样,但是不需要方括号。ArrayList还有一个size()方法,使你可以知道已经有多少元素添加了进来,从而不小心因索引越界而引发错误。例如,在下面的例子中,将Apple和Orange都放置在了容器中,然后将他们取出。
public class Apple {
private static long counter;
private final long id = counter++;
public long id(){
return id;
}
}
public class Orange {
}
public class ApplesAndOrangeWithoutGenerics {
public static void main(String[] args) {
ArrayList apples = new ArrayList();
for (int i = 0; i < 3; i++) {
apples.add(new Apple());
}
apples.add(new Orange());
for (int i = 0; i < apples.size(); i++) {
((Apple) apples.get(i)).id();
}
}
}
Apple和Orange是有区别的,它们除了都是Object之外没有任何共性。因为ArrayList 保存的是Object,因此你不仅可以通过ArrayList 的add()方法将Apple对象放进这个容器,还可以添加Orange对象,而且无论在编译器还是运行时都不会有问题。当你再使用ArrayList 的get()方法来取出你认为是Apple对象时,你得到的只是Object引用,必须将其转换位Apple,因此,需要将整个表达式括起来,在调用Apple的id()方法之前,强制转型。否则,就会有语法错误。在运行时,当你试图将Orange对象转型为Apple时,就会出现java.lang.ClassCastException异常。
然而通过使用泛型俩创建,就可以在编译器防止将错误类型的对象放置到容器中。修改上面的代码为:
public class ApplesAndOrangeWithoutGenerics {
public static void main(String[] args) {
ArrayList<Apple> apples = new ArrayList<Apple>();
for (int i = 0; i < 3; i++) {
apples.add(new Apple());
}
// apples.add(new Orange());
for (int i = 0; i < apples.size(); i++) {
System.out.println(apples.get(i).id());
}
}
}
现在编译器可以阻止你将Orange放到apples中,因此它变成了一个编译期错误,而不是运行时错误。这样的话,在你将元素从List中取出来时,类型转换也不再是必需的了。因为List知道它保存的是什么类型,因此它会在调用get()时替你执行转型。
2.基本概念
java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
(1)collection一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,List中可以有重复的元素,而Set不能有重复元素。Queue按照排队规则来确定对象产生的顺序。
(2)Map。一组成对的“键值对”对象,允许你使用键来查找值。ArrayList允许你使用数字来查找值,隐藏在某种意义上讲,它将数字和对象关联起来。映射表允许我们使用另一个对象来查找某个对象,它也被称为“关联数组”,因为它将某些对象与另外一些对象关联在一起;或者称为“字典”,因为你可以使用键对象来查找值对象,就像在字典中使用单词来定义一样。Map是强大的编程工具。
尽管并非总是这样,但是在理想的情况下,你编写的大部分代码都是在与这些接口打交道,并且你唯一需要指定使用的精确类型的地方就是在创建的时候。因此你可以这样创建List:
List<Apple> apples = new ArrayList<Apple>()
,因此,你应该创建一个具体类的对象,将其转型为对应的接口,然后再其余代码中都使用这个接口。
3.添加一组元素
在java.util包中的Arrays和Collections类中都有很多实用方法,可以在一个Collection中添加一组元素。Arrays.asList()方法接受一个数组或是一个用逗号分隔的元素列表(使用可变参数),并将其转换为一个List对象。Collections.addAll()方法接受一个Collection对象,以及一个数组或是一个用逗号分隔的列表,将元素添加到Collection中。下面的例子展现这两个方法:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class AddingGroups {
public static void main(String[] args) {
Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5));
Integer[] moreInts = {6,7,8,9,10};
collection.addAll(Arrays.asList(moreInts));
Collections.addAll(collection, 11,12,13,14,15);
Collections.addAll(collection, moreInts);
List<Integer> list = Arrays.asList(16,17,18,19,20);
list.set(1,99);
}
}
Collection的构造器可以接受一个Collection,用它来将自身初始化,因此你可以使用Arrays.asList()来为这个构造器产生输入。但是,Collections.addAll()方法运行起来要快的多,而且构建一个不包含元素的Collection,然后调用Collections.addAll()这种方式很方便,因此它是首选方式。
4.Collection和Collections的区别
java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。