11.1 泛型和类型安全的容器
在JavaSE5之前的容器,编译器允许我们向容器中放入不正确的类型。诸如,有一个Apple对象的容器,它是一个ArrayList,而我们也可以往它里面放入一个Orange对象(这里的Apple和Orange除了继承Object之外,没有其他共性)。因为ArrayList保存的是Object,所以添加Apple、Orange在编译期和运行期都没问题。但是,当从ArrayList中取出时,取出的是Object对象,这就需要强转为Apple或Orange对象。若是操作失误,将Orange强转为Apple就会得到一个异常:java.lang.ClassCastException。
通过预定义的泛型可以解决这个问题。比如:要想定义用来保存Apple对象的容器ArrayList,我们可以通过类型参数声明一个ArrayList
当我们指定了某个类型作为泛型参数时,并不仅限于将确切类型的对象添加到容器中,向上转型也适用于泛型。比如,红富士也是一种苹果(Fuji extends Apple),这样就可以将Fuji的对象添加到ArrayList
练习题1:创建一个新类Gerbil(沙鼠),包含int gerbilNumber属性,在构造器中初始化它。添加一个方法hop(),用以打印沙鼠的号码以及它正在跳跃的信息。创建一个ArrayList,并向其中添加一串Gerbil对象。使用get()遍历List,并且对每个Gerbil调用hop()。
代码如下:
public class Gerbil {
private int gerbilNumber;
public Gerbil(int gerbilNumber) {
this.gerbilNumber = gerbilNumber;
}
public void hop() {
System.out.println("第" + gerbilNumber + "只沙鼠,正在跳跃");
}
public static void main(String[] args) {
List
for (int i = 1; i <= 5; i++) {
Gerbil gerbil = new Gerbil(i);
gerbils.add(gerbil);
}
int size = gerbils.size();
for (int i = 0; i < size; i++) {
Gerbil gerbil = gerbils.get(i);
gerbil.hop();
}
}
}
11.2 基本概念
Java容器类类库被划分为两个不同的概念:
①、Collection:一个独立元素的序列(一种存放一组对象的方式),这些元素都服从一条或多条规则。List要按照插入顺序保存元素,Set不能有重复元素。Queue按照排队规则(FIFO)来确定对象产生的顺序。
②、Map:一组成对的"键值对"对象,允许我们使用键来查找值。而键可以是其他对象,甚至是null。通过其他对象来查找某个对象,也被称为"关联数组"或"字典"。
练习题2:修改SimpleCollection.java,使用Set来表示c。
代码如下:
public class SimpleCollection {
public static void main(String[] args) {
Collection
for (int i = 0; i < 10; i++) {
c.add(i);
}
for (Integer i : c) {
System.out.print(i + " ");
}
}
}
练习题3:修改innerclasses/Sequence.java,使你可以向其中添加任意数量的元素。
如下代码:
public class SeqContainer
private List
public void add(T t) {
container.add(t);
}
private class SequenceSelector implements Selector {
private int i = 0;
@Override
public boolean end() {
return i == container.size();
}
@Override
public T current() {
return container.get(i);
}
@Override
public void next() {
if (i < container.size()) {
i++;
}
}
}
public Selector getSelector() {
return new SequenceSelector();
}
public Selector reverseSelector() {
return new Selector() {
private int i = container.size() - 1;
@Override
public void next() {
if (i >= 0)
i--;
}
@Override
public boolean end() {
return i < 0;
}
@Override
public T current() {
return container.get(i);
}
};
}
public static void main(String[] args) {
SeqContainer
for (int i = 0; i < 10; i++) {
Employee emp = new Employee(i, "compony" + i);
container.add(emp);
}
Selector selector = container.getSelector();
while (!selector.end()) {
System.out.println(selector.current());
selector.next();
}
Selector reverseSelector = container.reverseSelector();
while (!reverseSelector.end()) {
System.out.println(reverseSelector.current());
reverseSelector.next();
}
}
}
11.3 添加一组元素
书中介绍了几种在容器中添加一组元素的方式。
方式一:使用Collection的构造器,它可以接受另一个Collection。比如:ArrayList的构造器ArrayList(Collection extends E> c)。
方式二:使用Collection的addAll()方法,它可以接受另一个Collection,其方法签名如下:
boolean addAll(Collection extends E> c)
方式三:使用Arrays的asList()方法,它可以接受数组或一个用逗号分隔的元素列表(可变参数),其方法签名如下:
static
注:asList()方法返回的ArrayList是Arrays的内部类,而不是java.util.ArrayList。
方式四:使用Collections的addAll()方法,它可以接受数组或一个用逗号分隔的元素列表(可变参数),其方法签名如下:
static
书中说Arrays.asList()在某些场景下需要告诉编译器产生的List类型的实际的目标类型(显式类型参数说明),我这里试了一下发现JDK1.8已经修复了这个问题,而JDK1.8以下会报错:
class Snow {
}
class Powder extends Snow {
}
class Light extends Powder {
}
class Heavy extends Powder {
}
class Crusty extends Snow {
}
class Slush extends Snow {
}
public class AsListInference {
public static void main(String[] args) {
List
// 在编译器1.8之下报错:Type mismatch: cannot convert from List
List
Collections.addAll(snow, new Light(), new Heavy());
List
}
}
备注:Arrays和Collections里有很多工具方法来操作容器,比如二分查找、排序、max()、min()等,需要可以自行查询文档。
11.4 容器的打印
使用标准输出(System.out)打印容器的时候,由于容器实现了toString()方法,因此,打印结果可读性很好。但是,打印数组则需要借助Arrays.toString()方法,比如打印一个int数组:Arrays.toString(new int[] { 34, 21, 7, 87, 45 })。
练习题4:创建一个生成器类,它可以在每次调用其next()方法时,产生你最喜欢的电影的名字。在电影名列表的名字用完之后,循环到该列表的开始处。使用这个生成器来填充数组、ArrayList、LinkedList、HashSet、LinkedHashSet和TreeSet,然后打印每个容器。
代码如下:
public class MovieGenerator {
private String[] movies = new String[] { "SS", "DD", "HH", "FF", "XX", "ZZ" };
private int i = 0;
public String next() {
return movies[i++ % movies.length];
}
public String[] getMovies() {
return movies;
}
public Collection
for (int i = 0; i < 8; i++) {
collection.add(next());
}
return collection;
}
public static void main(String[] args) {
MovieGenerator generator = new MovieGenerator();
System.out.println(Arrays.toString(generator.getMovies()));
System.out.println(generator.fill(new ArrayList
System.out.println(generator.fill(new LinkedList
System.out.println(generator.fill(new HashSet
System.out.println(generator.fill(new LinkedHashSet
System.out.println(generator.fill(new TreeSet
}
}
运行结果如下:
[SS, DD, HH, FF, XX, ZZ]
[SS, DD, HH, FF, XX, ZZ, SS, DD]
[HH, FF, XX, ZZ, SS, DD, HH, FF]
[XX, ZZ, SS, DD, HH, FF]
[SS, DD, HH, FF, XX, ZZ]
[DD, FF, HH, SS, XX, ZZ]
备注:本来想用这些电影名: { "电锯惊魂", "死神来了", "咒怨", "音乐僵尸", "僵尸至尊", "驱魔警察" },但是发现TreeSet输出不是预想的顺序,因此使用 { "SS", "DD", "HH", "FF", "XX", "ZZ" }更明显一些,后续文章再关注TreeSet中文排序。