积极主动敲代码,使用JUnit学习Java
早起看到周筠老师在知乎的回答软件专业成绩很好但是实际能力很差怎么办?,很有感触。
从读大学算起,我敲过不下100本程序设计图书的代码,我的学习经验带来我的程序设计教学方法是:程序设计入门,最有效的方法要积极主动敲代码。这也就是为什么我要求同学们把教材上的代码动手敲一遍的原因。
引用一下徐宥的例子:
记得《The TeXbook》上有一个程序,Knuth让大家自己照着敲入计算机,然后还很幽默地说,实验证明,只有很少的人会按照他说的敲入这个程序,而这部分人,却是学TeX学得最好的人。看到这里我会心一笑,觉得自己的方法原来也不算笨。从此,一字不漏敲入一本书的程序成了我推荐别人学习语言的最好办法。
针对《Java程序设计》课程,我可以改写一下,实验证明:只有很少的同学会按照我说的敲入教材上的程序,而这部分人,却是学Java学得最好的人。我知道那些所谓“把书上代码都敲了,但还是搞不懂”的同学是怎么回事,所以我验收代码的第一个问题是“代码是自己敲的吗?”
好吧,我承认个别同学真的敲了代码还不明白怎么回事,问题出在敲代码没有积极主动上,为了让这部分同学掌握学习方法,我给出一个敲代码的例子。
下面有个StringBuffer的例子,代码如下:
1 public class StringBufferDemo{
2 public static void main(String [] args){
3 StringBuffer buffer = new StringBuffer();
4 buffer.append('S');
5 buffer.append("tringBuffer");
6 System.out.println(buffer.charAt(1));
7 System.out.println(buffer.capacity();
8 System.out.println(buffer.indexOf("tring"));
9 System.out.println("buffer = " + buffer.toString());
10 }
11}
作为初学者的你们,在敲代码的时候肯定会遇到很多问题:大小写,丢字母,中英文字符,文件命名等,能让这个程序顺利编译就能学到不少知识。顺便说一下,上面的代码直接拷贝是不能够用javac StringBufferDemo.java
直接编译成功的,你可以试一下。
程序编译通过后,如果直接用java StringBufferDemo
运行一下程序,看一下运行结果,这样学不到太多。一种较好的做法是自己先理解代码,推导一下可能的结果,如果运行结果和自己想象的一样,基本就理解了代码,否则就有知识盲点,要深入学习了。
上面的代码,第3行构建了一个空StringBuffer的对象buffer,第4、5行调用两次append后,buffer的内容应该是“StringBuffer”, Java中的函数看一下名字就差不多猜出来它的功能了,我们猜一下后面的代码的结果:
第6行:charAt(1)应该是返回字符串中的第1个字符,很可能是‘S’或‘t’,考虑到程序设计中数组等编号都是从0开始,最有可能是‘t’
第7行:capacity大概是返回字符串的容量,字符串“StringBuffer”的长度是12,猜一下,12?
第8行:indexOf是子串匹配,与第6行问题差不多,可能是1,2,最可能是1
代码编译、运行的结果是:
t
16
1
buffer = StringBuffer
看来除了第7行,其他都猜对了。这时候是该看看StringBuffer
的帮助文档了。Java8 API官方文档不大好用,在Windows下的话,推荐大家下载CHM格式的Java API,这个版本的API具个检索功能,用好了,很快可以掌握举一反三的自学能力,当然要有一点点的英语基础了。补全见下图:
比如我们想对几个数进行排序,你如果知道「排序」的英文单词是「sort」,那么你可以找到一堆可以解决你问题的方法来,是不是有了在搜索引擎中使用关键词进行搜索的感觉?如下图。
我们看看StringBuffer
的capacity
的帮助文档:
看不懂?去扇贝背单词查一下不认识的单词的意思吧,然后把单词加入自己的记忆列表吧。计算机的英语都比较简单,英语不懂,翻译成汉语也还是不懂,因为缺少的是专业知识,而不仅仅是语言。老版本的Java API有汉化版的,下面的能看懂吗?
好象没有什么帮助。我们需要通过修改代码来理解这个方法了。把上面代码中的第4、5、6、8行屏蔽掉,让buffer
是个空串看看是什么结果,结果如下:
16
buffer=
哦,明白了,capacity返回的不是字符串的长度,而是目前的最大容量。那要获得字符串的长度用哪个方法?查找一下StringBuffer
的Method Summary,好象应该是length(),如下图:
我们增加一行代码调用一下length():
1 public class StringBufferDemo{
2 public static void main(String [] args){
3 StringBuffer buffer = new StringBuffer();
4 buffer.append('S');
5 buffer.append("tringBuffer");
6// System.out.println(buffer.charAt(1));
7 System.out.println(buffer.capacity();
8// System.out.println(buffer.indexOf("tring"));
9 System.out.println("buffer = " + buffer.toString());
10 System.out.println(buffer.length());
11 }
12}
程序运行结果如下:
16
buffer = StringBuffer
12
这下明白了capacity()和length()的关系,前者是最大容量,默认是16,length返回当前长度。如果学过《数据结构》相关的课程并用到过extern void *realloc(void *mem_address, unsigned int newsize);
就更容易理解了。如果字符串超过16个字符呢?我们修改代码如下:
1 public class StringBufferDemo{
2 public static void main(String [] args){
3 StringBuffer buffer = new StringBuffer();
4 buffer.append('S');
5 buffer.append("tringBuffer");
6// System.out.println(buffer.charAt(1));
7 System.out.println(buffer.capacity();
8// System.out.println(buffer.indexOf("tring12345"));
9 System.out.println("buffer = " + buffer.toString());
10 System.out.println(buffer.length());
11 }
12}
程序运行结果如下:
34
buffer = StringBuffer
17
哦,超过16个字符会再分配18个字符的空间。那超过34个字符呢?你猜猜capacity()的返回值(52?54?56?还是其他值)并修改上面的代码验证一下。
其实学习Java中的任何一个新类时,首先要学习一下其构造方法,我们看一下StringBuffer
的构造方法,如下图:
汉化版如下:
原来我们可以构造StringBuffer
时指定其capacity的,如果不指定默认值是16,我们修改代码如下:
1 public class StringBufferDemo{
2 public static void main(String [] args){
3 StringBuffer buffer = new StringBuffer(20);
4 buffer.append('S');
5 buffer.append("tringBuffer");
6// System.out.println(buffer.charAt(1));
7 System.out.println(buffer.capacity();
8// System.out.println(buffer.indexOf("tring12345"));
9 System.out.println("buffer = " + buffer.toString());
10 System.out.println(buffer.length());
11 }
12}
程序运行结果如下:
20
buffer = StringBuffer
12
这时对capacity的理解是不是更深入了?
当然,如果能想到StringBuilder
和String
有什么不同,到网上找一下类「《全面解释java中StringBuilder、StringBuffer、String类之间的关系》」这类的文章看看就更积极主动了。
对于Java API中的类、方法,我们学习时写个测试类很利于我们理解类和方法的功能。我们在实验二 Java面向对象程序设计中学习了单元测试和JUnit,使用JUnit改写教材上的例子是积极主动敲代码的好途径。
比如下面这个代码,equals方法的代码要好好理解。equals实现了等价关系,满足:
- 自反性 (reflexive):对于任何一个非null的引用值x,x.equals(x)为true。
- 对称性 (symmetric):对于任何一个非null的引用值x和y,x.equals(y)为true时y.equals(x)为true。
- 传递性 (transitive):对于任何一个非null的引用值x、y和z,当x.equals(y)为true 且 y.equals(z)为true 则 x.equals(z)为true。
- 一致性 (consistent):对于任何一个非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)的结果依然一致。
- 另外还要满足:对于任何非null的引用值x,x.equals(null)必须返回false。
public class Score {
private int No; //学号
private int score; //成绩
public ScoreClass() { //无参数的构造方法
No = 1000;
score = 0;
}
public ScoreClass(int n, int s) { //有两个参数的构造方法
No = n;
score = s;
}
public void setInfo(int n, int s) { //设置成绩
No = n;
score = s;
}
public int getNo() {
return No; //获取学号
}
public int getScore() {
return score; //获取成绩
}
@Override
public String toString() {
return No + "\t" + score;
}
@Override
public boolean equals(Object obj) {
if (this == obj) { // 是否引用同一个对象
return true;
}
if (obj == null) { // 是否为空
return false;
}
if (getClass() != obj.getClass()) { // 是否属于同一个类型
return false;
}
ScoreClass other = (ScoreClass) obj;
if (other.No == No && other.score == score) {
return true;
} else {
return false;
}
}
}
参考极简单元测试示例(以除法为例),在你熟悉的Java IDE中(Intellij IDEA、Eclipse或NetBeans)中利用JUnit写一些测试代码,要求能测到equals中的每一个return,动手试试吧!
当然,积极主动是一种学习态度,看看学姐在我另一门课如何积极主动学习的「对atime、mtime和ctime的研究」,我说多思考了吗?深度思考对一个人的成长太重要了。
进一步参考
参考资料
欢迎关注“rocedu”微信公众号(手机上长按二维码)
做中教,做中学,实践*同进步!
版权声明:*转载-非商用-非衍生-保持署名| Creative Commons BY-NC-ND 3.0