積極主動敲代碼,使用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