986198@第一次JAVA实验收获
JAVA的初体验
本次实验使用的是java语言编写,对我而言java是一门全新的语言,包,类,方法,对象……与c大相径庭,一切都是如此的陌生。通过第一次完成第一次实验的几个问题,对java也渐渐变得熟悉起来,体验到了java的便利(内置各种类和方法等)以及Ecipse IDE的优秀之处(错误提示、快捷修改、Junit等)。通过本次实验,让我们快速地熟悉了java语言,甚至学习了使用以及编写Test文件,对以后的课程实验以及工作都有很大帮助。
遇到的难题及解决方法
异常的产生以及处理
在c语言中,判断打开一个文件指针是否成功可以通过指针是否为空判断
FILE pointer=fopen("D:\\test.txt“,"r"));
if (pointer == NULL)
printf("failed to open the txt\n");
在java中无法这么做,失败会抛出异常。此外,Lab1 P1中有将String类型转化为int类型的操作,此操作在转化失败时也会抛出异常。为了使代码正常运行,我们必须对抛出的异常进行检测并作出正确的处理。于是我们引入try - catch 语句。
try {
逻辑代码块1;
} catch(ExceptionType e) {
处理代码块1;
}
具体示例如下
//判断行列数是否相等
int columns=0;//列数
String[] column_array=line_array[0].split("\t");
columns = column_array.length;
System.out.println("测试columns"+columns);//测试用,查看是否正确
if (columns != rows) {//行列数不相等则不是方阵,返回false。
flag = false;
return flag;
}
//将数据拆分成二维数组并转化位int
for (int i=0; i < rows; i++)
{
magic_str_array[i] = line_array[i].split("\t");//按制表符把数据单个拆开
for(int j=0; j < rows; j++)
{
try {
MagicSquare[i][j]=Integer.valueOf(magic_str_array[i][j]);//将string转化成int
}catch(NumberFormatException e) {//失败则抛出异常NumberFormatException
e.printStackTrace();
flag = false;
return flag;//抓取到NumberFormatException,则有非int元素,判断不是magic square,返回false
}
}
}
输出重定向问题
Lab1 P1中要求我们将生成的MagicSquare输入到文件中并保存。对于该要求我采取的实现方法是将输出流重定向到指定文件
PrintStream ps= new PrintStream("src/p1/txt/6.txt");
System.setOut(ps);
//实现你的具体操作
ps.close();
以上操作就可以实现文件输出的要求。但问题出现在文件输出完成以后。当我需要使用控制台输出时,我无论如何也无法打印到我的屏幕。
当我查找多方资料后发现这是因为我没有备份原本的输出流,所以当我关闭我原本的文件输出流后,我也无法回到原本的输出流。解决方法如下
PrintStream ps= new PrintStream("src/p1/txt/6.txt");
PrintStream out = System.out;//备份输出流
System.setOut(ps);
//方法的具体实现
ps.close();//关闭文件输出
System.setOut(out);//切换输出流
谁修改了我的数据?
在Lab1 P3中,我们需要使用广度优先搜索来判断两个人之间的距离。代码实现似乎没有问题
@Test
public void getDistance( ) {
FriendshipGraph graph = new FriendshipGraph();
Person rachel = new Person("Rachel");
Person ross = new Person("Ross");
Person ben = new Person("Ben");
Person kramer = new Person("Kramer");
graph.addVertex(rachel);
graph.addVertex(ross);
graph.addVertex(ben);
graph.addVertex(kramer);
graph.addEdge(rachel, ross);
graph.addEdge(ross, rachel);
graph.addEdge(ross, ben);
graph.addEdge(ben, ross);
assertEquals(1, graph.getDistance(rachel, ross));
assertEquals(2, graph.getDistance(rachel, ben));
assertEquals(0, graph.getDistance(rachel, rachel));
assertEquals(-1, graph.getDistance(rachel, kramer));
}
上述测试文件成功通过,当我们更进一步添加几行代码进行注释时
graph.addEdge(rachel, kramer);
graph.addEdge(kramer, rachel);
assertEquals(3, graph.getDistance(kramer, ben));
Person hugh = new Person("hugh");
graph.addVertex(hugh);
assertEquals(-1, graph.getDistance(hugh, ben));
发现后续的测试结果为1的和0的能正确通过,其他的都会返回-1.这是为什么呢?
经过长时间调试,发现问题在于源数据被改动了。一开始为了便于广度优先搜索的进行,我在Person这个类里添加了一个boolean 属性
boolean visit = false;//记录遍历情况
初始置为false,表示未被遍历,一旦被遍历则置为true,防止重复遍历
我们会发现问题出现在当函数运行一次后,被遍历过的点的visit属性都被置为true了。以至于再次进入函数就会出现错误结果。然而原代码中
Person pointer = bsf_queue.get(0);
每次循环前把队列顶端的点赋值给pointer防止源数据更改,那为什么我的数据仍然被改动了呢?
for(int j = 0; j <pointer.friend.size(); j++) {
if(!pointer.friend.get(j).visit) {
bsf_queue.add(pointer.friend.get(j));
pointer.friend.get(j).visit = true;
count++;
}
仔细检查代码才发现,我们确实保证了pointer这个当前对象的源数据没有被更改,但是在后续代码中我们对pointer.friend这个ArrayList里所保存的Person进行了遍历并更改了他们的visit属性,原来是我们自己在不知不觉间修改了源数据引起错误!
既然找到问题所在那就要解决问题,已知代码运行错误的原因是源数据在运行时visit属性被更改,那只要在每次进入函数时重新置为false就能保证函数运行正确,那么这样改真的好吗?
答案显然是否定的。第一,这样会大大增加代码的时间复杂度,带来不必要的开销;第二,广度优先搜索函数的功能应当是搜索,不应当改变源数据的内容,贪图方便添加的visit本身就是不合理的,这个属性是没必要的,会增加空间复杂度。
所以认真思考以后,我决定优化代码,将Person的visit属性删去。改为在广度优先搜索中创建一个ArrayList visit来记录已遍历过的点,通过visit.contains函数判断是否遍历过该点,实现如下
ArrayList<Person> bsf_queue = new ArrayList<Person>();
bsf_queue.add(person_1);
ArrayList<Person> visit = new ArrayList<Person>();
visit .add(person_1);//已遍历的点录入,防止重复遍历
ArrayList<Integer> counter = new ArrayList<Integer>();
for(int i = 1; !bsf_queue.isEmpty(); i++) {
Person pointer = bsf_queue.get(0);
int count = 0;
if (pointer.friend.contains(person_2))
return path;
for(int j = 0; j <pointer.friend.size(); j++) {
if(!visit.contains(pointer.friend.get(j))) {
bsf_queue.add(pointer.friend.get(j));
visit.add(pointer.friend.get(j));
count++;
}
}
counter.add(count);
if(counter.contains(i)) {
path++;
i=0;
}
bsf_queue.remove(0);
}
return -1;
感想
本次的实验给了我很深刻的认识
1.java程序完成必须通过覆盖度足够的Test文件的测试,优秀的Test文件能让你代码中的问题暴露出来,否则即使是运行通过也可能存在后续问题(如上文中广度优先搜索隐藏的问题)。一个差的Test文件对我们是负帮助。
2.编写java时要有良好的代码风格,这样有助于我们编写良好的代码以及发现代码中的错误,尤其是以后工作中尤为重要!