一点个人的编程语言学习总结

笔者近期花了点时间来 “拓宽了自己的语言池”。有些是出于功利角度 ( 如 Java 的 Web 开发,Scala 的大数据框架 ),有些则完全出于兴趣 ( 如 Groovy,Go ),在学习不同语言的过程中多少了解到了不同的编程思路和风格。不过最近同时学习的东西难免太多了,导致笔者在不断切换语言的过程中思路发生了一点混乱。这里通过一些小短文总结笔者所学习的 Java,Scala,Groovy,Go 在语言特性的一些异同,以便于日后能在需要的场合快速回忆起如何操纵它们。

如果要快速上手一门新语言,笔者率先会以下角度入手:数据类型,函数定义,依赖导入。数据结构永远是通用的:比如数组,列表,映射,还有一些日常开发中的常用操作,HTTP,数据库连接,Socket, IO 流。笔者认为,了解完这些,基本能够去逐步去上手一些开发任务了 ( 深入挖掘语言特性的除外 ),剩下的要做的就是通过合适的开发框架来解决需求。

1. 梗概

Java:语法比较固定,开发时无需纠结语法,团队开发时沟通成本和开发成本都很低。受众最多,有任何问题都能在网上查到。

Scala:大量隐式转换,原生支持函数柯里化,高阶类型,类型型变,有非常灵活的操作符重载机制,以及随处可见的 _ 符号 ( 这个符号让 Scala 程序员升华到了 “意识流编程” 的境界 )。无拘无束的语法导致不同开发者的 Scala 代码风格迥异,不太适合在大型团队协作时使用。此外,IDE 对语言本身的支持性一般 ( 靠安装插件凑合过日子 ),囿于复杂的编译机制,有些错误提示晦涩难懂。

Groovy:动态类型判断,元编程,方法运行时注入,为 JDK 包了不少 “糖衣”,适合去做一些基于 JVM 的自动化维护。然而,高甜度和高动态性的代价是牺牲运行性能,和 Scala 一样没有 “趁手” 的 IDE ( 也是靠安装插件 ),偶尔会有一些来自 IDE 的 Bug。

Go:提供基于 CSP 理论的通信编程 goroutine & chan,天生具备异步编程的能力。支持短变量命名,不允许出现未使用的库引用或者变量;有指针的概念,但是也具备 GC 机制;跨平台肯定不如 JVM 语言,至少需要把源代码拿到其它平台重新 build go 一下。有专门的 IDE GoLand,代码写起来很舒服。

2. 编程范式

Java:仍旧以 OOP 为主,FP 体验一般 ( 甚至很差 ),因为 Java 会强迫我去记忆各种函数式接口的名字。

Scala,Groovy:支持 FP 和 OOP 两种编程风格,Scala 对函数类型有非常直观的符号表达,Groovy 则将一切闭包都视作 Closure<ReturnType>

Go:函数即一切。Go 抛弃了固有的 OOP 模式,因此也不存在继承,多态 的概念 ( 但不意味着 Go 就不 OOP 了 )。Go 更鼓励以组合的形式实现代码复用。

3. 关于静态

这四个语言中,只有 Java,Groovy 有 “静态” static 的设定。

Scala 认为这违背了 OOP,因此 “静态” 的概念被伴生对象 object 替换掉了;Go 则认为 “静态” 的内容完全可以使用纯函数或者是包变量代替,所以它也没有 static 的概念。

4. 访问权限控制

Java:publicprotected ( 包和子类可见 ),缺省 ( 仅本包可见 ),private

Scala:缺省即表示 public,其次是 protected ( 仅对子类可见 ),private。额外提供包变量使包内成员共享数据。

Groovy:缺省即表示 public ,其次是 protected ( 包和子类可见 ) 和 private。实际上,编译器对跨权限访问无能为力 ( 仅仅给出警告 ),因为 Groovy 有的是办法 “合法地” 访问它。

Go:不需要任何关键字,仅通过变量 / 函数首字母的大小写来决定它们能否导出。

5. 数据结构的划分

Groovy 基本遵循 Java 传统的 8 个基本数据类型:byteshortintlongfloatdoublecharboolean

Scala 在 Java “老八珍” 的基础之上做了封装,每种类型都有 toXXX方法供强制转换;具备顶类型 Any 以及底类型 Nothing;Scala 非常在意一个变量应当是不可变的 val 还是可变的 var

Go 语言则主要是根据长度对数据进行划分:int8 ~ int64 ( 包括无符号的 uint8 ~ uint64 ),float32float64。有一种特殊的类型:无符号类型,用于维持常量的高精度。

6. 对字符,字符串的处理

Java 的 char 表示一个 “字符”,在内部对 Unicode 字符集进行 utf-16 编码,固定为 2 个 byte ( 极少部分字符可能需要两个 char 存储 )。当数据被序列化到外部时,采用 utf-8 编码方式,此时一个字符长度从 1 ~ 3 byte 不等。JVM 的字符串由 java.lang.String 类实现,内置了各种字符串操作方法。

在此基础上, Groovy 将所有的字符,字符串全部包装成了 String 或者 GString。

Go 语言有原生的 string 类型,编码方式统一采用 utf-8 ,而截取 string 的单个元素却只能得到 byte。Go 语言的单个字符使用 rune 来表示,一些涉及字符的操作要首先将 string 强制转换到 []rune ( 这样来看,Go 的 []rune 更像是 Java 中的 String ) ,比如 “查字数” 需要调用 utf8.RuneCount() 函数。字符串处理一般要依赖 stringsstrconvunicode 包。

7. 类型与类型推导

Java:JDK 11 开始可以使用 var 令编译器进行局部变量的类型推导。支持泛型,支持泛型上下界,但不支持泛型型变 ( 数组支持协变 )。不支持高阶类型。

Groovy:与其说是类型推导,不如说将类型验证延迟到了运行时。

Scala:原生支持类型推导,支持泛型,支持型变的概念,除此之外还有上下文界定和视图界定。支持高阶类型,可通过反射时获取。

Go:原生支持类型推导,目前不支持泛型 ( 以后应该也没有 )。

8. 对接口的定义

Java:在 JDK 8 之后,支持设置默认方法,静态方法,接口常量;在 JDK 9 之后,支持设置私有方法。

Groovy:允许使用动态地将多个闭包组合成一个 Map,并将其声明为一个接口的实现。

Scala:允许在创建对象时动态混入并叠加多个特质,以及专门服务于某个类的自身特质。

Go:无需主动声明实现了接口,只要一个类型 struct 实现了接口的所有方法,那它就可以被当作这个接口去使用 ( 笔者最喜欢的一种方式 )。

9. 关于函数定义

Java:平平无奇。

Scala:原生支持柯里化,分为传名调用和传值调用。

Groovy:函数被称作闭包,其它 FP 应有的特性都具备。

Go:支持返回多个值,支持 defer 调用,可以很方便地通过 Execute Around Method ( 类似于 AOP ) 模式关闭资源。

10. 循环结构

Groovy:Java 的语法在 Groovy 同样适用,额外支持 for(... in ..) {} 实现遍历 ( 像 Python 那样 )。

Scala:for 表达式的信息熵非常大,能够表达 mapflatMapforeachwithFilter 等语义,反过来说,这些语义的组合可以使用 for 表达式简化。推崇递归解决问题,编译器可将尾递归函数转换为等效的迭代代码。

Go:只使用 for 表达式,可以配合 range 关键字实现对序列,映射的遍历。

11. 异常处理

Java:受检异常必须 try-catch,或者 throws 抛给上级。

Scala,Groovy:不要求主动处理 Java 中的受检异常 ( 大概都觉得 Java 的这个东西太毒瘤了 )。

Go:除了严重的问题使用 panic 宕机之外,一般异常 ( error 接口 ) 都以返回值的形式传递给外部。

12. 有关于数组

在 Go 语言中,数组的长度属于数组类型的一部分,拥有独特的切片类型。其它三种大同小异。

13. 有关于常用集合

Java:常用的是 ListMap 两种接口 ( Set 表示数学意义上的集 )。每个接口下有多种实现,可以根据实际情况 ( 随机访问多?还是插入操作多?插入键值对时有必要记录插入次序吗?) 来选择合适的类型。对它们进行 mapfilter 等变换操作时需要切换到 Stream。

Groovy:沿用了 Java 的 ArrayListLinkedHashMap,支持直接对集合做变换操作。

Scala:主要是强调可变和不可变两种类型,支持直接对集合做 ( 很多复杂的 ) 变换操作。

Go:通过 make(map[key]value) 创建映射,通过 list.new() 创建列表,没有直接地提供集合变换操作。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享