20162330 第三周 蓝墨云班课 泛型类-Bag 练习
目录
题目及要求
- 代码运行在命令行中,路径要体现学号信息,IDEA中,伪代码要体现个人学号信息;
- 参见Bag的UML图,用Java继承BagInterface实现泛型类Bag,并对方法进行单元测试(JUnit),测试要涵盖正常、异常情况、边界情况;
- 课上提交测试代码和测试运行的结果截图,截图要求全屏截图,包含自己的学号信息,否则无效;测试Bag类的代码中至少包含一个自定义类如Student;
- 课下完成码云上代码的上传。
- 【附】Bag的UML图:
思路分析
-
首先自定义一个Bag类,使用
public class Bag<T> implements BagInterface<T>
实现给出接口的所有方法框架,之后在里面通过泛型类型 T 自定义一个数组,通过填充方法来完善Bag类。考虑到给出的接口含有add
、remove
、isEmpty
等方法,如果使用 List 创建对象实现有些简单,所以我选择了使用 Object 类定义数组实现这些方法。
在 Bag 类的 UML类图 中,我们可以清楚地知道每个方法的返回类型、参数及需要实现的功能,大部分功能的实现比较框架化,比如:getCurrentSize()
、isEmpty
、add(T newEntry)
、remove()
等,其方法填充内容基本符合以下框架:public <返回类型> <函数名>(<参数>){ //初始化返回值参数对象 //循环(遍历、修改元素) //条件(何时修改、赋值) //返回值(参数) }
- 之所以能用 循环 + 条件 的框架是因为这些方法基本都涉及遍历环节,我也使用了几种不同的遍历方式。
需要注意的是最后一个方法toArray()
,这个方法相对陌生,我查找了API,关键句如下:
按 适当顺序 返回包含此列表中所有元素的数组;
如果指定的数组能容纳队列,并有剩余的空间,那么会将数组中紧接 collection 尾部的元素设置为 null。- 所谓的“适当顺序”,可以理解为不同与原来数组排列元素的顺序,我又继续看了UML类图中的这一方法的注释:
A new array of entries currently in the bag.
- 我的思路是将原来数组中的空值元素都填补为同一类型的相同的值,然后再重新返回这个数组,这样就可以通过一个含空值的数组调用此方法之后的元素容量进行单元测试。实现代码如下:
/* Shows all objects in a new array of food bag entries. */ public T[] toArray() { int j = food.length - 1; for (int i = 0;i<food.length;i++) { food[j] = "apple"; if (food[i] == null) { //将空值全部填补 food[i] = food[j]; j--; } } return (T[]) food; }
- 之所以能用 循环 + 条件 的框架是因为这些方法基本都涉及遍历环节,我也使用了几种不同的遍历方式。
遇到的问题和解决过程
-
【问题】在使用Junit测试
add(T newEntry)
方法时,Junit测试异常:
-
【解决方法】我仔细看了一下自己写的 add 方法:
public boolean add(T newEntry) { boolean boo = false; for (Object i : food) { if (i == null) { i = newEntry; //添加到第一个空值位置 boo = true; break; } } return boo; }
-
由于使用foreach遍历比较简单,我就没有考虑其他问题。IDEA的提示,给 i 重新赋值的那条语句中的 i 是多余的:
-
我就想不通为什么是多余的,于是又开始检查 foreach 的结构框架:
for(<元素类型> <元素变量> : <遍历对象>){ 引用元素变量的相关语句; }
-
修改了几次数组类型之后还是不对,于是我认为是foreach方法出现问题,就做了一个foreach的测试类,发现果然是foreach方法的问题,同样的修改赋值语句,使用for循环遍历就正常,使用foreach就不能进行相应赋值:
-
刚开始设断点,我并没有注意观察细节,只是跟着步骤走了一遍,又跟踪了一遍才发现给 i 赋值的语句好像并没有效果,这一句出现了问题。我又仔细地跟踪了一遍,发现add元素的地址和原来空值的地址不一样,而最后数组添加的是空值的地址:
所以总结起来就是,i 原来对应的地址就不是指向数组元素的,所以只要用其他循环(for循环)替代即可正常修改原数组的元素:
如果一定要使用foreach修改数组元素的话,那只能再另外加一层循环:
public boolean add(T newEntry) { boolean boo = false; for (Object i : food) { if (i == null) { //foreach不能修改数组元素 for (int j = 0; j < food.length; j++) { //重新使用for循环赋值 if (food[j] == i) { food[j] = newEntry; //添加到第一个空值位置 break; } } boo = true; break; } } return boo; }
-
至于我的验证是否正确,我又查找了相关资料,一种说法是:
foreach结构中的元素变量是个基本数据类型,在遍历时不指向数组元素的地址,它只代表数字它自己。
-
还有一个实例可以更好地证明我的验证:
for (Integer temp : list) { if (temp == 1) { temp = temp * 2; } }
-
根据oracle的官方文档,正式翻译应该如下:
for (Iterator i = list.iterator(); i.hasNext(); ) { float i0 = (Integer)i.next(); if(i0 == 1) i0 = i0*2; }
-
所以foreach中的 temp变量只是一个局部变量(i0),而且还是集合中元素的一个副本,并不是元素本身。这样才导致输出“修改过的数组”时,仍然输出原数组。
-
综上所述,我的验证正确,foreach只适合遍历数组,在实现涉及到修改数组元素的功能时,不宜使用,会造成赋值失败。
代码实现及托管链接
-
这里只贴出Bag类代码和运行成功截图,其余代码见相关代码托管链接:
【BagInterface类】
【Bag类】
【BagTest类】 -
Bag类如下:
/**
* A finite number of objects,not necessarily distinct,in no particular order,
* and having the same data type(collection).
*
* @author 20162330
*/
public class Bag<T> implements BagInterface<T> {
private Object food[] = new Object[5];
/*
Returns the current number of objects in the bag(except null).
*/
public int getCurrentSize() {
int foodSize = 0;
for (Object i : food) { //foreach遍历
if (i != null)
foodSize++;
}
return foodSize;
}
/*
Demonstrates if the food bag is empty(null).
*/
public boolean isEmpty() {
boolean boo = true; //默认为空
for (Object i : food) {
if (i != null) {
boo = false;
break;
}
}
return boo;
}
/*
Adds a given object to the food bag,according to whether the addition succeeds,
return true or false.
*/
public boolean add(T newEntry) {
boolean boo = false;
for (Object i : food) {
if (i == null) { //foreach不能修改数组元素
for (int j = 0; j < food.length; j++) { //重新使用for循环赋值
if (food[j] == i) {
food[j] = newEntry; //添加到第一个空值位置
break;
}
}
boo = true;
break;
}
}
return boo;
}
/*
Removes an unspecified object from the food bag,if possible.
*/
public T remove() {
Object n = null;
for (int i = 0; i < food.length; i++) {
if (food[i] != null) {
n = food[i];
food[i] = null; //移除第一个不为空的元素
break;
}
}
return (T) n;
}
/*
Removes an occurrence of a particular object from the food bag,if possible.
*/
public boolean remove(T anEntry) {
boolean boo = false;
int i = 0;
while (i < food.length) {
if (food[i] == anEntry) {
food[i] = null;
boo = true;
break;
}
i++;
}
return boo;
}
/*
Removes all objects from the food bag.
*/
public void clear() {
int i = 0;
do { //do-while方式遍历
food[i] = null;
i++;
}
while (i < food.length);
}
/*
Counts the number of times an object occurs in the food bag.
*/
public int getFrequencyOf(T anEntry) {
int t = 0;
for (Object i : food) {
if (i == anEntry)
t++;
}
return t;
}
/*
Tests whether the food bag contains a particular object.
*/
public boolean contains(T anEntry) {
boolean boo = false;
for (Object i : food) {
if (i == anEntry) {
boo = true;
break;
}
}
return boo;
}
/*
Shows all objects in a new array of food bag entries.
*/
public T[] toArray() {
int j = food.length - 1;
for (int i = 0;i<food.length;i++) {
food[j] = "apple";
if (food[i] == null) { //将空值全部填补
food[i] = food[j];
j--;
}
}
return (T[]) food;
}
}
感想
- 这次实践中解决问题确实花费了不少时间,不过我一直坚持独立思考,最终顺利解决问题,同时又练习了一下几种不同的循环(遍历)方式,Junit测试与之前相比也更全面了,也算是又体验了一回“做中学”。