选择在暑期学习面向对象先导课程的初衷是为大二下学期面向对象课程做一些铺垫,接触入门java语言。在接触java的过程中,就编程语言的学习方法而言,我从刚入学时的手慌脚乱四处寻求帮助到现在慢慢养成了自己不断寻找困难解决方法的习惯,感觉自己的另一种自学能力——一种计算机工科的实践能力得到了培养,这种自学能力跟学习基础课程理论知识的感觉完全不同,这种需要在实践和动手中得到经验和知识的自学方法与以前理解现成抽象理论概念的自学方法可以说是完全不同了。作为一个偏好理论研究的理科女生,动手实践能力在此之前几乎为零,是一个完完全全的编程小白,大学对我而言是一个重新开始、从零开始的起点,能在先导课程中进一步培养短期内学习到大量内容掌握大量技能的能力,是我觉得收获很大的地方。我有时候也会觉得困难,但这只会让我回想起以前的学习过程中,我总会默默告诉自己,“All things are difficult before they are easy”,没关系,所有不能毁灭我的,必将使我更强大。
在先导课的第一节课上,我了解到了java的一些基本编程思想。Java是一门面向对象的高级编程语言,在第一节课,我就感受到了面向对象的思路,在后来的课程中,对java的高级有了更深刻的印象,其中之一表现在java丰富的类库以及友好的编程环境。Java的面向对象编程的特点之一体现在类的概念。这和之前一直熟悉的数据结构所使用的C语言有很大的区别。在之前的学习过程中,使用C语言的时候大多是面向过程进行编程,更侧重于思考怎么解决一个问题的过程。而使用java时,需要思考抽象出来一个类,这个class中包含着抽象出来的共同属性与方法,其中抽象出类的过程往往是比较困难。这让我想到接触的第一门编程语言Python中类和实例的概念,跟java极其相似。在后来对类的使用过程中了解到,前面有static限制的函数方法只能通过函数的直接调用被访问到,而不能通过建立实例来调用方法。Public类型的函数可以在实例化以后被访问到,protected类型不可以被外界直接访问但是可以被继承,private则是类内部的函数,不可以被外界访问,是安全的。在java的使用过程中,变量的类型尽量使用private来提高安全性能防止局部变量的值被任意改变。如果想要获取或者改变私有变量的值的话,调用相关的public即可,这实际上是一种封装的方法。如:
1 public class WordCount { 2 private String word; 3 private int count; 4 5 public int getCount() { 6 return count; 7 } 8 9 public void setCount(int count) { 10 this.count = count; 11 } 12 13 public String getWord() { 14 return word; 15 } 16 17 public void setWord(String word) { 18 this.word = word; 19 } 20 }
还有笔者在第一次接触java的时候曾经犯过两个印象深刻的错误,一个是在类里面写了除变量声明和函数之外的语句(实际上这些语句应该在函数体里面),另一个是没有实例化对象直接调用了方法,笔者认为这两个错误对于写惯C语言的java初学者来说都是值得注意的小地方。在第一节课,老师带我们以box类为例学习了一些java的基本语法要求,重要的内容是学习到了java中的构造方法以及继承。构造方法相当于C语言里面的初始化,在没有构造函数的时候,java会对变量进行特定的默认初始化赋值。构造函数无需返回值类型,作用域类型是public,this表示当前对象(用.取对象的变量或者方法,类似于C语言的指针用法,但java里面没有指针),然后用传进来的参数对当前对象(使用的时候需要实例化)进行赋值即可。构造函数中所使用的变量声明不需要构造函数之前,因为在编译的时候会搜索类中所有内容来找到要复制的变量。还有最重要的一个特征是,构造函数的名字要和类的名字相同。在new一个类即实例化一个对象的时候,相当于同时调用了类的构造函数,即进行了初始化赋值。下面是构造函数的使用demo:
1 public class Box1 { 2 public Box1(double width, double height, double depth){ 3 this.width = width; 4 this.height = height; 5 this.depth = depth; 6 } 7 private double vol; 8 double width; 9 double height; 10 double depth; 11 public double volume(){ 12 vol = width*height*depth; 13 return vol; 14 } 15 }
还有一个很重要的特性就是继承。继承可以从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。但是不能有选择性的继承。子类可以继承父类非private型的所有成员。继承的关键字是extends。下面是一个继承使用方法的例子:
1 public class ScaleBox extends Box1{ 2 private double volume; //注意养成写作用域的好习惯 3 public ScaleBox(double width, double height, double depth, double volume){ 4 super(width, height, depth); //用super调用父类方法,必须是第一行防止后面值被改变(否则编译器会报错) 5 volume = 0; 6 } 7 public double setScale(double scale){ 8 volume = height * scale * width * scale * depth * scale; 9 return volume; 10 } 11 }
此外,java的语法与C语言有很多的相似之处,但是有些细节还是不尽相同。在java是根据布尔值来确定if条件的真假,如像“boolean flag = false;”这样声明变量,同样,函数的返回值也可以是布尔值。还有值得注意的是,在java中回车是由回车符(\r)与换行符(\n)两个字符组成的。
第二堂课上主要介绍了封装以及java接口的概念。Java接口(Interface)是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为或功能。Java语言不支持一个类有多个直接的父类(多继承),但可以实现(implements)多个接口,间接的实现了多继承。Java接口中的成员变量默认都是public,static,final类型的(都可省略),必须被显示初始化,即接口中的成员变量为常量(大写,单词之间用"_"分隔)。Java接口中的方法默认都是public,abstract类型的(都可省略),不能是静态方法,没有方法体,不能被实例化,接口中只能包含抽象方法而不能直接实现函数体的内容和功能。Java接口中只能包含public,static,final类型的成员变量和public,abstract类型的成员方法。接口中没有构造方法,不能被实例化,接口只是抽象的。一个接口不能实现另一个接口,但它可以继承多个其它的接口,同样使用extends方法进行继承,新定义的接口被称为复合接口。Java接口必须通过类来实现它的抽象方法,如“public class Cylinder implements Geometry”。此外,值得注意的是,当类实现了某个Java接口时,它必须实现接口中的所有抽象方法,否则这个类必须声明为抽象的。不允许创建接口的实例(实例化),但允许定义接口类型的引用变量,该引用变量引用实现了这个接口的类的实例 ,如:
public class B implements A{} A a = new B(); //引用变量a被定义为A接口类型,引用了B实例 A a = new A(); //错误,接口不允许实例化
在第三和第四节课里,老师和助教又带领我们学习了java的调试功能以及如何进行性能分析,进一步对程序进行性能提高练习,两节课里我们以词频统计为练习样例,同时练习了一些字符串处理的小技巧。字符串的分割(split方法)对于格式化处理字符串有着很大的帮助作用。其中分割时借助正则表达式可以大大化简处理方法,如对字符串"ab-c,de..f,gAB, CDE,FGH,CDE?f"进行分割取出字母串的时候,可以使用按位或从正面进行分割,也可以用非字母的正则表达式进行分割,用法如下:
String str1[]=s1.split("[\\?]+|[\\.]+|[\\-]+|[,| ]+"); //[^A-z]+ “+”表示不含空串
笔者为了提高程序的性能,使用了哈希方法的变种对文本文档进行了词频统计,写法类似于字典树。不仅提高了查找效率,还使得单词本身有序,用深度优先搜索即可完成单词或者短语的有序输出。笔者在完成第四次作业的时候遇到了几个难缠的bug,第一个是对大容量文档测试的时候,总是容易出现数组溢出的报错,笔者将所有报错点的字符输出,发现都是乱码的字符,经很多次的资料查找后发现,可能是是因为原文文档出现了中文之类的字符,java中的中文字符是两个字符进行表示,在读入的时候没有进行特殊的读入处理的时候是会出现乱码的情况的,于是笔者更换了测试文档。在测试了大量不同的测试文档之后,笔者增加了分隔符种类以保证短语定义的严密性。还有一个是对空串的处理,要是想实现在读入字符串的时候能够读入在首位的空白字符而不是过滤掉空白字符进行字符串读入,需要使用BufferedReader而不是scanner,因为scanner会自动过滤掉首位空白字符进行字符串读取。另外要是想判断空白字符及空白字符组合形成的空串,需要使用字符串的trim方法。具体实现的部分代码如下:
1 BufferedReader buffer = new BufferedReader(new InputStreamReader(System.in)); 2 String s = buffer.readLine(); 3 if (s.trim().isEmpty()) { 4 System.out.println("The string is empty!"); 5 } 6 else {... 7 }
经过短短两周的学习过程,笔者觉得自己对java有了初步的了解,可以尝试多在以后的代码编写中利用java写程序。在课程学习中,个人对吴老师富有启发式和引导式的教学方法印象深刻,这让笔者对java学习产生了浓厚的兴趣,无论是课上还是课下,一个接着一个的紧密衔接的任务让笔者对java有了不同的感受。还有对笔者的学习过程中有即大帮助的是助教在课上和课后的细心解答,有困难和问题的时候能得到及时的解决,加深了对java的理解。这些帮助是我觉得选择先导课的幸运之处。从不安与担心到享受解决问题,笔者对coding有了不同的看法。还有一个重大的收获是,以前写代码都是力求每个函数每个细节都亲力亲为,比如排序之类的函数,但是现在终于认识到java类库的强大,这样写出的代码质量更高而且看起来很漂亮。以及笔者在逐渐锻炼自己很匮乏的一项能力——自己查找资料,百度也好Google也好,利用现有的工具解决问题,远比自己一个人战斗要高效的多,要学会善于利用资源,即所谓的工欲善其事必先利其器。以前在数理化学习过程中喜欢自己思考自学,很少问别人也很少寻求帮助,只要自己思考能解决的问题绝对不会问别人,现在才体会到借助互联网学习是很强大的资源。在学习过程中,能得到老师和助教面对面的帮助以及对java某些概念的引导来帮助理解陌生内容并且在课堂上一起完成代码是我觉得本课程最有帮助的地方。因为是初次接触课上的代码测试,而不是类似于在oj上直接课后提交看到测试结果,会在课上测试的时候显得手慌脚乱而拖延课程的进度,希望在刚开始的时候可以提前发一些测试点让大家适应一下课上测试。以及发布课后作业要求的时候更清楚一些。
面向对象这门课程很重要,不仅仅体现在学科知识掌握方面。从宏观人类社会角度来看,面向对象的研究方法是符合人类社会规律的。科幻电影《降临》介绍了因果论和目的论,其原著《你一生的故事》中打翻了人类不可预知未来的前提,间接说明了如果知道未来的结果,人类就会向着这个结果来实现中间的过程,不再存在因果与选择。笔者在寒假看完《降临》的一个午后,突然意识到,面向过程编程是目的论的产物,而面向对象编程才是符合目前人类社会因果论的解决问题的方法,这种选择与不确定性以及类和对象的概念,是用来模拟人类社会最好的选择,在那一瞬间,忽然体会到计算机科学的奇妙之处,感觉到自己所知甚少,是多么渺小。就像曾经初学物理时,感到有很多问题无处可问,有焦躁和不安,现在想想,初学计算机的感受竟和小时候的自己如此相似,忽然感觉到一种妙不可言的联系。那就借用物理书的编者曾写的一首打油诗来结尾吧
“昔年曾见此湖图,不信人间有此湖。
今日打从湖上过,画工还欠费功夫。”
愿以后的自己看到现在的自己,永远能感叹一声画工还欠费功夫。