諶衛(wèi)軍
(清華大學(xué) 計算機(jī)科學(xué)與技術(shù)系,北京 100084)
眾所周知,Java既是一種編程語言,又是一個跨系統(tǒng)的運行平臺,在軟件工業(yè)界得到廣泛的應(yīng)用,成為眾多程序員的首選編程語言。事實上,在軟件工業(yè)歷屆的程序設(shè)計語言排名榜中,Java語言始終名列前茅,并在大部分時間內(nèi)排名第一,尤其是隨著移動開發(fā)和安卓手機(jī)的爆炸式增長,Java語言又出現(xiàn)了新的增長點,因為安卓系統(tǒng)上的應(yīng)用軟件都是用Java語言開發(fā)的,因此Java語言的普及和推廣具有廣泛的市場需求。由于Java是一種面向?qū)ο蟮木幊陶Z言,很多內(nèi)容具有一定的難度,如抽象與封裝、函數(shù)重載與重寫、數(shù)據(jù)的存儲、繼承、多態(tài)、動態(tài)綁定、抽象類與接口、對象集合等,特點是單調(diào)枯燥、晦澀難懂,而且有的還與其他課程如操作系統(tǒng)的知識相融合,因此學(xué)生在學(xué)習(xí)時可能會面臨一些困難。
筆者在清華大學(xué)開設(shè)了一門全校性選修課“Java語言程序設(shè)計”,主要面向全校對Java 編程比較感興趣的本科生,以工科院系為主,既有計算機(jī)、軟件、電子、自動化等信息相關(guān)專業(yè)的學(xué)生,又有工業(yè)工程、物理、經(jīng)管、精儀、機(jī)械、化學(xué)工程等非信息專業(yè)的學(xué)生。經(jīng)過幾年的努力,這門課程逐漸受到學(xué)生的歡迎。雖然春季和秋季這兩個學(xué)期都有開課,但是仍然供不應(yīng)求,學(xué)生的選課熱情很高。因此,在以往教學(xué)經(jīng)驗的基礎(chǔ)上[1-2],如何講好這樣一門既有需求、又有一定難度的課程,需要認(rèn)真地思考、總結(jié)和實踐。
從教學(xué)目標(biāo)來看,這門課程的教學(xué)目標(biāo)主要有3個。
(1)編程語言的學(xué)習(xí),即把Java看成一種編程語言,學(xué)習(xí)這種語言的使用方法。
(2)編程思想的學(xué)習(xí),即把Java看成面向?qū)ο缶幊陶Z言的一個具體實例,通過它學(xué)習(xí)面向?qū)ο蟮木幊趟枷?包括編碼、設(shè)計與分析。
(3)實踐能力的培養(yǎng),即把Java看成一種實用的軟件開發(fā)工具,利用它解決實際的問題,編寫出一些實用的應(yīng)用程序。
為了實現(xiàn)這些目標(biāo),在教學(xué)理念上應(yīng)遵守如下原則。
首先,堅持“以學(xué)生為中心”的觀念。教學(xué)的根本目標(biāo)是讓學(xué)生學(xué)到知識、提高能力,從而有所收獲,而不僅僅是讓教師完成一項教學(xué)任務(wù),因此,教師要投入大量的心血研究課程的教學(xué)規(guī)律和學(xué)生的特點,有針對性地設(shè)計課程的教學(xué)內(nèi)容和教學(xué)方式,讓學(xué)生愿意學(xué),而且能學(xué)好。
其次,正確處理編程語言與編程思想之間的關(guān)系。選修此課程的學(xué)生大多是非計算機(jī)專業(yè)的本科生,編程基礎(chǔ)較為薄弱,基本上只學(xué)過一門結(jié)構(gòu)化的程序設(shè)計語言,如C語言,因此,要想方設(shè)法幫助他們建立面向?qū)ο蟮木幊趟枷?。從C到Java,不僅僅是編程語言的變化,還是編程思想的升級。要讓學(xué)生逐漸脫離“程序就是數(shù)據(jù)加函數(shù)”的舊觀念,形成“類、對象、抽象與封裝、繼承與多態(tài)”等新思想。
最后,正確處理理論與實踐之間的關(guān)系。理論學(xué)習(xí)固然重要,但實踐能力的培養(yǎng)更加重要。作為一門程序設(shè)計課程,它的最終目的一定不是讓學(xué)生學(xué)會多少理論知識,而是實踐能力得到提高,能夠解決實際的問題,編寫出真正有用的軟件,因此,在課程設(shè)計上,要勇于創(chuàng)新,銳意改革,突出強(qiáng)調(diào)學(xué)生思維能力和動手實踐能力的培養(yǎng)。在課堂講授環(huán)節(jié),通過引入一些創(chuàng)新性問題激發(fā)學(xué)生思維,培養(yǎng)學(xué)生的創(chuàng)新能力和分析、解決問題能力;在課外訓(xùn)練環(huán)節(jié),要精心設(shè)計大作業(yè)的內(nèi)容,讓學(xué)生完成一個具有一定規(guī)模和實用性的軟件,通過這種方式培養(yǎng)和提高學(xué)生的動手實踐能力。
在課堂教學(xué)上,根據(jù)課程和學(xué)生的特點,精心地設(shè)計教學(xué)內(nèi)容,并在教學(xué)目標(biāo)和教學(xué)理念的指導(dǎo)下,提出相應(yīng)的教學(xué)方法。
本課程是一門3學(xué)分、48學(xué)時的課程,每周3學(xué)時,共16周。采用的教材是由清華大學(xué)出版社出版的《Java程序設(shè)計》,是為了配合本課程的講授以及便于學(xué)生學(xué)習(xí)而編寫的,其內(nèi)容與本課程的內(nèi)容一致。課程的考核方式為平時作業(yè)占30%,大作業(yè)占40%,期末考試占30%。
表1是課程的教學(xué)大綱,包括每一章節(jié)的主要內(nèi)容和相應(yīng)的學(xué)時數(shù),總共有10章。
表1 課程大綱
課程的教學(xué)大綱和教學(xué)內(nèi)容是按照3條主線展開的,即Java語言、面向?qū)ο蟪绦蛟O(shè)計和Java類庫。
2.2.1 Java語言
Java語言的內(nèi)容安排在第2章,主要介紹Java的語法,包括數(shù)據(jù)類型與變量、運算符與表達(dá)式、控制結(jié)構(gòu)、數(shù)組等。由于Java可以算是從C++發(fā)展而來的,因此Java與C語言的語法比較類似,有些甚至是完全相同的,如算術(shù)運算、關(guān)系運算、邏輯運算、選擇結(jié)構(gòu)、循環(huán)結(jié)構(gòu)等。熟悉C語言就能夠很輕松地掌握J(rèn)ava語言的語法,只要重點關(guān)注兩者之間的不同部分即可,如布爾型和字符型數(shù)據(jù)、數(shù)組等。
2.2.2 面向?qū)ο蟪绦蛟O(shè)計
面向?qū)ο蟪绦蛟O(shè)計的內(nèi)容主要安排在第3章,另外在第10章也有所涉及,這是本課程最核心、最重要的內(nèi)容之一,從課時數(shù)來看,占到整個課程的1/3以上。如前所述,選修本課程的學(xué)生大多是非計算機(jī)專業(yè)的本科生,編程基礎(chǔ)較為薄弱,一般只學(xué)過一門編程語言,如C語言,因此,如何幫助他們培養(yǎng)和建立面向?qū)ο蟮乃季S方式,是一個巨大的挑戰(zhàn)。在第3章,我們用11課時講類、對象、訪問控制、函數(shù)重載、繼承和多態(tài)。在內(nèi)容設(shè)計上,要突出難點和重點。
一個難點是引用(Reference)類型,Java中的引用類型有點像C語言中的指針類型,引用可以脫離對象而單獨存在,要善于區(qū)分引用的運算和對象的運算。例如,在比較兩個字符串時,不能直接用關(guān)系運算符“==”,因為比較的是兩個引用的值,而不是目標(biāo)字符串的內(nèi)容。另外,在使用引用作為函數(shù)的參數(shù)時,傳遞的是目標(biāo)對象的地址而非內(nèi)容,這一點也比較容易混淆。
另一個難點是數(shù)據(jù)的存儲,即對于一個Java程序,它的不同類型的數(shù)據(jù)分別存儲在內(nèi)存的什么地方,以及各自的作用范圍和生存期分別是什么。靜態(tài)變量存放在靜態(tài)數(shù)據(jù)區(qū),函數(shù)的形參和局部變量存放在棧(stack)中,這就需要講清楚棧和棧幀(stack frame)的原理。棧幀是隨著函數(shù)調(diào)用的開始而分配空間,隨著函數(shù)調(diào)用的結(jié)束而釋放空間。當(dāng)執(zhí)行一個Java程序時,隨著一次次的函數(shù)調(diào)用,就會在棧中形成一個動態(tài)的棧幀序列。動態(tài)申請的空間都是存放在內(nèi)存的堆(heap)中,例如,如果用new新建一個類的對象,那么該對象(包括它的所有成員變量)就存放在堆中。此外,對于程序員來說,堆中的對象可以只創(chuàng)建不釋放,但這并不會導(dǎo)致內(nèi)存泄露,因為Java專門有一個垃圾收集器(garbage collector)回收所有失聯(lián)對象的內(nèi)存空間。
另一個難點是多態(tài),這個知識點對于學(xué)生來說是最難掌握的。所謂多態(tài),即在一個類的層次結(jié)構(gòu)中,一個引用變量根據(jù)它所指向的對象類型改變其行為的能力。這允許不同子類的多個對象可被視為同一個父類的對象,卻又能根據(jù)各個對象所屬的子類自動地選擇合適的函數(shù)去執(zhí)行。這里的難點在于:類與成員函數(shù)是分離的,對象與引用也是分離的。如果定義了一個父類引用,然后調(diào)用了它的某個成員函數(shù),但最終執(zhí)行的可能并不是這個父類相應(yīng)的成員函數(shù),可能是該父類的某個子類同名的成員函數(shù),而且該父類往往又會有多個子類,每個子類都會有一個同名的成員函數(shù)。在這種情形下,學(xué)生就會迷惑不解,到底執(zhí)行的是哪一個類的成員函數(shù)呢?因此,對于這樣一個難度比較大的內(nèi)容,需要仔細(xì)斟酌,精心設(shè)計課件內(nèi)容,務(wù)必讓學(xué)生能夠真正掌握。
除了上述難點,在教學(xué)內(nèi)容設(shè)計上,還要注意突出重點。本課程的一個重點,就是要幫助學(xué)生建立面向?qū)ο蟮乃季S方式,讓學(xué)生在面對一個實際的問題時,學(xué)會如何進(jìn)行分析、歸納和抽象,從中抽取出類的定義,并構(gòu)造類和類之間的層次關(guān)系。在課堂講授上,可以在課件中設(shè)計多個案例。例如,在第3章給出如下3個案例:斗地主游戲、Windows中的畫圖軟件以及一家模擬的律師事務(wù)所。在這些案例中,引導(dǎo)學(xué)生分析和總結(jié),正確地區(qū)分類與對象以及設(shè)計合理的類的層次結(jié)構(gòu)。在斗地主游戲中,有一個角色叫豬八戒,可以引導(dǎo)學(xué)生這個豬八戒并不能定義為一個類,因為他和其他的游戲玩家如孫悟空和沙和尚具有相同的屬性和行為,因此可以把他們抽象為一個類:游戲玩家類,然后豬八戒、孫悟空和沙和尚都只是該類的一個對象。在律師事務(wù)所案例中,有不同類型的員工,如律師、市場銷售、秘書、法律秘書等,可以引導(dǎo)學(xué)生建立類和類之間的層次關(guān)系。具體來說,無論是律師、銷售還是秘書,都是公司的員工,都要遵守一些相同的規(guī)章制度,如果把他們定義為完全獨立、互不相干的類,就不太合適,可以建立一個新的類:雇員類,然后律師、銷售和秘書都是該類的子類。同樣的道理,秘書和法律秘書也可以形成相應(yīng)的父類和子類關(guān)系。
在第10章,進(jìn)一步系統(tǒng)地討論如何進(jìn)行面向?qū)ο蠓治鲞@個問題,即對于給定的問題領(lǐng)域,如何確定類與類之間的關(guān)系以及如何確定類的屬性和操作,可以介紹當(dāng)前常用的幾種方法,包括名詞/動詞分析法、RUP構(gòu)造型和CRC模型法。以名詞/動詞分析法為例,這是一種運用語言分析的簡單方法,即從文本描述中識別出有關(guān)的名詞或動詞,嘗試找出類、屬性和職責(zé)。一般來說,名詞和名詞短語暗示著類或類的屬性,動詞和動詞短語暗示著職責(zé)或者類的操作。
由于Java本身就是一種面向?qū)ο蟮木幊陶Z言,因此還可使用Java類庫中的例子。例如,在講授數(shù)據(jù)的輸入輸出這部分內(nèi)容時,并不是直接把相關(guān)的Java類圖告訴學(xué)生,而是引導(dǎo)學(xué)生,這個類圖是如何設(shè)計出來的。如果是我們自己設(shè)計這個類圖,需要考慮如下問題。
(1)數(shù)據(jù)流的方向:輸入還是輸出?
(2)用戶接口:需要定義哪些操作?接口函數(shù)的定義和實現(xiàn)放在什么地方?
(3)數(shù)據(jù)的來源:文件、網(wǎng)絡(luò)、鍵盤、其他線程等。
(4)讀寫延遲:從外部設(shè)備輸入輸出數(shù)據(jù)時,在時間上會有延遲。
根據(jù)數(shù)據(jù)流的方向,可以推斷出需要定義兩個方面的類,即輸入相關(guān)的類和輸出相關(guān)的類;根據(jù)用戶接口,可以推斷出需要定義一個類的層次結(jié)構(gòu),然后把函數(shù)的原型放在上層的接口中,把具體的實現(xiàn)放在下層的實體類中;根據(jù)數(shù)據(jù)的來源,可以推斷出對于每一種不同的來源,需要定義一個相應(yīng)的實體類;根據(jù)讀寫延遲,可以推斷出需要定義帶有緩沖區(qū)的輸入輸出類。
2.2.3 Java類庫
本課程的第3條主線是Java類庫,主要是介紹Java系統(tǒng)中現(xiàn)有的一些類的使用方法。這部分內(nèi)容安排在第4章—第8章,包括異常處理、輸入輸出、圖形用戶界面、線程、網(wǎng)絡(luò)和對象集合。
在介紹這部分內(nèi)容時,為了讓學(xué)生的理解更深入、更透徹,需要與其他課程知識相融合。例如,在講解輸入輸出、文件、線程、網(wǎng)絡(luò)等章節(jié)時,都是先從操作系統(tǒng)的角度講清楚相關(guān)原理,然后再從Java類庫的角度講具體實現(xiàn)。
以線程為例,先從操作系統(tǒng)的角度講解相關(guān)的理論背景,如什么是進(jìn)程、進(jìn)程的并發(fā)運行、進(jìn)程的狀態(tài)、什么是線程、線程間的數(shù)據(jù)共享、同步與互斥、調(diào)度與優(yōu)先級等;然后再討論在Java語言中如何實現(xiàn)這些內(nèi)容,如用Thread類創(chuàng)建線程,用堆對象實現(xiàn)數(shù)據(jù)共享,用互斥鎖和線程間通信方法實現(xiàn)同步與互斥以及線程的優(yōu)先級等。
在教學(xué)目標(biāo)、教學(xué)理念和教學(xué)內(nèi)容明確以后,就需要精心設(shè)計教學(xué)方法,把課程講好、講精彩。
首先, 在內(nèi)容組織上,要做到結(jié)構(gòu)清晰,各部分內(nèi)容之間要前后關(guān)聯(lián)、邏輯性強(qiáng)。以多態(tài)為例,如前所述,這部分內(nèi)容難度很大,因此在設(shè)計課件時,是按照Why,What,How這樣一個邏輯結(jié)構(gòu)組織內(nèi)容:先講為什么要引入多態(tài),即希望對一組擁有相同父類的不同子類的對象進(jìn)行批量化或參數(shù)化處理,如定義一個數(shù)組,能夠同時裝入這組對象;或者定義一個函數(shù),能夠使用這組對象作為參數(shù)。然后講什么是多態(tài),它是一種合而不同的機(jī)制,即在編寫代碼時將這組子類對象按照相同類型(即父類)進(jìn)行處理,以簡化代碼;而在程序運行時,再根據(jù)對象的類型分別綁定到各自不同的實現(xiàn)函數(shù)。最后,再通過例子系統(tǒng)地歸納和總結(jié)多態(tài)的所有用法,具體來說,一次函數(shù)調(diào)用的形式為obj.method(),其中,obj是一個引用,本身會有一個類型(父類或子類),另外它會指向一個對象,該對象也會有一個類型(父類或子類),而method是父類或子類中的某個成員函數(shù)。在這種情形下,可以把所有可能的組合都枚舉出來,見表2。
表2 多態(tài)函數(shù)調(diào)用
在表2中,“父有子無”就是指該函數(shù)在父類中有定義,而在子類中沒有?!案笩o子有”則相反,“父子都有”即該函數(shù)在父類和子類中都有定義。該表格把所有可能的組合都枚舉了出來,這樣學(xué)生今后再碰到類似的情形,就不會再有疑惑。例如,對于子類對象、父類引用的情形,在調(diào)用一個“父有子無”的函數(shù)時,會調(diào)用父類的函數(shù);在調(diào)用一個“父無子有”的函數(shù)時,會編譯錯誤,無法執(zhí)行;在調(diào)用一個“父子都有”的函數(shù)時,會根據(jù)多態(tài)的原理,調(diào)用子類的相應(yīng)函數(shù)。
其次,在內(nèi)容設(shè)計上,要善于激發(fā)學(xué)生的學(xué)習(xí)興趣,能夠以生動活潑、通俗易懂的方式講述復(fù)雜的原理概念,原因在于學(xué)生聽課是一個體力活,時間長了容易疲倦,而生動有趣的內(nèi)容容易留下深刻的印象,而且記得牢。
例如,在講解Java子類對象的存儲這部分內(nèi)容時,相關(guān)的知識點是這樣:在創(chuàng)建一個子類對象后,一方面,該子類對象本身是一個獨立、完整的對象;另一方面,在該對象內(nèi)部,又包含一個父類子對象,該子對象與正常創(chuàng)建的父類對象相同。對于這樣一段話,學(xué)生顯然不太好理解,因此可以在課件中貼一張圖片,即電影鋼鐵俠的海報。如果把人類(Man)看成父類,鋼鐵俠(IronMan)看成子類,那么顯然,在一個子類對象(即影片中的鋼鐵俠)內(nèi)部,又包含了一個父類對象(即商界大亨Tony Stark)。事實上,只要鋼鐵俠把外面這層裝甲脫掉,他就變成一個普通人。
再如,在介紹接口(interface)這部分內(nèi)容時,相關(guān)的知識點是這樣:接口主要用來將不同類中的共性方法抽取和封裝起來,它只是一個抽象的接口,只有函數(shù)的聲明,沒有具體的實現(xiàn)。顯然,這段話的確很抽象,學(xué)生不太好理解。為了加深學(xué)生的印象,可以“杜撰”一個例子。在一些國外的電影中,經(jīng)常會有英雄的形象,所謂英雄,一般會做兩件事情,即打仗和談戀愛,因此可以定義一個英雄類,類名為Hero,它本身是人類類(類名為Man)的子類,同時又實現(xiàn)兩個接口,一個是Fighter,即能打仗;另一個是Lover,即能談戀愛,這樣,學(xué)生就在輕松歡快的氛圍中記住接口的原理。
最后,在教案設(shè)計和課堂教學(xué)安排中,要以學(xué)生為中心,師生互動、課堂交流充分。在課堂教學(xué)中,一個常見的問題就是教師講得太多、太快,而學(xué)生的參與程度不夠。講課的最終目的是讓學(xué)生能夠聽懂,能夠?qū)W到知識和提高能力。如果教師講了100%的內(nèi)容,而學(xué)生只聽懂30%,那么這樣的課堂就是失敗的。在教案設(shè)計和課堂教學(xué)中,都要特別重視與學(xué)生的交流。具體來說,在教案設(shè)計中,要經(jīng)常采用啟發(fā)式教學(xué)方法,提高學(xué)生的參與程度。例如,在設(shè)計課件時經(jīng)常預(yù)留一些交互式問答,讓學(xué)生回答問題或者讓學(xué)生問問題,同時在課堂上經(jīng)常使用Java集成開發(fā)環(huán)境,現(xiàn)場與學(xué)生一起編寫和運行程序,這樣,學(xué)生的積極性和主動性就會得到很大提高。
對于一門程序設(shè)計類課程,課堂教學(xué)固然重要,但更重要的是課外實踐,是實踐能力得到提高,能夠解決實際的問題,編寫出真正有用的軟件。為了實現(xiàn)這個目標(biāo),課后作業(yè)分為兩部分,一個是每周的編程練習(xí),主要是用來復(fù)習(xí)課堂上所講授的內(nèi)容;另一個是大作業(yè),學(xué)生要完成一個具有一定規(guī)模和實用性的軟件。大作業(yè)的選題需要遵守以下幾條原則。
(1)大作業(yè)要與課堂講授的內(nèi)容相一致。課堂上講授的內(nèi)容主要包括面向?qū)ο缶幊?、圖形用戶界面、多線程編程、網(wǎng)絡(luò)編程、異常處理、文件等,在大作業(yè)中需要體現(xiàn)這些內(nèi)容。
(2)大作業(yè)需要有一定的規(guī)模,代碼量不能太少,否則無法起到訓(xùn)練的效果。
(3)大作業(yè)需要有一定的實用性,如果做好了,那么就是一個實用的應(yīng)用軟件。
(4)大作業(yè)要有一定的趣味性,這樣學(xué)生的積極性就更高一些。
就目前來說,各個學(xué)期布置的大作業(yè)題目一般是一些經(jīng)典的小游戲,包括貪吃蛇、俄羅斯方塊、連連看、坦克大戰(zhàn)、黑白棋、飛機(jī)大戰(zhàn)、黃金礦工等。
從大作業(yè)的要求來看,以坦克大戰(zhàn)為例,大作業(yè)的總分為40分,其要求包括圖形用戶界面16分(包括界面功能實現(xiàn)6分、美觀程度4分、性能要求6分)、事件處理4分(包括鍵盤按鍵響應(yīng)2分和碰撞檢測2分)、網(wǎng)絡(luò)編程6分(包括socket網(wǎng)絡(luò)通信3分、客戶端狀態(tài)同步策略3分)、其他游戲功能4分、聲音和音效2分、文件讀寫2分、實驗報告6分。要求只能使用Java語言編程,而且每位學(xué)生獨自完成整個作業(yè)。
從往屆學(xué)生的情況來看,絕大多數(shù)學(xué)生能順利完成作業(yè),只是完成的效果不盡相同。有部分學(xué)生的作業(yè)非常完美,界面美觀、運行流暢、網(wǎng)絡(luò)狀態(tài)同步實時、音效良好、動畫炫美,幾乎就是一個可以上線的軟件產(chǎn)品。圖1和圖2所示分別是坦克大戰(zhàn)和黑白棋的優(yōu)秀作品。
圖1 坦克大戰(zhàn)的優(yōu)秀作品
圖2 黑白棋的優(yōu)秀作品
Java是一門重要的編程語言,在軟件工業(yè)有著廣泛的應(yīng)用。如何在大學(xué)中講好這門課程,切實地提高學(xué)生的分析問題、解決問題和動手實踐能力,是每一名任課教師需要認(rèn)真思考的問題。本文就是在這個方面的一個嘗試,從教學(xué)目標(biāo)、教學(xué)理念、教學(xué)內(nèi)容設(shè)計,到教學(xué)方法和實踐訓(xùn)練,完整地描述了一門課程的所有環(huán)節(jié)。經(jīng)過多年的實踐,這門課程已經(jīng)基本成熟,也逐漸得到了學(xué)生的認(rèn)可;在歷年的教學(xué)評估中,都取得了較好的成績,尤其是在2016年秋季學(xué)期的教學(xué)評估中,在全校同類、同規(guī)模的536門課程中,進(jìn)入了全校前5%。
未來的工作包括進(jìn)一步完善教學(xué)內(nèi)容,提出新的教學(xué)案例,根據(jù)學(xué)生的反饋改進(jìn)大作業(yè)的內(nèi)容和安排。