Kotlin 的 val list: ArrayList<String>= ArrayList() 居然报错!

2020-02-20 13:25:14 浏览数 (1)

1 语法错误?

也许看了我们的题目,大家还没有明白过来到底发生了什么,那么我请大家再仔细看看:

代码语言:javascript复制
val list: ArrayList<String>= ArrayList()
                          ^

什么地方报错呢?就是泛型参数后面的 > 处。

这就让人不理解了,看上去并没有什么问题啊。我们再来看看错误提示:

嗯?说 > 那个地方缺个 > ? 什么鬼。。好吧,我大概猜到原因了,泛型参数后半个 > 估计与后面赋值用的 = 连起来被识别成了 >=,于是乎。。。

我们打开 PsiViewer(IntelliJ 插件),将光标放到报错的位置,嗯,这货果然被识别成了 >= (GTEQ)。

2 分析 Kotlin 的解析过程

这么说来就比较有意思了,Kotlin 的解析器并不会因为前面有泛型而把后面的 >= 识别成 > =难道是说在解析的过程中,先通过词法分析器把一个个字符识别成一个个 TOKEN,然后再用语法分析器根据这些 TOKEN 去解析识别语法的?

想要验证这个其实并不难,我们找到 Kotlin 的源码,找到 lexer 相关的源码:

在 Kotlin.flex 这个文件中发现:

代码语言:javascript复制
">="         { return KtTokens.GTEQ      ; }

不惊讶吧。实际上编译之后 Kotlin.flex 会生成 _JetLexer.java 这个文件,KotlinLexer 这个类是词法分析器的入口,我们在解析处打个断点:

注意 FlexAdapter 实际上是 KotlinLexer 的父类。

调试运行编译器,我比较喜欢的方式是编译一段脚本:

脚本里面就只有我们最开头的那句报错的代码,那么结果会怎样呢?

我们看下调用堆栈,解析器被调用的地方实际上是 PsiElement 构造的过程中。我们再来看看 doParseContents 这个方法是干什么的:

一旦 PsiElement 构造完成,那么词法分析实际上就结束了,后面的 KotlinParser 就是语法分析了。这样看起来似乎跟我们前面猜测的没有什么两样。

显然在语法分析之前,这俩符号已经被强制结合成 >=,后面也并没有做重新解析的操作。

3 对比看看其他语言

能够出现类似语法的,我们可以对照一下 Swfit 和 Scala。

先说说 Scala,它的解析器应该是极其强大的,毕竟人家允许各种字符作为运算符啊。。

嗯,毕竟人家的泛型参数机智地选择了 [] 而不是 <> ,OK,你赢了。。

那么我们再来看看 Swift 吧,这个比较有说服力:

代码语言:javascript复制
let ints: Array<Int>= [1, 2, 4]
ints.forEach{item in print(item)}

瞧见没,人家就能识别出来!当然这里有个奇怪的地方,如果你用 JetBrains 家的 AppCode 运行这段代码,结果就会报错,额,这也许是一个悲伤的故事。。

4 一些思考

实际上通过前面的讨论,我们就知道为什么 Kotlin 的移位居然不用 >><<,而是 shrshr 这样的中缀表达式,毕竟人家没办法识别呀。。

好啦,其实这都不是什么大问题了,这篇文章探讨的那句代码本身就比较蛋疼:

代码语言:javascript复制
val list: ArrayList<String>= ArrayList()

我就问你为什么不去掉前面的类型,类型推导难道还不够吗?

代码语言:javascript复制
val list = ArrayList<String>()

或者在 >= 中间打一个空格嘛,这样就啥事儿没有了。

而说到移位用中缀表达式的问题,我们群里有位大佬就终于忍不住为 Kotlin 发声了:

用中缀这种方法未尝不好啊。因为其实位移这种根本没什么人用的,直接换成一个方法其实在概念理解上简单多了。 还有我觉得Scala 用[ ]来表示泛型比<>好多了我以前看一个人说后者写起来感觉很反人类或者看上去很奇怪,我很赞同。不过我觉得>=编译器没解析好不是什么大问题,很多语言都有类似的缺陷,但是这从来不是一个问题。

0 人点赞