照例,我们对集合型的抽象数据类型的讨论从定义他们的API开始,如表1.3.1所示。每份API都含有一个无参数的构造函数、一个向集合中添加单个元素的方法、一个测试集合
是否为空的方法和一个返回集合大小的方法。Stack和Queue都含有一个能够删除集合中的特定元素的方法。
背包 | |
Public class Bag<Item> implements Iterable<Item>
|
|
Bag() | 创建一个空背包 |
Void add (Item item) | 添加一个元素 |
Boolean isEmpty() | 背包是否为空 |
Int size() | 背包中的元素数量 |
先进先出(FIFO)队列 | |
Public class Queue<Item> implements Iterable<Item> |
|
Queue() | 创建空队列 |
Void enqueue(Item item) | 添加一个元素 |
Item dequeue() | 删除最早添加的元素 |
Boolean isEmpty() | 队列是否为空 |
Int size() | 队列中的元素数量 |
| |
下压(后进先出,LIFO)栈 | |
Public class Stack<Item> implements Iterable<Item> |
|
Stack() | 创建一个空栈 |
Void push(Item item) | 添加一个元素 |
Item pop() | 删除最近添加的元素 |
Boolean isEmpty() | 栈是否为空 |
Int size() | 栈中的元素数量 |
泛型:
例如在上面的每份API中,类名后的<Item>记号将Item定义为一个类型参数,它是一个象征性的占位符,表示的是用例将会使用的某种具体数据类型。
可以将Stack<Item>理解为某种元素的栈。
在实现Stack时,我们并不知道Item的具体类型,在创建栈时,用例会提供一种具体的数据类型:我们可以将Item替换为任意引用数据类型(Item出现的每个地方都是如此)。
这种能力正是我们所需要的。
例如,我们可以编写如下代码来用栈处理String对象:
Stack<String> stack = new Stack<String>()
stack.push(“Test”)
...
String next = stack.pop()
并在以下代码中使用队列处理Date对象
Queue<Date> queue = new Queue<Date>()
Queue.enqueue(new Date(12,31,1999))
Date next = queue.dequeue();
自动装箱与拆箱
java有一种特殊的机制来使泛型代码能够处理原始数据类型。Java的原始数据类型对应的引用数据类型分别是:
原始数据类型:
boolean,byte,char,double,float,int,long,short
对应的
引用数据类型:
Boolean,Byte,Character,Double,Float,Integer,Long,Short
java会自动在原始数据类型和引用数据类型之间进行转换。在这里,这种转换有助于我们同时使用泛型和原始数据类型
Stack<Integer> stack = new Stack<Integer>()
stack.push(17) // 自动装箱(int -> Integer)
int i = stack.pop() // 自动拆箱(Integer -> int)
定义:
自动将一个原始数据类型转换为一个封装类型被称为 “自动装箱”,自动将一个封装类型转换为一个原始数据类型被称为 “自动拆箱”。
在这个例子中:
当我们将一个原始数据类型的值17传递给push()方法时,Java将它的类型自动转换(自动装箱)为Integer。
pop()方法返回了一个Integer类型的值,Java在将它赋予变量i之前将他的类型自动转换(自动拆箱)为了int。
可迭代的集合类型
对于许多应用场景,用例的要求只是用某种方式处理集合中的每个元素,或者叫做“迭代”访问结合中的所有元素
有了它,我们能够写出清晰简洁的代码且不依赖于集合类型的具体实现
例如:假设用例再Queue中维护一个交易集合,如下:
Queue<Transaction> collection = new Queue<Transaction>();
如果集合是可迭代的,用例用一行语句即可打印出交易的列表:(因为Queue实现了Iterable方法)
for(Transaction t:collection) { StdOut.println(s)}
这种语法叫做foreach语句:可支持这种迭代需要再实现中添加额外的代码,但都是值得的
背包
背包是一种不支持从中删除元素的的集合数据类型----它的目的就是帮助用例收集元素并迭代遍历所有收集到的元素。
例如:
计算标准输入中的所有Double的值的平均值和样本标准差。
如果标准输入中有N个数组,那么平均值为他们的和除以N
样本标准差:每个值和平均值之差的平方和除以N-1之后的平方根
先进先出队列(简称队列)
队列是一种基于先进先出(FIFO)策略的集合类型。在应用程序中使用队列的主要原因是在用集合保存元素的同时保存它们的相对顺序:使他们的入列顺序和出列顺序一致
例如:
我们先将所有的整数读入队列中,然后使用Queue的size方法得到所需数组的大小,创建数组并将队列中的所有整数移动到数组中。
将数据移动到数组中,使用队列,之所以合适,是因为它能够将整数按照文件中的顺序放入到数组中(如果该顺序并不重要,也可以使用Bag对象)
下压栈(简称栈)
下压栈是一种基于后进先出(LIFO)的策略
例子:
实际生活中:将你的邮件在桌子上放成一叠时,使用的就是栈,因为每次取得的都是最后的那一封
计算机中: 许多人仍然使用栈的方式存放电子邮件---在收信时将邮件压入(push)最顶端,在取信时从最顶端将他们弹出(pop),且第一封一定是最新的邮件(因为栈的策略是后进先出)
这种策略的好处是:我们能够即时的看到感兴趣的邮件
坏处是:如果不把栈清空,某些较早的邮件可能永远都不会阅读
浏览器:点击一个超链接,浏览器会显示一个新的页面(并将它压入一个栈)。你可以不断的点击超链接访问新页面,但总是可以通过“回退”按钮重新访问以前的页面(从栈中弹出)
注意:当用例使用foreach语句迭代遍历栈中的元素时,元素的处理顺序和他们被压入的顺序正好相反