谌卫军
(清华大学 计算机科学与技术系,北京 100084)
众所周知,Java既是一种编程语言,又是一个跨系统的运行平台,在软件工业界得到广泛的应用,成为众多程序员的首选编程语言。事实上,在软件工业历届的程序设计语言排名榜中,Java语言始终名列前茅,并在大部分时间内排名第一,尤其是随着移动开发和安卓手机的爆炸式增长,Java语言又出现了新的增长点,因为安卓系统上的应用软件都是用Java语言开发的,因此Java语言的普及和推广具有广泛的市场需求。由于Java是一种面向对象的编程语言,很多内容具有一定的难度,如抽象与封装、函数重载与重写、数据的存储、继承、多态、动态绑定、抽象类与接口、对象集合等,特点是单调枯燥、晦涩难懂,而且有的还与其他课程如操作系统的知识相融合,因此学生在学习时可能会面临一些困难。
笔者在清华大学开设了一门全校性选修课“Java语言程序设计”,主要面向全校对Java 编程比较感兴趣的本科生,以工科院系为主,既有计算机、软件、电子、自动化等信息相关专业的学生,又有工业工程、物理、经管、精仪、机械、化学工程等非信息专业的学生。经过几年的努力,这门课程逐渐受到学生的欢迎。虽然春季和秋季这两个学期都有开课,但是仍然供不应求,学生的选课热情很高。因此,在以往教学经验的基础上[1-2],如何讲好这样一门既有需求、又有一定难度的课程,需要认真地思考、总结和实践。
从教学目标来看,这门课程的教学目标主要有3个。
(1)编程语言的学习,即把Java看成一种编程语言,学习这种语言的使用方法。
(2)编程思想的学习,即把Java看成面向对象编程语言的一个具体实例,通过它学习面向对象的编程思想,包括编码、设计与分析。
(3)实践能力的培养,即把Java看成一种实用的软件开发工具,利用它解决实际的问题,编写出一些实用的应用程序。
为了实现这些目标,在教学理念上应遵守如下原则。
首先,坚持“以学生为中心”的观念。教学的根本目标是让学生学到知识、提高能力,从而有所收获,而不仅仅是让教师完成一项教学任务,因此,教师要投入大量的心血研究课程的教学规律和学生的特点,有针对性地设计课程的教学内容和教学方式,让学生愿意学,而且能学好。
其次,正确处理编程语言与编程思想之间的关系。选修此课程的学生大多是非计算机专业的本科生,编程基础较为薄弱,基本上只学过一门结构化的程序设计语言,如C语言,因此,要想方设法帮助他们建立面向对象的编程思想。从C到Java,不仅仅是编程语言的变化,还是编程思想的升级。要让学生逐渐脱离“程序就是数据加函数”的旧观念,形成“类、对象、抽象与封装、继承与多态”等新思想。
最后,正确处理理论与实践之间的关系。理论学习固然重要,但实践能力的培养更加重要。作为一门程序设计课程,它的最终目的一定不是让学生学会多少理论知识,而是实践能力得到提高,能够解决实际的问题,编写出真正有用的软件,因此,在课程设计上,要勇于创新,锐意改革,突出强调学生思维能力和动手实践能力的培养。在课堂讲授环节,通过引入一些创新性问题激发学生思维,培养学生的创新能力和分析、解决问题能力;在课外训练环节,要精心设计大作业的内容,让学生完成一个具有一定规模和实用性的软件,通过这种方式培养和提高学生的动手实践能力。
在课堂教学上,根据课程和学生的特点,精心地设计教学内容,并在教学目标和教学理念的指导下,提出相应的教学方法。
本课程是一门3学分、48学时的课程,每周3学时,共16周。采用的教材是由清华大学出版社出版的《Java程序设计》,是为了配合本课程的讲授以及便于学生学习而编写的,其内容与本课程的内容一致。课程的考核方式为平时作业占30%,大作业占40%,期末考试占30%。
表1是课程的教学大纲,包括每一章节的主要内容和相应的学时数,总共有10章。
表1 课程大纲
课程的教学大纲和教学内容是按照3条主线展开的,即Java语言、面向对象程序设计和Java类库。
2.2.1 Java语言
Java语言的内容安排在第2章,主要介绍Java的语法,包括数据类型与变量、运算符与表达式、控制结构、数组等。由于Java可以算是从C++发展而来的,因此Java与C语言的语法比较类似,有些甚至是完全相同的,如算术运算、关系运算、逻辑运算、选择结构、循环结构等。熟悉C语言就能够很轻松地掌握Java语言的语法,只要重点关注两者之间的不同部分即可,如布尔型和字符型数据、数组等。
2.2.2 面向对象程序设计
面向对象程序设计的内容主要安排在第3章,另外在第10章也有所涉及,这是本课程最核心、最重要的内容之一,从课时数来看,占到整个课程的1/3以上。如前所述,选修本课程的学生大多是非计算机专业的本科生,编程基础较为薄弱,一般只学过一门编程语言,如C语言,因此,如何帮助他们培养和建立面向对象的思维方式,是一个巨大的挑战。在第3章,我们用11课时讲类、对象、访问控制、函数重载、继承和多态。在内容设计上,要突出难点和重点。
一个难点是引用(Reference)类型,Java中的引用类型有点像C语言中的指针类型,引用可以脱离对象而单独存在,要善于区分引用的运算和对象的运算。例如,在比较两个字符串时,不能直接用关系运算符“==”,因为比较的是两个引用的值,而不是目标字符串的内容。另外,在使用引用作为函数的参数时,传递的是目标对象的地址而非内容,这一点也比较容易混淆。
另一个难点是数据的存储,即对于一个Java程序,它的不同类型的数据分别存储在内存的什么地方,以及各自的作用范围和生存期分别是什么。静态变量存放在静态数据区,函数的形参和局部变量存放在栈(stack)中,这就需要讲清楚栈和栈帧(stack frame)的原理。栈帧是随着函数调用的开始而分配空间,随着函数调用的结束而释放空间。当执行一个Java程序时,随着一次次的函数调用,就会在栈中形成一个动态的栈帧序列。动态申请的空间都是存放在内存的堆(heap)中,例如,如果用new新建一个类的对象,那么该对象(包括它的所有成员变量)就存放在堆中。此外,对于程序员来说,堆中的对象可以只创建不释放,但这并不会导致内存泄露,因为Java专门有一个垃圾收集器(garbage collector)回收所有失联对象的内存空间。
另一个难点是多态,这个知识点对于学生来说是最难掌握的。所谓多态,即在一个类的层次结构中,一个引用变量根据它所指向的对象类型改变其行为的能力。这允许不同子类的多个对象可被视为同一个父类的对象,却又能根据各个对象所属的子类自动地选择合适的函数去执行。这里的难点在于:类与成员函数是分离的,对象与引用也是分离的。如果定义了一个父类引用,然后调用了它的某个成员函数,但最终执行的可能并不是这个父类相应的成员函数,可能是该父类的某个子类同名的成员函数,而且该父类往往又会有多个子类,每个子类都会有一个同名的成员函数。在这种情形下,学生就会迷惑不解,到底执行的是哪一个类的成员函数呢?因此,对于这样一个难度比较大的内容,需要仔细斟酌,精心设计课件内容,务必让学生能够真正掌握。
除了上述难点,在教学内容设计上,还要注意突出重点。本课程的一个重点,就是要帮助学生建立面向对象的思维方式,让学生在面对一个实际的问题时,学会如何进行分析、归纳和抽象,从中抽取出类的定义,并构造类和类之间的层次关系。在课堂讲授上,可以在课件中设计多个案例。例如,在第3章给出如下3个案例:斗地主游戏、Windows中的画图软件以及一家模拟的律师事务所。在这些案例中,引导学生分析和总结,正确地区分类与对象以及设计合理的类的层次结构。在斗地主游戏中,有一个角色叫猪八戒,可以引导学生这个猪八戒并不能定义为一个类,因为他和其他的游戏玩家如孙悟空和沙和尚具有相同的属性和行为,因此可以把他们抽象为一个类:游戏玩家类,然后猪八戒、孙悟空和沙和尚都只是该类的一个对象。在律师事务所案例中,有不同类型的员工,如律师、市场销售、秘书、法律秘书等,可以引导学生建立类和类之间的层次关系。具体来说,无论是律师、销售还是秘书,都是公司的员工,都要遵守一些相同的规章制度,如果把他们定义为完全独立、互不相干的类,就不太合适,可以建立一个新的类:雇员类,然后律师、销售和秘书都是该类的子类。同样的道理,秘书和法律秘书也可以形成相应的父类和子类关系。
在第10章,进一步系统地讨论如何进行面向对象分析这个问题,即对于给定的问题领域,如何确定类与类之间的关系以及如何确定类的属性和操作,可以介绍当前常用的几种方法,包括名词/动词分析法、RUP构造型和CRC模型法。以名词/动词分析法为例,这是一种运用语言分析的简单方法,即从文本描述中识别出有关的名词或动词,尝试找出类、属性和职责。一般来说,名词和名词短语暗示着类或类的属性,动词和动词短语暗示着职责或者类的操作。
由于Java本身就是一种面向对象的编程语言,因此还可使用Java类库中的例子。例如,在讲授数据的输入输出这部分内容时,并不是直接把相关的Java类图告诉学生,而是引导学生,这个类图是如何设计出来的。如果是我们自己设计这个类图,需要考虑如下问题。
(1)数据流的方向:输入还是输出?
(2)用户接口:需要定义哪些操作?接口函数的定义和实现放在什么地方?
(3)数据的来源:文件、网络、键盘、其他线程等。
(4)读写延迟:从外部设备输入输出数据时,在时间上会有延迟。
根据数据流的方向,可以推断出需要定义两个方面的类,即输入相关的类和输出相关的类;根据用户接口,可以推断出需要定义一个类的层次结构,然后把函数的原型放在上层的接口中,把具体的实现放在下层的实体类中;根据数据的来源,可以推断出对于每一种不同的来源,需要定义一个相应的实体类;根据读写延迟,可以推断出需要定义带有缓冲区的输入输出类。
2.2.3 Java类库
本课程的第3条主线是Java类库,主要是介绍Java系统中现有的一些类的使用方法。这部分内容安排在第4章—第8章,包括异常处理、输入输出、图形用户界面、线程、网络和对象集合。
在介绍这部分内容时,为了让学生的理解更深入、更透彻,需要与其他课程知识相融合。例如,在讲解输入输出、文件、线程、网络等章节时,都是先从操作系统的角度讲清楚相关原理,然后再从Java类库的角度讲具体实现。
以线程为例,先从操作系统的角度讲解相关的理论背景,如什么是进程、进程的并发运行、进程的状态、什么是线程、线程间的数据共享、同步与互斥、调度与优先级等;然后再讨论在Java语言中如何实现这些内容,如用Thread类创建线程,用堆对象实现数据共享,用互斥锁和线程间通信方法实现同步与互斥以及线程的优先级等。
在教学目标、教学理念和教学内容明确以后,就需要精心设计教学方法,把课程讲好、讲精彩。
首先, 在内容组织上,要做到结构清晰,各部分内容之间要前后关联、逻辑性强。以多态为例,如前所述,这部分内容难度很大,因此在设计课件时,是按照Why,What,How这样一个逻辑结构组织内容:先讲为什么要引入多态,即希望对一组拥有相同父类的不同子类的对象进行批量化或参数化处理,如定义一个数组,能够同时装入这组对象;或者定义一个函数,能够使用这组对象作为参数。然后讲什么是多态,它是一种合而不同的机制,即在编写代码时将这组子类对象按照相同类型(即父类)进行处理,以简化代码;而在程序运行时,再根据对象的类型分别绑定到各自不同的实现函数。最后,再通过例子系统地归纳和总结多态的所有用法,具体来说,一次函数调用的形式为obj.method(),其中,obj是一个引用,本身会有一个类型(父类或子类),另外它会指向一个对象,该对象也会有一个类型(父类或子类),而method是父类或子类中的某个成员函数。在这种情形下,可以把所有可能的组合都枚举出来,见表2。
表2 多态函数调用
在表2中,“父有子无”就是指该函数在父类中有定义,而在子类中没有。“父无子有”则相反,“父子都有”即该函数在父类和子类中都有定义。该表格把所有可能的组合都枚举了出来,这样学生今后再碰到类似的情形,就不会再有疑惑。例如,对于子类对象、父类引用的情形,在调用一个“父有子无”的函数时,会调用父类的函数;在调用一个“父无子有”的函数时,会编译错误,无法执行;在调用一个“父子都有”的函数时,会根据多态的原理,调用子类的相应函数。
其次,在内容设计上,要善于激发学生的学习兴趣,能够以生动活泼、通俗易懂的方式讲述复杂的原理概念,原因在于学生听课是一个体力活,时间长了容易疲倦,而生动有趣的内容容易留下深刻的印象,而且记得牢。
例如,在讲解Java子类对象的存储这部分内容时,相关的知识点是这样:在创建一个子类对象后,一方面,该子类对象本身是一个独立、完整的对象;另一方面,在该对象内部,又包含一个父类子对象,该子对象与正常创建的父类对象相同。对于这样一段话,学生显然不太好理解,因此可以在课件中贴一张图片,即电影钢铁侠的海报。如果把人类(Man)看成父类,钢铁侠(IronMan)看成子类,那么显然,在一个子类对象(即影片中的钢铁侠)内部,又包含了一个父类对象(即商界大亨Tony Stark)。事实上,只要钢铁侠把外面这层装甲脱掉,他就变成一个普通人。
再如,在介绍接口(interface)这部分内容时,相关的知识点是这样:接口主要用来将不同类中的共性方法抽取和封装起来,它只是一个抽象的接口,只有函数的声明,没有具体的实现。显然,这段话的确很抽象,学生不太好理解。为了加深学生的印象,可以“杜撰”一个例子。在一些国外的电影中,经常会有英雄的形象,所谓英雄,一般会做两件事情,即打仗和谈恋爱,因此可以定义一个英雄类,类名为Hero,它本身是人类类(类名为Man)的子类,同时又实现两个接口,一个是Fighter,即能打仗;另一个是Lover,即能谈恋爱,这样,学生就在轻松欢快的氛围中记住接口的原理。
最后,在教案设计和课堂教学安排中,要以学生为中心,师生互动、课堂交流充分。在课堂教学中,一个常见的问题就是教师讲得太多、太快,而学生的参与程度不够。讲课的最终目的是让学生能够听懂,能够学到知识和提高能力。如果教师讲了100%的内容,而学生只听懂30%,那么这样的课堂就是失败的。在教案设计和课堂教学中,都要特别重视与学生的交流。具体来说,在教案设计中,要经常采用启发式教学方法,提高学生的参与程度。例如,在设计课件时经常预留一些交互式问答,让学生回答问题或者让学生问问题,同时在课堂上经常使用Java集成开发环境,现场与学生一起编写和运行程序,这样,学生的积极性和主动性就会得到很大提高。
对于一门程序设计类课程,课堂教学固然重要,但更重要的是课外实践,是实践能力得到提高,能够解决实际的问题,编写出真正有用的软件。为了实现这个目标,课后作业分为两部分,一个是每周的编程练习,主要是用来复习课堂上所讲授的内容;另一个是大作业,学生要完成一个具有一定规模和实用性的软件。大作业的选题需要遵守以下几条原则。
(1)大作业要与课堂讲授的内容相一致。课堂上讲授的内容主要包括面向对象编程、图形用户界面、多线程编程、网络编程、异常处理、文件等,在大作业中需要体现这些内容。
(2)大作业需要有一定的规模,代码量不能太少,否则无法起到训练的效果。
(3)大作业需要有一定的实用性,如果做好了,那么就是一个实用的应用软件。
(4)大作业要有一定的趣味性,这样学生的积极性就更高一些。
就目前来说,各个学期布置的大作业题目一般是一些经典的小游戏,包括贪吃蛇、俄罗斯方块、连连看、坦克大战、黑白棋、飞机大战、黄金矿工等。
从大作业的要求来看,以坦克大战为例,大作业的总分为40分,其要求包括图形用户界面16分(包括界面功能实现6分、美观程度4分、性能要求6分)、事件处理4分(包括键盘按键响应2分和碰撞检测2分)、网络编程6分(包括socket网络通信3分、客户端状态同步策略3分)、其他游戏功能4分、声音和音效2分、文件读写2分、实验报告6分。要求只能使用Java语言编程,而且每位学生独自完成整个作业。
从往届学生的情况来看,绝大多数学生能顺利完成作业,只是完成的效果不尽相同。有部分学生的作业非常完美,界面美观、运行流畅、网络状态同步实时、音效良好、动画炫美,几乎就是一个可以上线的软件产品。图1和图2所示分别是坦克大战和黑白棋的优秀作品。
图1 坦克大战的优秀作品
图2 黑白棋的优秀作品
Java是一门重要的编程语言,在软件工业有着广泛的应用。如何在大学中讲好这门课程,切实地提高学生的分析问题、解决问题和动手实践能力,是每一名任课教师需要认真思考的问题。本文就是在这个方面的一个尝试,从教学目标、教学理念、教学内容设计,到教学方法和实践训练,完整地描述了一门课程的所有环节。经过多年的实践,这门课程已经基本成熟,也逐渐得到了学生的认可;在历年的教学评估中,都取得了较好的成绩,尤其是在2016年秋季学期的教学评估中,在全校同类、同规模的536门课程中,进入了全校前5%。
未来的工作包括进一步完善教学内容,提出新的教学案例,根据学生的反馈改进大作业的内容和安排。