kotlin 能在 JAVA 中取代 SQL 吗
Kotlin 在 Stream 的基础上有所改进,很多程序员尝试用 Kotlin 取代 SQL,但 Kotliln 同样是编译型语言,缺乏专业的数据对象,在结构化计算方面远不如 SQL 专业。SPL 有完善的结构化数据对象,且提供了不依赖于数据库的结构化数据计算能力,在结构化计算方面更加专业。点击kotlin 能在 JAVA 中取代 SQL 吗了解详情。
很多人都会遇到不方便使用数据库但又要结构化数据计算的情况。JAVA 8之前只能全都硬编码实现。JAVA8推出了惰性(Lazy Evaluation)的集合计算库Stream,虽然一定程度上缓解了这种状况,但仍然存在不少的缺点(详见《Stream能在Java中取代SQL吗》)。Kotlin是一门全兼容JAVA生态系统、并额外支持JavaScript的开发语言。Kotlin以Stream为基础并针对其缺点进行了改进,最重要的改进就是简化Lamda语法,其次增加了热情(Eager Evaluation,与惰性相对)的集合计算,并补充了很多集合函数。
Kotlin对Stream的改进是值得肯定的,但Kotlin仍然没有提供专业的结构化数据对象,本质不变,Kotlin就仍然无法代替SQL。事实上,Stream的很多缺点同样会在Kotlin上体现出来。我们把之前Stream贴子中的例子用Kotlin再实现一下,读者可以感受其中的异同。
集合的成员是简单数据类型时,Kotlin和Stream一样,都可以方便地实现集合计算,比如整数集合的过滤:
var numbers=listOf(3,11,21,27,9) |
排序:
var r2=numbers.sorted() |
比如交集:
var others=listOf(2,11,21) var result=numbers intersect others |
上面用到的都是热情集合List<T>,在输入输出、复用、类型转换时比惰性集合Stream<T>更方便,特别适合数据量较少且性能要求不高的场景,其中交集是Stream缺乏而Kotlin新增的集合函数(中缀形式)。类似集合计算还很多,代码通常简短易懂,与SQL有相通之处,比如去重distinct\求和sum \计数count等。
但数据对象不是简单数据类型,而是记录(通常是data class)时,和Stream类似, Kotlin就不那么方便了。比如对订单表的Client字段逆序排序,对Amount字段顺序排序:
//Orders是List<Order>类型,Order定义如下: //data class Order(var OrderID: Int,var Client: String,var SellerId: Int, var Amount: Double, var OrderDate: Date) var resutl=Orders.sortedBy{it.Amount}.sortedByDescending{it.Client} |
可能因为使用了Stream作为基础,Kotlin的排序字段也要前后颠倒,所用函数也和简单数据类型的排序函数不同,写法麻烦了许多。
再看分组,比如对订单表的SellerId分组,对Amount求和:
var result =Orders.groupingBy(Order::SellerId).fold(0.0){acc,elem->(acc+elem.Amount)} |
Kotlin提供了fold函数,用于封装Stream中collect + Collectors + summarizingDouble + DoubleSummaryStatistics等多个类和函数组合而成的代码片段,使整体结构变得清晰许多。
但无论怎么封装,分组汇总的结果是不变的,同样是Map类型,而不是常规的记录。Kotlin的数据类型不如SQL那样一致(源和结果都是记录),这在进一步计算时会遇到类型转换的麻烦。除了fold之外,Kotlin还提供了reduce或aggregate等函数实现汇总的效果,每种函数用法各不相同,适用于不同的细分场景,全部学会要花不少精力,远不如SQL方便。
多字段分组汇总,即按年份和Client分组,对Amount求和并计数:
data class Grp(var OrderYear:Int,var SellerId:Int) .toSortedMap(compareBy<Grp> { it. OrderYear}.thenBy {it. SellerId})
|
单字段分组汇总时,可以用Map里的key存储分组字段(value类似),多字段分组汇总就不能这么干了,因为key里不能放多个字段。这种情况下可以定义一个有结构的数据对象Grp,把多个分组字段拼进这个对象里,再用key来存储Grp。由于Kotlin是基于Stream的,所以同样不支持动态数据结构,必须先定义结果的数据结构再计算,SQL程序员很难适应这种死板的用法。相对的,SQL是解释型语言,动态数据结构是基本功能,不必事先定义数据结构。
上面的方法实现起来较简单,也可以麻烦一些,像Stream那篇帖子一样,把多个字段按分隔符拼成一个字段,转为单字段分组汇总,最后再按分隔符拆开。
分组汇总的最后还有个排序(不是必须的,主要为了和SQL的计算结果保持一致),可以发现Map和记录所用的排序函数不同。这些不一致的地方还有很多,导致Kotlin的学习成本相当高。
对于关联计算,比如对Orders表和Employee表进行内关联,然后对Employee.Dept进行分组,对Orders.Amount求和并计数。
// Employees是List<Employee>类型,Employee定义如下: // data class Employee(var EId:Int, var State:String, var Dept:String , var Name:String ,var Gender:String ,var Salary:Int,var Birthday:Date)
data class OrderNew(var OrderID:Int ,var Client:String, var SellerId:Employee ,var Amount:Double ,var OrderDate:Date) |
Kotlin和Stream一样不支持关联,只能硬编码实现,而且实现的思路一样,都是把Order的外键SellerId替换成Employee对应的记录。因为替换后数据结构发生变化(字段类型不同),而Kotlin和Stream又不支持动态数据结构,所以要定义一个新数据结构。此外,左关联和外关联的代码同样要硬编码,关键的代码逻辑还不一样,这类不一致的现象会对程序员造成许多麻烦。应该注意到,Kotlin关联后的分组汇总代码与直接分组汇总的代码不同,而Stream是相同的,这是因为Kotlin强制要求空安全(Null Safety),关联后需要做一些处理才能保证空安全,而Stream(本质是JAVA语言)没有这样的强制要求。
关联在结构化数据计算中很重要,Kotlin对关联计算的支持和Stream一样不好,在结构化数据计算方面很不专业,远不如SQL。
从这些例子可以看出来,Kotlin的确对Stream有所改进,代码长度也有所缩短。但也应当看到,Kotlin的很多缺点与Stream几乎一致。这是因为Kotlin和 Stream(JAVA)都是编译型语言,缺乏专业的结构化数据对象,无法支持动态数据结构,难以真正简化Lambda语法,无法直接引用字段,也就不能取代SQL(详见Stream的帖子)。
如果遇到不方便使用数据库但又要结构化数据计算的情况,目前看来还是集算器SPL最可靠。