面向对象的编程-Application 26

Previously on OOP:

FileReaderandFileWriterclass can be used to read a file composed of characters. And there are three ways: (1), read one character at a time; (2), read a buffer's size at a time; (3), read one line at a time.

在上文中,我们提到过可以在读取“marks.txt”的时候使用regular expression来把不同的字段存放到相应的数据结构中。

由于“marks.txt”的结构还算比较简单,每行只有两个字段,中间用“-”分割,所以我们也可以选用比regular expression简单的分割方式。本黄鸭将要在本文中举一个这样的例子。

首先,我们要选定一种数据结构来存放分解出来的字段。下面是一种在本课程中最为普遍的结构:

数据结构分有内外两层。

外层选用数组,Collection,或者是Map中的任意一种。输入文件中的每一行就存为外层数据结构中的一个数据,或者说是一个entry。

内层是开发者新创建的一个类,比如Student,里面有两个attributes,分别存放当前这个学生的名字和分数。

因为编程推荐使用top-down的思维方式,即从大的框架上想起,再慢慢补充实现的细节,这样一来,许多细节不用自己编写,能选择编译器给出的几个建议中的一个,所以从编写顺序上来看,应该先编写外层数据结构。但是由于内层的实现比较简单,本黄鸭还是选择先看内层数据结构:

Student类的attributes, constructor, and getter methods都应该没有问题。toString()方法可以把Student的实例转化为字符串,方便于打印。

最后一个newStudent()函数是要和read by line+split according to“-”配合使用的。在split according to“-”之后,“-”的前后两个字段会自动被保存在一个数组中。然后这个数组作为参数传递到newStudent()函数中,进行类型转换,最后调用constructor,创建Student类的实例。

我们再看外层数据结构:

main函数下面第一行,“Collection”是囊括了两层数据结构的声明。外层是Collection或者是它的子类,内层是Student,也就是说一个Collection里面存放的数据类型是Student类的。本黄鸭还要再强调一遍,内层一定不能是Student类的子类,因为Java Generics的type invariant性质。

“Collection”数据结构的object reference的名称是“student”,这个名字不是特别好。最好改成复数形式,体现里面存放了复数个Student类的数据。

readMarks()是本类中的一个方法,下文中会作为重点分析。但是对于这个方法有两点是很明确的:

(1)返回值:肯定是Collection的实例。

(2)功能:读取“marks.txt”文件的内容,并且把各种字段分门别类地存放到数据结构中去。

本段代码中还包含了try block,是为了handleIOException。这里编写得不是很完整,缺少catch block。这是因为main函数位于hierarchy of invocation的顶端,不能把任何的Exception传递出去,只能当场解决。

接下来,在把数据写入output file之后,没有关闭文件,反而调用了flush()函数。这种做法也是可以的,因为该函数的功能是:Write everything from the cache to the secondary memory before the program terminates。

P.S.

Student类中重载了toString()函数,所以单个Student的打印结果肯定是按照重载后的来。但是Collection / List的toString()函数没有重载,所以整个student Collection的打印还是按照原来的来。

下面是本文的重点,implementation ofreadMarks() method,有以下两种方案可供选用。

Solution 1

readMarks()方法的返回值的类型是“List”,符合要求。BufferedReader类在上一篇文章中已经举过例子了,它能实现按照行来读取的文件内容。接下来,代码中声明一个局部的、临时的数据结构,类型是“LinkedList”,名称是“res”,顾名思义,会用作为返回值。还声明了一个String类型的变量,名字叫做“line”,用于存放从“marks.txt”中读取的每一行。

然后,使用while循环结构依次读取并处理“marks.txt”文件中的每一行。String类中有一个函数是split(),能把指定字符的前面和后面部分差分成sub-strings,并且存放到一个叫做“elements”的数组中。split()函数的指定字符通过参数传递。

再把elements数组作为参数,调用Student类中的newStudent()函数。这里使用dotted notation来调用,在dotted notation前面不是Student类的实例,而是类的名称,说明newStudent()函数必须是static的。

While循环体的最后一行把新建的Student的实例加入到res数据结构中。在这个函数的最后,返回res。

Solution 2

第二种做法就是用Stream。Open Stream的方法比较特别,调用了BufferedReader类的lines()函数。

第一个intermediate process是用Lambda expression来调用String类的split()函数,“line”是一个String类型的临时变量。之所以用Lambda expression是因为split()函数有参数需要传递。

比较特别的是这个Lambda expression外面套着一个map()函数。为什么是map()函数呢?因为在open Stream执行之后,Stream中的数据是“marks.txt”中的每一行,所以都是String类型的。而在split()函数调用之后,Stream的数据都要变成存放sub-strings的数组。也就是说,第一个intermediate process会导致Stream中数据的类型发生变化。那么,像filter()这些不会发生数据类型变化的肯定就不行了,只能在flatMap() and map()中选。所谓flatMap(),是把原来Stream的数据中的数据结构抽出来,整合成一个新的Stream,不符合现在的使用场景,所以就只能选用map()函数了。

第二个intermediate process是用method reference来调用Student类的newStudent函数。

最后的termination process采用了已经定义在库类中的Collector,名称是“toList()”,这个Collector能把Stream的所有数据收集起来,形成一个List。如果不用Collector,用forEach loop也能实现这个功能,有兴趣的宝宝们可以自己练习一下。

欢迎使用本黄鸭编写的小程序~

微信公众号二维码:

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181031G1DTV200?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券