1、问题背景
在完成UC Berkeley的CS61B课程HW1的过程中,要求我们完成一个电子合成器的包,用来模拟吉他乐声,其中的一步是构建一个ArrayRingBuffer的数据结构,用其来实现Karplus-Strong算法,这个数据结构继承了一个名为AbstractBoundedQueue的抽象类,而这个抽象类实现了一个名为BoundedQueue的接口类,这个数据结构有基本的环形数组的特征,同时包含了生成迭代器,我首次实现的代码如下
import java.util.Iterator;
import java.util.NoSuchElementException;
// BoundedQueue (Interface)
public interface BoundedQueue<T> extends Iterable<T> {
int capacity(); // return size of the buffer
int fillCount(); // return number of items currently in the buffer
void enqueue(T x); // add item x to the end
T dequeue(); // delete and return item from the front
T peek(); // return (but do not delete) item from the front
/** Is the buffer empty (fillCount equals zero)? */
default boolean isEmpty() {
return fillCount() == 0;
}
/** Is the buffer full (fillCount is same as capacity)? */
default boolean isFull() {
return capacity() == fillCount();
}
}
// AbstractBoundedQueue (Abstract class)
public abstract class AbstractBoundedQueue<T> implements BoundedQueue<T> {
protected int fillCount;
protected int capacity;
@Override
public int capacity() {
return capacity;
}
@Override
public int fillCount() {
return fillCount;
}
}
// ArrayRingBuffer
public class ArrayRingBuffer<T> extends AbstractBoundedQueue<T> {
/* Index for the next dequeue or peek. */
private int first; // index for the next dequeue or peek
/* Index for the next enqueue. */
private int last;
/* Array for storing the buffer data. */
private T[] rb;
/**
* Create a new ArrayRingBuffer with the given capacity.
*/
public ArrayRingBuffer(int capacity) {
this.capacity = capacity;
this.fillCount = 0;
first = 0;
last = 0;
rb = (T[]) new Objects[capacity];
}
private int addOne(int index) {
return (index + 1) % capacity;
}
/**
* Adds x to the end of the ring buffer. If there is no room, then
* throw new RuntimeException("Ring buffer overflow"). Exceptions
* covered Monday.
*/
@Override
public void enqueue(T x) {
if (isFull()) {
throw new RuntimeException("Ring buffer overflow");
}
rb[last] = x;
last = addOne(last);
this.fillCount++;
}
/**
* Dequeue the oldest item in the ring buffer. If the buffer is empty, then
* throw new RuntimeException("Ring buffer underflow"). Exceptions
* covered Monday.
*/
@Override
public T dequeue() {
if (isEmpty()) {
throw new RuntimeException("Ring buffer underflow");
}
T res = rb[first];
fillCount--;
first = addOne(first);
return res;
}
/**
* Return oldest item, but don't remove it.
*/
public T peek() {
if (fillCount == 0) {
throw new RuntimeException("The ArrayRingBuffer is EMPTY!");
}
return rb[first];
}
private class BufferIterator implements Iterator<T> {
private int index;
private int curNum;
public BufferIterator() {
index = first;
curNum = 0;
}
@Override
public boolean hasNext() {
return curNum < fillCount;
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
T nextValue = rb[index];
curNum++;
index = addOne(index);
return nextValue;
}
}
@Override
public Iterator<T> iterator() {
return new BufferIterator();
}
}
2、问题发现
IDEA未提示错误,可以正常编译,但是始终无法通过单元测试,报错信息为java.lang.ArrayStoreException: java.lang.Double,是Java中的一个运行时异常,表示尝试将一个对象存储到数组中,但对象的类型与数组的元素类型不兼容。我开始以为是我传入的数据类型有问题,但是上下检查没有问题,很清楚的是double类型,在泛型的声明也没有混淆Double和double,经过debug检查,最后将问题锁定在了ArrayRingBuffer的构造函数中,如下所示
public ArrayRingBuffer(int capacity) {
this.capacity = capacity;
this.fillCount = 0;
first = 0;
last = 0;
rb = (T[]) new Objects[capacity];
}
3、问题解决
因为类似的数据结构我此前写过,所以写的比较得心应手,也使用了很多的自动补全,没有仔细检查,看了很久才发现在给rb变量绑定数组对象的时候,new的是Objects[]而不是Ojbect[],我很惊讶代码自动补全的结果是这样,在修改之后发现可以通过测试了,问题解决
4、回顾
通过查阅资料,我发现Java中存在Object和Objects两个类,其中Object的意思耳熟能详不用多说,Objects的全名是java.util.Objects,它提供了静态方法来操作对象,例如检查是否为 null、比较对象等,一般不用于构建数组,因为它们限制了数组元素的类型。
5、完整代码
下面附上经过修改的完整代码
import java.util.Iterator;
import java.util.NoSuchElementException;
// BoundedQueue (Interface)
public interface BoundedQueue<T> extends Iterable<T> {
int capacity(); // return size of the buffer
int fillCount(); // return number of items currently in the buffer
void enqueue(T x); // add item x to the end
T dequeue(); // delete and return item from the front
T peek(); // return (but do not delete) item from the front
/** Is the buffer empty (fillCount equals zero)? */
default boolean isEmpty() {
return fillCount() == 0;
}
/** Is the buffer full (fillCount is same as capacity)? */
default boolean isFull() {
return capacity() == fillCount();
}
}
// AbstractBoundedQueue (Abstract class)
public abstract class AbstractBoundedQueue<T> implements BoundedQueue<T> {
protected int fillCount;
protected int capacity;
@Override
public int capacity() {
return capacity;
}
@Override
public int fillCount() {
return fillCount;
}
}
// ArrayRingBuffer
public class ArrayRingBuffer<T> extends AbstractBoundedQueue<T> {
/* Index for the next dequeue or peek. */
private int first; // index for the next dequeue or peek
/* Index for the next enqueue. */
private int last;
/* Array for storing the buffer data. */
private T[] rb;
/**
* Create a new ArrayRingBuffer with the given capacity.
*/
public ArrayRingBuffer(int capacity) {
this.capacity = capacity;
this.fillCount = 0;
first = 0;
last = 0;
rb = (T[]) new Object[capacity];
}
private int addOne(int index) {
return (index + 1) % capacity;
}
/**
* Adds x to the end of the ring buffer. If there is no room, then
* throw new RuntimeException("Ring buffer overflow"). Exceptions
* covered Monday.
*/
@Override
public void enqueue(T x) {
if (isFull()) {
throw new RuntimeException("Ring buffer overflow");
}
rb[last] = x;
last = addOne(last);
this.fillCount++;
}
/**
* Dequeue the oldest item in the ring buffer. If the buffer is empty, then
* throw new RuntimeException("Ring buffer underflow"). Exceptions
* covered Monday.
*/
@Override
public T dequeue() {
if (isEmpty()) {
throw new RuntimeException("Ring buffer underflow");
}
T res = rb[first];
fillCount--;
first = addOne(first);
return res;
}
/**
* Return oldest item, but don't remove it.
*/
public T peek() {
if (fillCount == 0) {
throw new RuntimeException("The ArrayRingBuffer is EMPTY!");
}
return rb[first];
}
private class BufferIterator implements Iterator<T> {
private int index;
private int curNum;
public BufferIterator() {
index = first;
curNum = 0;
}
@Override
public boolean hasNext() {
return curNum < fillCount;
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
T nextValue = rb[index];
curNum++;
index = addOne(index);
return nextValue;
}
}
@Override
public Iterator<T> iterator() {
return new BufferIterator();
}
}