第11章 持有对象
若果一个程序只包含固定数量的且生命周期都是已知的对象,那么这是一个非常简单的程序。
书上第一句话就是这个,什么意思呢?就是说很多时候我们事先都不知道创建对象的数量和生命周期,不是简单的程序,这个时候就要用到我们第一章说到的容器的概念。
从这里,引出了本章所讲授的内容,即 java 中的容器。
Java 中基本的容器有:List/Set/Queue/Map
11.1 泛型和类型安全的容器
这一个小节想告诉我们,在创建容器的时候,最好使用“泛型”来创建一个安全的容器。
书上给出了两个例子作为对比。
package holding;
import java.util.ArrayList;
class Apple{
private static long counter;
private final long id = counter++;
public long id(){return id;}
}
class Orange{}
public class ApplesAndOranges {
@SuppressWarnings("unchecked")
public static void main(String args[]){
ArrayList apples = new ArrayList();
for(int i=0;i<3;i++)
apples.add(new Apple());
// 添加一个 Orange 对象也是不被拒绝的
apples.add(new Orange());
for(int i=0;i<apples.size();i++){
((Apple)apples.get(i)).id();
// 运行的时候才会检测到容器中有一个不同类型的元素
}
}
}
在上面这个例子中,我们在声明容器 apple 对象的时候,并没有给出泛型来指定其中存放的元素类型,于是,在添加一个 Orange 对象时候,编译也是正确的,直到访问元素时候系统才会发现错误。
与之相对应的是,在一开始声明 容器的时候就使用泛型来指定容器中的对象类型:
ArrayList<Apple> apples = new ArrayList<Apple>();
这样在添加元素时候,编译器就会检查出相应的错误,这就是所谓的类型安全的容器的声明方法。
这里可以用 foreach 语法来访问 List 中的元素,同样List中支持 upcast。看一下下面这个例子。
package holding;
import java.util.ArrayList;
class GrannySmith extends Apple{}
class Gala extends Apple{}
class Fuji extends Apple{}
class Braebrun extends Apple{}
public class GenericsAndUpcast {
public static void main(String args[]){
ArrayList<Apple> apples = new ArrayList<Apple>();
apples.add(new GrannySmith());
apples.add(new Gala());
apples.add(new Fuji());
apples.add(new Braebrun());
for(Apple c:apples){ // upcast
System.out.println(c); // 自动调用 toString()方法
}
}
}
11.2 基本概念
Java 容器类库的用途是“保存对象”,将其划分成为两个不同的概念:
1)Collection。一个独立元素的序列,这些元素都符合一定的规则。比如List,是要按照一定的顺序插入;又比如Set,不能有重复的元素;再比如Queue,按照排队顺序来确定对象产生的顺序。
2) Map。“键值对”对象,是一种映射表,也叫做“关联数组”。
// 给一个例子
Collection c = new ArrayList<Integer>(); // upcast
这里所谓的基本概念就是,总的来说,容器分成两个不同的概念,键值对的 Map是一个,剩下的是另外一个。
11.3 添加一组元素
这里书上给出了 Arrays 类 和 Collection类里面添加元素的一些方法,不止是可以简单的 add()。
这里主要是 Arryas.asList() 和 Collections.addAll() 两个静态方法。
package holding;
import java.util.*;
public class AddingGroups {
public static void main(String args[]){
// Arryas.asList(1,2) 方法,接收逗号分隔的元素列表,
// 将其转为一个 List
Collection<Integer> collection =
new ArrayList<Integer>(Arrays.asList(1,2,3,4,5));
Integer[] moreInts = {6,7,8,9,10};
// addAll() 方法
collection.addAll(Arrays.asList(moreInts));
// 注意 .addAll()是C类中的静态方法。
// 其接受一个 Collection对象以及一个列表
Collections.addAll(collection,1,2,3); // 即把列表添加到哪个对象。
// 也是静态方法
List<Integer> list = Arrays.asList(1,3,4,6);
//! list.add(1) 因为用上面方法得来底层表示的是数组,
// 所以不能改变长度了。
}
}
11.4 容器的打印
之前我们说过,对数组的打印我们用 Arrays.toString() 这个方法。但是这里的容器打印不需要任何帮助,直接输出容器对象就可以。
下面的几个小节就来看一下几个常用的容器类型。
11.5 List
List接口在Collection的基础上添加了大量方法,使得List可以在中间插入和移除元素。
两种List:
- ArrayList:基本的List,在随机访问元素时较快,但是在中间插入和移除较慢
- LinkedList:和上面相反。
书上给出了一个很长的例子,这里看一下其中用到的方法。
- contains() 方法来确定对象是否在列表中,
- remove() 方法
- indexOf() 方法 返回索引
- subList() 方法,从整个List中拿出一个片段(子List)
- toArray() 方法,将List转换成一个数组
11.6 迭代器
通常情况下,对容器的访问方式的代码,我们都需要添加具体的容器中元素的类型,如果容器换了,类型变了,我们的之前写的访问的代码也就需要重新来写。
而迭代器给我们提供了一种不需要定义具体类型的访问方式。
Java的Iterator只能单向移动,一般的用法是这样的:
1)使用方法 Iterator() 要求容器返回一个 Iterator。Iterator将准备好返回序列的第一个元素。
2)使用 next() 获得序列的下一个元素
3)使用 hasNext() 来检查序列中是否还有元素
4)使用 remove() 将迭代器新返回的元素删除
比如我们这里需要,写一个遍历访问 List的方法
public static void diaply(Iterator<Pet> it){
while(it.hasNext()){
Pet p = it.next();
System.out.println(p.id()+":"+p+" ");
}
}
11.7 LinkedList 链表
书上给了一个例子说明链表中的特有的方法:
- getFirst(),element(),peek() 三者都是获得第一个元素,当链表为空时,peek()方法返回 null,其余二者报错
- remove(),removeFirst(),poll() 三者都是移除 表头的元素并返回,链表为空,poll() 方法返回 null,其余二者报错
- addFirst(),add(),offer(),addLast() 四者都是添加元素到末尾。
- removeLast() 移除并返回列表的最后一个元素
具体还有很多方法,这里只是介绍了最常用的一些方法。可以查阅java官方的接口文档。
11.8 Stack
stack 就是我们说的“后进先出”的容器。而由于链表的特性,其可以实现栈的所有功能,所以通常可以把一个链表当做 stack 使用。
所以书上给了两个代码例子,第一个是通过 LinkedList来实现 Stack 的所有功能,另外一个就是直接用 Stack类。
11.9 Set
在上面的基础概念中我们说了,容器就分为 Collection和 Map。而这里的 Set 作为 Collection中的概念,其实际上就是 Collection,只是行为不同。
Set 的继承接口有 HashSet 和 TreeSet 。前者是无序的,而后者是可以对结果进行排序的。
11.10 Map
Map 数组可和其他的 Collection数组一样是可以扩展到多维的。通过把元素设置为 Map对象这样来做,相当于多层嵌套实现多维。(跟我们python中的 json 很像)
11.11 Queue
之前说了 Stack 是后进先出的代表,而这里的 Queue 则是先进先出的容器。就是说从屁股放进去,从脑袋取出来。LinkedList 提供了非常多的方法,所以除了我们前面说的其可以实现 Stack,这里其实也是可以实现 Queue的数据结构的。
11.13 Foreach与迭代器
这个语句,很好用,之前我们在访问数组时候,如果不需要index,foreach 语句就非常方便。
for(Integer i:nums[]){
i++;
}
这里同样的,foreach语句可以用来访问 Collection。再结合我们之前说的迭代器。
public static void diaply(Iterator<Pet> pets){
for(Pet p: pets){
System.out.println(p.id()+":"+p+" ");
}
}
总结一下,这一章就是,讲了一些接口,后面如果具体用到的话可以去查阅一下接口文档。所以这一章大概看一哈就行了。
网友评论