Sort源码实现比较Go&Java语言风格(3)

前面两部分分别描述了Gojava两种语言对sort的使用方式和源码实现。作为go初学者,最想做的是通过例子和源码来对新的语言有个理解。这部分就结合自己的理解整理下,可以看到,是非常主观。

4 语言语法比较

可以看到,两种语言的思路基本上是一样的,用户必须定义比较规则。在排序过程中都要考察集合长度,使用用户定义的比较规则,然后调整元素位置来达到排序的效果,对应go的interface要求的三个方法。但是还是有挺多不同。

首先从使用方式上看,go的规则(通过方法来体现)定义在集合上,并且定义了三个方法,分别获取集合的状况,元素比较的规则,元素位置操作的方法;而java的规则定义在元素上,用户只用一个元素比较的规则compareTo就够了。看起来java要求用户定义少,因为其封装的多,这也是其一贯的风格。对用户要似乎更友好一点。比较而言go更直接,简单。留给程序员能干预的更多一点。但还是觉得Len和Swap方法留给用户定义的意义不是很大,就像源码中自带的好几个例子,Len除了返回集合长度, Swap(i,j)除了{ a[i], a[j] = a[j], a[i] },真想不好我们还能赋予它其他的使用方法。

从package的组织,能看到Go中更面向功能(或者说行为),功能更简单直接,而java更是面向对象组织的,封装更多。看到go sort的package下面的内容如图,而java一个Collections下面这么多功能方法,而看所在的的uitl包下上内容(只能截一屏还有一大半显示不全)。

看到了java的uitl包,下面除了Collections和Arrays这样几个工具类外更多的是ListMapSet这样的数据结构定义(通过接口),以及常用的ArrayList,LinedList,HashMap,HashTable,TreeSet这样的实现。同样的这样键值对的容器,即有HashMap这样非同步的,又有Hashtable这样同步的,同步个容器上嫌Hashtable上加一把锁影响并发性能,后来大师Doug Lea,又发明了ConcurrentMap接口,以及现在已成经典的实现ConcurrentHashMap,在一个容器上分开加多把锁。

而比较而言,go的结构就简单很多,语言内置容器数组和map提供了几乎所有我们要的功能,并且其操作非常简单,在源码中费劲的想找其他基于其上的结构居然没有找见。Go的简的风格在此体现的真是明显,只提供基础的数据结构,和对这些结构的好用的操作。稍微用了下切片操作真的非常赞,非要对照下和java的ArrayList挺像,但是[:]这样操作起来真的很简单直接。另外看sort源码时发现两个语言在各自package中,和sort 相伴的也都是search之类的操作。

当然,看例子中还有源码中两个语言的语法差别那就更多了。尤其要称赞下go的几个非常酷的语法。如快排中找中间点的方法

1func doPivot(data Interface, lo, hi int) (midlo, midhi int){..
2return lo + b - a, hi - (d - c)
3}

居然可以返回多个返回值,不用再像原来那样,要么一个返回值作为参数传进来,另外一个从return上取,要么就不得不构造一个结构来做组织多个返回值,费劲又别扭。真是解决程序员的真正问题。

还有,这个swap的方法实现,

1func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

这样一句就解决问题。而不是java这样费劲。

一下就把我大java比下去了。有没有想到很多年前,我们用c来数据结构的时候,老师反复强调,这时候要声明一个局部变量temp类做数据交换,非常神秘非常高级的知识点哈。在go这里居然真的可以直接交换。看上去有点像SQL这样的非过程语言了,又有点像我们描述代码功能的伪代码,只要告诉我你想干啥描述清楚,过程的能省就省了。非常非常简单,就这一小点,让咱这个新人产生了无数好感。

1private static void swap(Object[] x, int a, int b) {
2Object t = x[a];
3x[a] = x[b];
4x[b] = t;
5}

当然还有左大括号的使用,声明未使用的变量编译报错等新语法,看着蛮横,但却是简单。有一点点不习惯的是居然用方法名首字母的大小写来区分方法的访问权限,不像java中public protected private这样关键字来修饰,但说实话,写大写字母大头的方法还是有点瞅着怪怪的,老想给改成小写,虽然都是所谓的驼峰式的命名。

语法上最有感触的还是interface上的差别,在怀疑go是不是在刻意淡化interface或者压根儿人家就不承认那是你们原来认识的interface。

除了语法上像例子中描述的必须要implements一个接口,然实现接口中规定的方法。在java中我们一般会写一个interface,然后在框架中使用这个interface,来调用interface中定义的方法,模板一样的构造业务逻辑,后面的子类只要实现了这个接口,就可以走这个模板里的流程。几乎所有号称framework的牛叉框架都是这么干的,尤其像spring struts这些已经在深入人心了,所有搞web编程的小朋友们不管理解不理解,首先就会配置**.xml 在里面写**.Action之类,然后在自己的代码里implements一下这个Action的interace(或者中间再搞一层抽象类),就可以来堆积自己的业务(在简历上就可以写熟练掌握**框架)。

在我们的项目中确实也经常写一堆的Abstract Class 和Interface,定义这些顶层对象的行为,以一定抽象和设计的角度来编码这些顶层对象在整个业务中的角色地位和使用。这也体现了一种约束,不只是代码上的约束,也是开发过程的一种约束,一种契约和协议,项目中其他角色的人这部分实现必须要实现这些接口,也必须要按照规定的行为来实现。多符合我们项目开发过程中,老大制定规则,小弟负责遵守实施的思路。把文档文档中的设计变成interface,实现不好就编译错,这个约束力老大们多喜欢啊。

说实话,个人是很喜欢,也很习惯这么干。但每次这么干的时候,在子类上面必须要加个implements Interface1,表示我实现了这个接口,我会实现这个接口规定的行为,比较复杂的类往往会实现多个接口。如Hadoop的JobTracker这样的重要类,真是越重要的角色越要具备各种能力,更要有担当。很像我们项目中的所谓矩阵式管理,听多个领导的安排。 每次要听命一个新领导,就不得不在声明中把新领导implements在后面。

曾经有思考过,又没没有可能,不用这么侵入式的来做,我实现我的行为,我满足了哪个领导的规定,我就是哪个领导的兵。我只管实现自己的方法,不用在class头上必须implements。Go居然做到了,感觉在go中是先构建对象,然后再归纳出接口,而java中是先定义好接口,再让对象去实现。看上去约束少了,所以go要自然洒脱很多。

实践中这种先有接口还是现有实现其实也区分不清楚的,如这个例子中,实际上go和java的思路是没有差别的,你让我排序的东西起码得有个排序的规则(go的Less,java的copareTo),我才能有办法给你排个序,如例子中的按年龄还是按姓名。虽然初次见识,还是被其这样简洁的特征却征服。感觉就是你有这样的行为我就让你做这样的事情,不用大声说出来!

如例子中,把ByeAge把Len方法注释掉,编译同样能检查出编译错ByAge does not implement sort.Interface (missing Len method),而不是我在这个实验前想的,编译不报错,运行报错。

整体看下来。Go无处不在秀气简洁之道,包的组成,结构的定义,看源码涉及的代码行,大致也能看出来。当然也不会为了夸强调go的简洁,就使劲埋汰java。个人觉得,与语言的发展阶段可能有关系。这里用的java的源码为了简单期间是jdk1.6版本,能看到用的是mergesort,在新的jdk1.8中改为引入一个TimSort的排序方法的 TimSort类,原来的mergesort现在重命名为legacyMergeSort,并且声明在未来会去掉。

Go作为一个比较新的语言,简单是其风格。但相信随着发展,也会逐步扩展些功能出来。)但就这接触这几天,有些地方还是比较肤浅的要吐槽下,比如上面看到sort.go的源码中,似乎真的再使劲淡化Interface吗?也太低调了吧,在子类实现的时候不用implements提名下也就算了,这里这么重要的的一个Interface居然连个像样的名字都没有,藏在package sort中,取名就叫Interface,这名字还不如没名字呢。哪像我大java中对应的接口名曰Comparable接口这么响亮霸气,最主要是能告诉人家我是什么东西。

还有,看源码中最核心的快速排序方法:func quickSort(data Interface, a, b, maxDepth int)。方法声明上怎么还a、b都上来啦,也太信手啦,最起码也得start,end这样的吧。尤其看到方法中递归调用quickSort时候,a,b两个最重要的变量像小蝌蚪一样游弋在这段核心代码段中

1quickSort(data, a, mlo, maxDepth)
2a = mhi // i.e., quickSort(data, mhi, b)
3} else {
4quickSort(data, mhi, b, maxDepth)
5b = mlo // i.e., quickSort(data, a, mlo)
6}

作者是觉得是a、b是洒脱,还是孤独。呵呵,也是说到这里,调侃下。

5 后记

总的,这几天接触下来的感受,似乎能体会到语言的作者应该是一个老程序员吧,起码是非常了解程序员的大师。很多细节都是在为程序员考虑,对程序员非常友好。 基于为程序员提供一个更方便的语言,把挺多咱们这种老(其实不老哈)程序员以前只是敢想想嚷嚷两下的东西真的给做出来了。非常酷,非常NB,非常惊艳。上手非常容易,但是稍微思考下,会发现其设计非常讲究,尤其挺多和其他习惯用的语言比较打破比较多的开始会不习惯,慢慢也能体会到作者的良苦用心。希望在慢慢使用中,慢慢体会。

尤其是读到一点源码的时候,以前就有这样的体会,读这个语言相关源码的时候,能感受到一些文档中表达不到的东西,有些我们看了源码会比文档理解更深刻;有些我们会发现人传人的所谓官方文档吹的很NB,代码中也就这样,换咱做不了那么高明,大致也能做个差不多的样,而有些时候冷不丁看到有些源码中出现类似变量声明未使用(这个在go中居然要编译报错了,彪悍!),变量类型使用错、明显逻辑错、循环不正确、几个相关包中方法各自重复定义,也会调侃大师代码覆盖测试不够,不得不感叹大师也不是神,也会犯咱们每天都在犯的小错。不管是其上的那种体会,都会顿时觉得和作者亲近很多。就像以前念书的时候,老师说的读一篇巨著能感受大师的思考,就像在和大师跨时空对话一样。

总体来说,感觉很好。包括sublime很轻便,代码补全提示都很不错,用着很舒服,写代码效率很高。另外,API可以联网看,居然还可以在自己机器上起个godocserver,而且自己gopath里面的代码和注释都会被load进来,虽然想想实现也不难,但真的是非常酷。访问package,非常方便。

当然啰嗦这么多,也只是一个新手个人零碎的一点体会。流水账一样的列举了一些体会,其中观点个人背景和个人主观色彩非常明显。不是一个面向主题的有深度的“深入论述java和go若干特性。。。”之类的专题文档。很多还是停留在体会,如go的interface,好像能感觉到其设计,想描述,但又说不完整,原因还是实践不够,体会不深,理解可能会有偏差。