创建自己的数据结构,实现双端队列deque(Double Ended Queue, 发音“deck”)。支持双端插入和移除的线性集合,支持容量限制的活没有固定大小的Deque。
A:linked nodes
通过链式节点实现Deque。
Creating the File
1 main函数中newLinkdListDeque中<>下有红色波浪线
Deque<Integer> lld = new LinkedListDeque<>();
解决,在类中声明
public class LinkedListDeque<T>;
2 main函数中new的这一行都被标记,按快捷键 Option+Enter(Mac). 选择“Make `LinkedListDeque’ implement Deque’并确认,会看到一系列方法,都需要填充进去
3 假设LinkedListDeque总是表示整数的Deque有点问题
public class LinkedListDeque<T> implements Deque<Integer>
解决:右击Integer,以此点击Refactor重构>Type Migraction类型迁移>将Java.lang.Integer替换为T>Refactor,然后所有错误的Integer都会被T替换
5 创建一个空的结构函数
Writing and Verifying the Constructor
在构造函数中写关于空列表的代码,这需要创建一个Node类,并引入一个或多个实例变量,构造函数时没有参数的。
节点nodes是双链表的,并具有双链表所需的字段,另外,只有一个node类,且node类需是哪部的,或LinkedLiseDeque的嵌套类。
问题:嵌套类是嵌套在类中,构造函数之外?还是也可以嵌套在构造函数内?–嵌套类及可以放置构造函数之外,也可以放在构造函数之内。
问题:构造函数是否需要写在类中的第一个,嵌套类可紧随其后?–都可以。
创建空列表时,自定义类型节点中的类型T可用null做初始值
addFirst and addLast
做添加操作时无论deque大小时间恒定,意味着操作不能使用遍历循环deque中的大量元素。
修改表链须注意顺序,前面步骤做的操作不能改变后面代码所需使用的内容。
Truth Assertions
Google’s Truth assertions library
@Test
tells Java that this is method is a test, and should be run when we run tests. 告诉Java这个方法是一个测试,尽在运行测试时执行
assertThat(actual).isEqualTo(expected);
assertWithMessage("actual is not expected").that(actual).isEqualTo(expected); // add a message to the assertion
assertThat(actualList).containsExactly(0, 1, 2, 3).inOrder();
assertThat(ReferenceObject).containsExactlyElementsIn(expected).inOrder(); // `expected`is a object
isNull
and isNotNull
isTrue
and isFalse
for boolean
s
getRecursive
getRecurive(index)递归创建获取值的方法,特殊用例getRecursive(0)=sentinel.next.next
getRecurive(index) 和 getRecurive(index - 1)不同索引对应的值之间有什么联系?没有联系
通过改变索引,现在索引i-1对应的值对应原有的索引i对应的item值,使得不同索引获得的对应值相同。
由于get方法是从sentinel下一个节点由0开始计数,所以只要sentinel指向下一个节点,移动后i-1位的item值即移动前i位的值。
getRecurive(index)中只有一个索引参数,如何传递引用元素节点,新建一个private递归方法,原有方法中返回这个private方法!
public T getRecursive(int index) {
return getRecursive(index, sentinel);
}
private T getRecursive(int index, Node node) { //private访问控制 仅对同一类可见
if(node.next == null) {
return null;
}
else {
return getRecursive(index - 1, node.next);
}
removeFirst and removeLast
当Deque为空时,remove应返回null。remove相当于add方法逆向编码
B:backing array
通过回退阵列实现Deque
Constucor
回顾Array
// Array creation
x = new int[3]; //填充int默认值0 (String[3]内存盒中默认值null)
y = new int[]{1, 2, 3, 4, 5};
int[] z = {9, 10, 11, 12, 13}; // 结合变量声明盒创建z,省略new
回顾AList
Glorp[] items = (Glorp[]) new Object[8];
public class AList<Item> {
private Item[] = items;
private int size;
/** Creates an empty list. */
public AList() {
items = (Item[]) new Object[100]; //java创建泛型的语法
size = 0;
}
/** Resizes the underlying array to the target size. */
private void resize(int capacity) {
Item[] a =(Item[]) new Object[capacity];
System.arraycopy(items, 0, a, 0, size);
items = a;
}
...
回顾LinkedListDeque
public class LinkedListDeque<T> implements Deque<T> {
public class Node { // 构造嵌套类表示节点
public Node prev;
public T item;
public Node next;
public Node(Node m, T i, Node n){
prev = m;
item = i;
next = n;
}
}
/* The first item (if it exists) is at sentinel.next. */
private Node sentinel;
private int size;
/* Creates an empty LinkedListDeque. */
public LinkedListDeque() {
// sentinel = new Node(sentinel, null, sentinel);
sentinel = new Node(null, null, null);
size = 0;
}
public static void main(String[] args) {
Deque<Integer> lld = new LinkedListDeque<>();
// lld.addFirst(10);
lld.addLast(100);
}
...
array实现循环
Resizing Up
不推荐在循环实现中使用arraycopy
思考如何实现的get方法和如何使用loop。
是否要新建一个int参数,以区分deque和array的大小
C:Deque Enhancements
Objects Methods
iterator()
Deque<String> lld1 = new LinkedListDeque<>();
lld1.addLast("front");
lld1.addLast("middle");
lld1.addLast("back");
for (String s : lld1.toList()) {
System.out.println(s);
}
assertThat(lld1.toList()).containsExactly("front", "middle", "back");
注:以上相比官网增加了toList()
Deque接口的一个缺点是它不支持被遍历,使用循环或Truth库断言测试时会变异报错“foreach not applicable to type”、“Cannot resolve method containsExactly in Subject”。
为了解决这个问题,我们要将Deque接口扩展继承Iterable接口。
public interface Deque<T> extends Iterable<T> {
Lecture12:如何实现iterator()方法
Set<String> s = new HashSet<>();
...
for (String city : s) {
...
}
// 等价于
public Iterator<E> iterator();
Set<String> s = new HashSet<>();
...
Iterator<String> seer = s.iterator();
while (seer.hasNext()) {
String city = seer.next();
...
}
完整如下:
思考:1 调用接口的迭代器方法返回迭代器对象,以实现迭代循环遍历功能;2 为类创建一个迭代器方法,该方法支持循环遍历功能;3 设计继承关系时,类须正式声明继承支持功能
import java.util.Iterator;
public class ArraySet<T> implements Iterable<T> { // 3 声明类支持循环
private T[] items;
private int size; // the next item to be added will be at position size
public ArraySet() {
items = (T[]) new Object[100];
size = 0;
}
/* Returns true if this map contains a mapping for the specified key.
*/
public boolean contains(T x) {
for (int i = 0; i < size; i += 1) {
if (items[i].equals(x)) {
return true;
}
}
return false;
}
/* Associates the specified value with the specified key in this map.
Throws an IllegalArgumentException if the key is null. */
public void add(T x) {
if (x == null) {
throw new IllegalArgumentException("can't add null");
}
if (contains(x)) {
return;
}
items[size] = x;
size += 1;
}
/* Returns the number of key-value mappings in this map. */
public int size() {
return size;
}
/** returns an iterator (a.k.a. seer) into ME */
public Iterator<T> iterator() {
return new ArraySetIterator();
} //2.1 在类中创建一个返回迭代器的迭代器方法
private class ArraySetIterator implements Iterator<T> { //2.2 嵌套私类实现迭代的循环遍历方法
private int wizPos;
public ArraySetIterator() {
wizPos = 0;
}
public boolean hasNext() {
return wizPos < size;
}
public T next() {
T returnItem = items[wizPos];
wizPos += 1;
return returnItem;
}
}
public static void main(String[] args) {
ArraySet<Integer> aset = new ArraySet<>();
aset.add(5);
aset.add(23);
aset.add(42);
//iteration-简化
//for (int i : aset) {
// System.out.println(i);
//}
//iteration-等价
Iterator<Integer> seer = javaset.iterator(); //1.1 调用接口的迭代器方法创建一个迭代器对象,编译检查Set是否具备iterator()方法
while (seer.hasNext()) {
System.out.println(seer.next());
} // 1.2 通过迭代器循环遍历,编译检查Iterator接口是否具备next/hashNext()方法
}
}
即:1 在类中添加返回Iterator迭代器的iterator()方法; 2 迭代器Iterator应具有next/hasNext()方法; 3 在类定义行添加声明实现Iterable
equals()
public boolean equals(Object obj) {
return (this == obj);
}
内容相同,但引用不同
提示:non-equal instance of same class with same string representation
instanceof
检查对象是否为某类的实例,包含了该对象为null的情况也可以正确处理
public boolean equals(Obiect o) {
if (o == null) {false}
return this.size == uddaDog.size;
}
//等价于
public boolean equals(Object o) {
if (o instanceof Dog uddaDog) { // 1 检查o的动态类型是否为Dog; 2 隐式地将作为Dog的o转变味名为uddaDog的变量
return this.size == uddaDog.size;
}
return false;
}
toString()
@Override
public String toString() {
Strng returnString = "{";
for (int i = 0; i < size - 1; i += 1) {
returnString += items[i].toString;
returnString += ", ";
}
returnString += items[size - 1].toString;
returnString += "}";
return returnString;
}
// 以上很慢,即使向字符串添加一个字符也会生成一个全新的字符串(不可变类,速度和简单性的平衡),改写成使用字符串构建器
public String toString() {
StrngBuilder stringSB = new StringBuilder("{");
for (int i = 0; i < size - 1; i += 1) {
returnSB.append(items[i].toString);
returnSB.append(", ");
}
returnSB.append(items[size - 1]);
returnSB.append("}");
return returnSB;
}
Guitar Hero
项目使用自制的ArrayDeque时,也在修正ArrayDeque。
若遇到Bug无法清晰定位时,可以通过日志打印(如打印循环中的i)来定位。
运行GuitarHeroLite,提示“TSM AdjustCapsLockLEDForKeyTransitionHandling - _ISSetPhysicalKeyboardCapsLockLED Inhibit“,意为键入大写C即可模拟划拨吉他dou弦发音。