摘要:Java語言已經(jīng)成為計(jì)算機(jī)專業(yè)學(xué)生所必須掌握的程序設(shè)計(jì)語言。本文根據(jù)作者近年來講授Java程序設(shè)計(jì)的教學(xué)實(shí)踐經(jīng)驗(yàn),介紹如何合理組織教學(xué)內(nèi)容及進(jìn)度安排,引導(dǎo)學(xué)生完成從面向過程到面向?qū)ο蟪绦蛟O(shè)計(jì)的重大轉(zhuǎn)變。
關(guān)鍵詞:Java語言;面向?qū)ο蟪绦蛟O(shè)計(jì);教學(xué)難點(diǎn)
中圖分類號:G642 文獻(xiàn)標(biāo)識(shí)碼:B
1引言
由于Java語言本身所具備的跨平臺(tái)、多線程、垃圾收集等優(yōu)點(diǎn),以及基于J2EE的企業(yè)級應(yīng)用的大規(guī)模開發(fā),Java語言已成為主流的計(jì)算機(jī)程序設(shè)計(jì)語言之一[1-3]。目前,基于Java語言的網(wǎng)絡(luò)資源急劇增加,大量的開源軟件通過Java語言開發(fā),因此,掌握J(rèn)ava語言已經(jīng)成為計(jì)算機(jī)專業(yè)學(xué)生的必然要求。
計(jì)算機(jī)專業(yè)開設(shè)Java程序設(shè)計(jì)課程基本上分為兩種情況:(1)將Java語言作為學(xué)生學(xué)習(xí)的第一種程序設(shè)計(jì)語言;(2)將Java語言作為C語言等程序設(shè)計(jì)語言之后的后續(xù)語言。我們學(xué)校計(jì)算機(jī)專業(yè)本科生首先在一年級學(xué)習(xí)C程序設(shè)計(jì)以及數(shù)據(jù)結(jié)構(gòu),然后在二年級學(xué)習(xí)Java程序設(shè)計(jì)。因此,我們的教學(xué)實(shí)踐活動(dòng)主要針對第二種情況。本文根據(jù)作者近五年的Java程序設(shè)計(jì)教學(xué)實(shí)踐所積累的經(jīng)驗(yàn),闡述如何引導(dǎo)學(xué)生完成從面向過程到面向?qū)ο蟪绦蛟O(shè)計(jì)的重大轉(zhuǎn)變。
2學(xué)生技能背景與授課重點(diǎn)難點(diǎn)
由于經(jīng)過一年多的C語言編程訓(xùn)練,這些學(xué)生已經(jīng)具備一定的面向過程的程序設(shè)計(jì)能力。由于Java語言在基本語法上與C語言非常類似。因此,講授Java程序設(shè)計(jì)的側(cè)重點(diǎn)不在于基本語法,而是在于面向?qū)ο蟪绦蛟O(shè)計(jì)能力的培養(yǎng)上。
對于具有一定C程序設(shè)計(jì)能力的學(xué)生,雖然已經(jīng)掌握了一定的編程方法,但是學(xué)習(xí)Java程序設(shè)計(jì)仍然需要克服許多困難。由于以前的程序設(shè)計(jì)一般側(cè)重于通過C語言設(shè)計(jì)一個(gè)算法,并以此來解決某個(gè)簡單的問題。由于這些問題規(guī)模很小,程序一般僅需要1個(gè)或者少數(shù)幾個(gè)函數(shù),因此他們往往將計(jì)算機(jī)程序設(shè)計(jì)理解為編寫幾個(gè)函數(shù),并不能深刻理解程序設(shè)計(jì)方法中的許多思想(比如模塊化、代碼重用等)。
由于在講授Java語言時(shí),采用的例子程序往往也非常簡單,一般采用面向過程的C程序也可以解決,因此,他們往往質(zhì)疑Java程序設(shè)計(jì)是將簡單問題復(fù)雜化。由于在教學(xué)時(shí)一般不能過多采用復(fù)雜的案例,因此必須在教學(xué)過程中充分說明每個(gè)例子所體現(xiàn)的程序設(shè)計(jì)思想,而不是僅僅通過算法解決某個(gè)簡單問題。
我們認(rèn)為,講授Java程序設(shè)計(jì)的重點(diǎn)與難點(diǎn)都在于如何將學(xué)生的程序設(shè)計(jì)思維從面向過程轉(zhuǎn)變?yōu)槊嫦驅(qū)ο?。通過合理組織Java程序設(shè)計(jì)教學(xué)內(nèi)容,將學(xué)生從面向過程的編程思維逐步轉(zhuǎn)變?yōu)槊嫦驅(qū)ο缶幊趟季S,并理解和掌握面向?qū)ο缶幊痰睦^承、多態(tài)、封裝等技術(shù)特點(diǎn);在教學(xué)進(jìn)度安排上,將從學(xué)生最熟悉的C程序面向過程編程方式開始,逐步引導(dǎo)過渡到Java面向?qū)ο蟪绦蛟O(shè)計(jì),按照先后次序包括Java程序設(shè)計(jì)入門、類的設(shè)計(jì)、繼承與多態(tài)等步驟。
3Java程序設(shè)計(jì)入門
對于具備C語言程序設(shè)計(jì)經(jīng)驗(yàn)的學(xué)生而言,Java程序設(shè)計(jì)入門并不困難。由于Java語言的基本語法與C語言非常相似,因此這一部分教學(xué)內(nèi)容的側(cè)重于說明Java語言基本語法、數(shù)據(jù)類型、程序結(jié)構(gòu)等方面與C語言的不同之處。這部分內(nèi)容的教學(xué)目標(biāo)包括:(1)使學(xué)生能夠編寫簡單的Java程序;(2)使學(xué)生能夠使用Java類庫中的類進(jìn)行編程,掌握如何創(chuàng)建類的對象,如何使用類中的方法,以及如何導(dǎo)入包中的類等。
事實(shí)上,許多人在講授Java程序設(shè)計(jì)入門時(shí),往往強(qiáng)調(diào)上述教學(xué)目標(biāo)的第一點(diǎn),即讓學(xué)生了解Java程序結(jié)構(gòu)特點(diǎn),能夠編寫簡單的Java程序即可,然后直接介紹Java類的設(shè)計(jì)。然而根據(jù)我們近五年的Java程序設(shè)計(jì)教學(xué)經(jīng)驗(yàn),上述教學(xué)組織方式知識(shí)跨度較大,不適合于基礎(chǔ)較差的學(xué)生。相反,讓學(xué)生多使用Java類庫中的類進(jìn)行編程,能夠在一定程度上了解Java類的特點(diǎn)以及常用術(shù)語,從而為Java類的設(shè)計(jì)提供良好基礎(chǔ),避免學(xué)生在開始階段就過多糾纏于類設(shè)計(jì)的細(xì)節(jié)。學(xué)生在使用Java類進(jìn)行編程時(shí),還未涉及面向?qū)ο蟮某绦蛟O(shè)計(jì)思想,他們可以使用熟悉的面向過程程序設(shè)計(jì)思想進(jìn)行編程。只不過在C語言中他們需要寫多個(gè)函數(shù),而在Java語言中需要將這些函數(shù)(即Java語言的方法)放入到某個(gè)類中。
這部分教學(xué)的主要內(nèi)容如下:
(1)Java應(yīng)用程序結(jié)構(gòu)
大部分教材都通過一個(gè)類似于輸出“HelloWorld”的例子,說明如何編寫一個(gè)可以獨(dú)立運(yùn)行的類。教學(xué)經(jīng)驗(yàn)告訴我們,這種方式效果很好。通過比較Java應(yīng)用程序結(jié)構(gòu)與C語言主函數(shù)的異同之處,學(xué)生能夠理解Java類的靜態(tài)主方法,很快就可以學(xué)會(huì)編寫一個(gè)簡單的Java程序。
(2) 基本語法與數(shù)據(jù)類型
由于Java語言的基本語法與數(shù)據(jù)類型與C語言非常類似,所以教學(xué)的重點(diǎn)是突出兩個(gè)語言的數(shù)據(jù)類型的不同之處,包括以下幾點(diǎn):
#61548;boolean類型:Java語言提供8種基本數(shù)據(jù)類型,其中boolean類型是Java所特有的數(shù)據(jù)類型,它擁有兩種取值true或1。在C語言中,往往通過0或者1來表示true還是1,但是對于Java語言將不再允許通過數(shù)字來表示true或者1。比如,在C語言中,可以使用if(1){},而在Java語言中,必須使用if(true){}
#61548;char類型:Java語言的char類型不同于C語言中的char類型。由于Java語言使用Unicode編碼,所以char類型是兩個(gè)字節(jié)長度。而對于標(biāo)準(zhǔn)C語言,char是1個(gè)字節(jié)長度。
#61548;數(shù)組類型: Java中的數(shù)組是對象類型,必須先創(chuàng)建對象才能使用。聲明一個(gè)長度為10的int類型數(shù)組,對于C語言用int a[10],而在Java中必須用int[] a = new int[10]。
教學(xué)經(jīng)驗(yàn)表明,上述三種數(shù)據(jù)類型是具備C語言編程經(jīng)驗(yàn)的Java初學(xué)者最容易犯錯(cuò)誤的地方,尤其是boolean與數(shù)組類型。由于Java語言與C語言的語法與數(shù)據(jù)類型過于相似,兩者容易混淆,必須通過大量的例子和訓(xùn)練強(qiáng)化它們的不同之處。
(3) 使用Java類庫編程
學(xué)生掌握了Java語言基本語法之后,可以開始講授Java基礎(chǔ)類庫所提供的類。這樣做主要達(dá)到如下目的:一方面,讓學(xué)生了解Java基礎(chǔ)類庫所提供的強(qiáng)大功能;另一方面,在講解這些常用類時(shí),逐步介紹Java類所涉及的一些概念(比如構(gòu)造方法,成員方法,成員變量、方法重載等),以及如何使用它們(比如導(dǎo)入包),從而為Java類的設(shè)計(jì)提供良好的鋪墊。
由于Java基礎(chǔ)類庫中的類很多,必須挑選一些簡單的、最好是熟悉或者感興趣的類。比如以下幾個(gè)類:
#61548;Math類:學(xué)生在學(xué)習(xí)C語言時(shí)都接觸過數(shù)學(xué)函數(shù),所以非常容易理解Math這個(gè)類。由于Math這個(gè)類使用了大量的靜態(tài)成員以及靜態(tài)方法重載,因此在講授這個(gè)類時(shí),重點(diǎn)說明如何使用靜態(tài)方法與靜態(tài)常量,并了解Java靜態(tài)方法的重載特點(diǎn)。
#61548;String類:學(xué)生在編程過程中經(jīng)常用到字符串,因此有必要介紹String類的使用。通過這個(gè)類讓學(xué)生了解如何創(chuàng)建對象,如何使用對象的實(shí)例方法,并了解構(gòu)造方法以及實(shí)例方法的重載(因?yàn)镾tring類中存在構(gòu)造方法及實(shí)例方法的重載現(xiàn)象)。
4類的設(shè)計(jì)
通過前面內(nèi)容的教學(xué),學(xué)生初步掌握了如何使用Java類,并了解了類的一些基本概念。這時(shí)就可以開始講解如何設(shè)計(jì)一個(gè)類,包括成員變量、構(gòu)造方法以及成員方法的設(shè)計(jì)。這部分內(nèi)容的教學(xué)過程中需要逐步傳授面向?qū)ο蟮囊恍└拍钆c思想。
由于C語言的結(jié)構(gòu)體與Java的類有點(diǎn)相似,因此,從一個(gè)普通的類似于結(jié)構(gòu)體的Java類開始,逐步添加構(gòu)造方法,實(shí)例方法,并說明它們的作用,然后再如何使用訪問控制符來對類成員進(jìn)行封裝,以及如何通過包來組織類。在教學(xué)過程中,不僅僅說明編寫一個(gè)類所涉及的知識(shí)點(diǎn),而且還說明這些內(nèi)容的用途和意義所在。
這部分教學(xué)的主要內(nèi)容如下:
(1) 從C結(jié)構(gòu)到Java類
對于C程序員,他們對Java類的最初認(rèn)識(shí),往往是覺得類是一個(gè)添加了方法的C結(jié)構(gòu)。這種認(rèn)識(shí)雖然不太準(zhǔn)確,但是卻是可以理解(至少形式上存在某種相似性)。可以通過一個(gè)矩形類來說明類似于C結(jié)構(gòu)的Java類,其中l(wèi)ength,width分別是矩形對象的長與寬, id代表矩形對象的編號(從0開始編號),而靜態(tài)變量nextId用于指明下一個(gè)矩形對象的編號(即每創(chuàng)建一個(gè)對象后nextId自增1)。
通過這個(gè)簡單的類,可以介紹類成員變量(包括實(shí)例變量與類變量)的特點(diǎn),以及如何使用它們(由于前面講解Java程序設(shè)計(jì)入門時(shí),學(xué)生已經(jīng)了解如何使用它們,所以容易接受這一部分內(nèi)容的知識(shí)點(diǎn))。
(2) 設(shè)計(jì)構(gòu)造方法
Java類的構(gòu)造方法的作用是創(chuàng)建對象,并對其進(jìn)行初始化。然而,許多學(xué)生一開始并不理解構(gòu)造方法,認(rèn)為即使沒有構(gòu)造方法,也可以對對象進(jìn)行初始化。比如,可以通過Rectangle a = new Rectangle()創(chuàng)建對象,然后通過a.length = 10.0; a.width = 5.0; a.id = 0; a.nextId ++; 等語句對對象進(jìn)行初始化。因此,需要通過一些簡單的例子向?qū)W生說明構(gòu)造方法的用途。比如,如果不定義Rectangle類的構(gòu)造方法,那么若干創(chuàng)建許多個(gè)Rectangle對象,需要類似于前面的大量的初始化代碼(每個(gè)對象需要4個(gè)語句初始化),并且需要保證初始化語句邏輯的正確性。相反,如果提供構(gòu)造方法,并將對象的初始化代碼移入到構(gòu)造方法,那么每個(gè)對象創(chuàng)建僅需要調(diào)用構(gòu)造方法,從而避免了功能相似代碼的重復(fù),并且在使用Rectangle類時(shí),不需要考慮初始化代碼間的邏輯關(guān)系(在構(gòu)造方法中考慮這些邏輯關(guān)系)。教學(xué)經(jīng)驗(yàn)表明,學(xué)生能夠理解構(gòu)造方法的作用,并主動(dòng)去寫構(gòu)造方法。
(3) 設(shè)計(jì)成員方法
學(xué)生開始階段很難主動(dòng)地編寫類的成員方法。許多學(xué)生仍然把java的類當(dāng)作C語言的結(jié)構(gòu)體來用。比如,對于某個(gè)矩形對象a,那么a的面積可以通過a.length*a.width獲得,a的周長可以通過(a.length+a.width)*2來計(jì)算。這時(shí)需要向?qū)W生說明實(shí)例方法的好處:(1)將這些針對對象的具體功能代碼移入實(shí)例方法,可以減少代碼重復(fù);(2)封裝具體功能的實(shí)現(xiàn)算法。比如,在Rectangle類中提供area()方法,那么每次調(diào)用這個(gè)方法就可以得到具體的矩形面積,并且不需要知道是如何計(jì)算出面積。更進(jìn)一步,可以舉出一個(gè)較為復(fù)雜的多邊形類,那時(shí)計(jì)算面積就復(fù)雜多了,這時(shí)提供實(shí)例方法就顯得非常有意義了。當(dāng)然,在這一部分教學(xué)內(nèi)容中,還需要強(qiáng)調(diào)實(shí)例方法與類方法的區(qū)別,同時(shí)說明如何設(shè)計(jì)重載的成員方法。教學(xué)經(jīng)驗(yàn)表明,學(xué)生理解了成員方法的用途之后,會(huì)主動(dòng)將經(jīng)常用到的功能代碼移入成員方法之中。
(4) 訪問控制與封裝
不同于C語言結(jié)構(gòu)體,Java類中的成員可以被訪問控制符所修飾,從而對類成員進(jìn)行封裝。訪問控制符包括public,private,protect等。對于初學(xué)者而言,非常不理解為何通過訪問控制進(jìn)行封裝。他們往往喜歡用public訪問控制(而不喜歡使用private訪問控制),因?yàn)檫@樣使用方式類似于C語言。這時(shí),向?qū)W生說明訪問控制與封裝的作用就顯得非常重要。就以前面的Rectangle類為例,如果它的成員變量length,width的訪問控制為public,那么這些變量的值就可以任意修改,比如修改為負(fù)數(shù),導(dǎo)致出錯(cuò),因此,可以將這些變量的訪問類型設(shè)置為private以防止非法修改,同時(shí)提供非private類型的實(shí)例方法以保證對這些private類型的成員變量的間接訪問。
學(xué)生理解了private訪問控制后,講解其他幾種訪問控制類型就容易多了。然后,再說明可以將類設(shè)置為public或者非public,前者將對其他包中的類開放訪問權(quán)限,而后者僅僅對同一個(gè)包中的類開放訪問權(quán)限。
5繼承與多態(tài)
這部分教學(xué)內(nèi)容主要講解子類與接口設(shè)計(jì),以及它們所帶來的繼承與多態(tài)的特性。學(xué)生掌握如何設(shè)計(jì)一個(gè)簡單的類之后,就可以開始講授子類與接口的設(shè)計(jì)。子類設(shè)計(jì)與前面一部分內(nèi)容可以很好地銜接,因?yàn)樵O(shè)計(jì)一個(gè)類時(shí)經(jīng)常可以在一個(gè)類的基礎(chǔ)上擴(kuò)展,而不需要從零開始。在講授子類設(shè)計(jì)的技術(shù)細(xì)節(jié)之后,需要講解如何設(shè)計(jì)類的繼承體系結(jié)構(gòu),以及接口的作用與意義。
這部分教學(xué)的主要內(nèi)容如下:
(1) 子類設(shè)計(jì)
講解子類設(shè)計(jì)的重點(diǎn)是繼承。子類通過繼承超類的成員,能夠很大程度上增加代碼的重用。這個(gè)意義必須要向?qū)W生說明。子類設(shè)計(jì)的授課重點(diǎn)包括:
#61548;子類構(gòu)造方法設(shè)計(jì)
子類構(gòu)造方法必須隱含或者顯式調(diào)用超類的構(gòu)造方法,而且這種調(diào)用過程構(gòu)成一條構(gòu)造方法調(diào)用鏈。雖然這僅僅是Java語言的一個(gè)規(guī)定,然而必須要向?qū)W生說明這個(gè)規(guī)定內(nèi)在的含義。由于構(gòu)造方法的主要作用是對象初始化,那么規(guī)定超類構(gòu)造方法必須先被調(diào)用的主要目的就是保證繼承而來的成員能夠在使用之前被正確初始化。
#61548;成員覆蓋與對象上轉(zhuǎn)型
在設(shè)計(jì)子類時(shí),可以重新定義超類繼承下來的成員變量或方法,從而出現(xiàn)覆蓋(overriding)或隱藏(hiding)兩種情況。據(jù)我們幾年來的教學(xué)經(jīng)驗(yàn),這部分內(nèi)容學(xué)生比較難以掌握。事實(shí)上,重定義超類的實(shí)例方法將出現(xiàn)覆蓋現(xiàn)象,而該現(xiàn)象的特點(diǎn)類似于替換。除了實(shí)例方法之外,重定義超類其它類型成員所導(dǎo)致的現(xiàn)象是隱藏。雖然學(xué)生可以較好地掌握覆蓋或者隱藏的語言特性,但是他們在開始階段往往無法掌握如何使用覆蓋或隱藏。這時(shí)需要通過對象的上轉(zhuǎn)型來加以說明。對象的上轉(zhuǎn)型現(xiàn)象說明實(shí)例方法的調(diào)用是與對象相關(guān),而與對象的引用類型無關(guān),而其它類型的成員則是與引用類型直接相關(guān)。對象的上轉(zhuǎn)型現(xiàn)象表明當(dāng)一個(gè)類的實(shí)例方法在子類中重新定義之后,而所有對該方法的默認(rèn)調(diào)用是子類所定義的版本,這使得編寫程序時(shí)即可以修改某一實(shí)例方法的功能,同時(shí)又不影響其它調(diào)用約定。教學(xué)過程中,需要通過較多的例子反復(fù)強(qiáng)調(diào)這些特點(diǎn)。這也是學(xué)生所必須掌握的Java程序設(shè)計(jì)不同于C程序設(shè)計(jì)的關(guān)鍵點(diǎn)之一。
(2) 類的繼承體系結(jié)構(gòu)設(shè)計(jì)
學(xué)生掌握子類設(shè)計(jì)技術(shù)之后,教學(xué)的重點(diǎn)轉(zhuǎn)向類的繼承體系結(jié)構(gòu)設(shè)計(jì),這是面向?qū)ο蟪绦蛟O(shè)計(jì)的核心部分。授課內(nèi)容包括抽象類與接口設(shè)計(jì),而授課重點(diǎn)在于強(qiáng)調(diào)面向?qū)ο蟮某绦蛟O(shè)計(jì)思想。對于抽象類與接口的語言語法特點(diǎn),可以安排少量課時(shí)。重點(diǎn)是通過設(shè)計(jì)實(shí)例來說明如何設(shè)計(jì)使用接口、抽象類,以及如何組織定義整個(gè)繼承框架結(jié)構(gòu)。我們講解的實(shí)例是Java語言所內(nèi)置的集合框架。通過這個(gè)實(shí)例說明面向?qū)ο蟪绦蛟O(shè)計(jì)中的關(guān)鍵思想與技術(shù),包括接口回調(diào)、對象上轉(zhuǎn)型以及代碼重構(gòu)等設(shè)計(jì)思想[4-5]。
6結(jié)束語
本文討論了作者講授Java程序設(shè)計(jì)的一些體會(huì)。我們所針對的教學(xué)對象是具有C程序設(shè)計(jì)技能背景的學(xué)生,所以教學(xué)重點(diǎn)是傳授面向?qū)ο蟪绦蛟O(shè)計(jì)的技術(shù)、思想與方法,而面臨的最大問題是如何將學(xué)生的程序設(shè)計(jì)思維從面向過程向面向?qū)ο筠D(zhuǎn)變,這需要一個(gè)循序漸進(jìn)的逐步轉(zhuǎn)變的過程。另外,我們認(rèn)為應(yīng)該將Java等面向?qū)ο蟪绦蛟O(shè)計(jì)語言作為計(jì)算機(jī)專業(yè)學(xué)生學(xué)習(xí)的第一門程序設(shè)計(jì)語言,而不是后續(xù)語言。因?yàn)橄葘W(xué)習(xí)C語言等面向過程的語言,會(huì)增加程序設(shè)計(jì)思維轉(zhuǎn)換的難度。當(dāng)然,這將涉及到整個(gè)課程體系結(jié)構(gòu)的調(diào)整,需要慎重考慮。
參考文獻(xiàn)
[1] Bloch Joshua. Effective Java [M]. Addison-Wesley, 2001.
[2] Sharon Zakhour, Scott Hommel, Jacob Royal, Isaac Rabinovitch, Tom Risser, Mark Hoeber. The Java Tutorial: A Short Course on the Basics (4th Edition)[M], Prentice Hall PTR, 2006.
[3] Bruce Eckel. Thinking in Java (4th Edition) [M]. Prentice Hall PTR, 2006.
[4] Martin Fowler. Refactoring: Improving the Desigh of Existing Code.[M]. Addison-Wesley, 2000.
[5] Joshua Kerievsky. Refactoring to Pattern [M]. Addison-Wesley, 2005.