由于这篇博文引起了高度关注和争议,我们认为有必要添加一些关于我们如何在Allegro工作和做出决策的背景知识。Allegro拥有50多个开发团队,可以自由选择我们PaaS支持的技术。我们主要使用Java、Kotlin、Python和Golang编写代码。本文中提出的观点来自作者的经验。Kotlin很流行,Kotlin很时髦。Kotlin为您提供编译时空值安全和更少的样板代码。当然,它比Java好。你应该切换到Kotlin或作为一个老程序员死去。等等,或者你不应该?在开始使用Kotlin编写代码之前阅读项目的故事。关于技巧和障碍的故事变得如此烦人,以至于我们决定重写它。我们尝试过Kotlin,但现在我们正在用Java10重写我有我最喜欢的一组JVM语言。Java的/main和Groovy的/test对我来说是一个很好的组合。2017年夏天,我的团队启动了一个新的微服务项目,我们照例聊语言和技术。Allegro有几个支持Kotlin的团队,我们想尝试一些新的东西,所以我们决定尝试一下Kotlin。由于在Kotlin中没有Spock的替代品,我们决定继续在/test中使用Groovy(Spek不如Spock)。在2018年冬天每天与Kotlin相处几个月后,我们总结了利弊,得出的结论是Kotlin让我们的工作效率降低了。我们开始用Java重写这个微服务。这有几个原因:名称阴影类型推断编译时空指针安全类文字反向类型声明伴随对象集合文字也许吧?没有数据类公共类陡峭的学习曲线名字阴影这是Kotlin最让我惊讶的地方。看看这个函数:funinc(num:Int){valnum=2if(num>0){valnum=3}println("num:"+num)}当你调用inc(1)时它输出什么?在Kotlin的Method参数是一个值,所以你不能改变num参数。这是很好的语言设计,因为你不应该改变方法的参数。但是您可以定义另一个具有相同名称的变量并按照您想要的方式对其进行初始化。现在,在这个方法级范围内,您有两个名为num的变量。当然一次只能访问num中的一个,所以num的值会发生变化。一般,没有解决办法。在if的主体中,您可以添加另一个num,这并不令人震惊(新的块级范围)。好的,在Kotlin中,inc(1)输出2。但是在Java中,等效代码将无法编译。voidinc(intnum){intnum=2;//error:variable'num'isalreadydefinedinthescopeif(num>0){intnum=3;//error:variable'num'isalreadydefinedinthescope}System.out.println("num:"+num);}名称隐藏不是Kotlin发明的。这在编程语言中很常见。在Java中,我们习惯于使用方法参数隐藏类中的字段。publicclassShadow{intval;publicShadow(intval){this.val=val;}}阴影在Kotlin中有点矫枉过正。当然,这是Kotlin团队的设计缺陷。IDEA团队试图通过向您显示每个阴影变量并附上简洁的警告来解决此问题:Nameshadowed。两个团队都在同一家公司工作,所以也许他们可以互相交谈并就跟踪达成一致?我觉得——IDEA是对的。我无法想象隐藏方法参数的有效用例。类型推断在Kotlin中,当你声明一个var或val时,通常会让编译器根据右边表达式的类型来猜测变量类型。我们称之为局部变量类型推断,这对程序员来说是一个很大的进步。它允许我们在不影响静态类型检查的情况下简化代码。例如,这段Kotlin代码:vara="10"将被Kotlin编译器翻译成:vara:String="10",这在过去是相对于Java的真正优势。我是故意说是的,因为-好消息-Java10已经具有此功能,并且Java10现在可用。Java10中的类型涂层标头:vara="10";公平地说,我需要补充一点,Kotlin在这方面仍然略胜一筹。您还可以在其他上下文中使用类型推断,例如,单行方法。有关Java10中的局部变量类型推断的更多信息。编译时Null安全Null安全类型是Kotlin的杀手级功能。是个好主意。在Kotlin中,默认情况下类型是不可空的。如果需要可空类型,则需要添加?symbol,例如:vala:String?=null//okvalb:String=null//编译错误如果你使用一个可以为空的变量而没有进行空检查,那么Kotlin将无法编译,例如:println(a.length)//compilationerrorprintln(a?.length)//fine,printsnullprintln(a?.length?:0)//fine,prints0一旦你有了这两种类型,non-nullableT和nullableT?,你就可以忘记最常见的Java中的异常-NullPointerException。真的吗?不幸的是,事情并没有那么简单。当您的Kotlin代码必须与Java代码一起工作时,事情就变糟了(库是用Java编写的,所以我想这种情况经常发生)。然后,第三种类型跳了出来——T!叫做平台型,意思是T还是T?,准确点说是T!表示具有未定义空值的类型T。这种奇怪的类型无法在Kotlin中表示,只能从Java类型中推断出来。T!会误导你,因为它放宽了对null的限制并禁用了Kotlin的null安全限制。看看下面的Java方法:publicclassUtils{staticStringformat(Stringtext){returntext.isEmpty()?null:text;}}现在,您想从Kotlin调用format(string)。您应该使用哪种类型来使用此Java方法的结果?那么,你有三个选择。第一种方法。可以使用字符串,代码看起来很安全,但是会抛出空指针异常。fundoSth(text:String){valf:String=Utils.format(text)//compilesbutassignmentcanthrowNPEatruntimeprintln("f.len:"+f.length)}解决这个问题需要加判断:fundoSth(text:String){valf:String=Utils.format(text)?:""//println("f.len:"+f.length)}第二种方法。您可以使用String?,并且您的程序是null安全的。fundoSth(text:String){valf:String?=Utils.format(text)//safeprintln("f.len:"+f.length)//编译错误,fineprintln("f.len:"+f?.length)//null-safewith?operator}第三种方法。如果让Kotlin进行令人难以置信的局部变量类型推断会怎样?fundoSth(text:String){valf=Utils.format(text)//ftypeinferredasString!println("f.len:"+f.length)//compilesbutcanthrowNPEatruntime}坏主意。这段Kotlin代码看起来安全并且可以编译,但允许空值在您的代码中自由漫游,就像在Java中一样。还有一个技巧,!!操作员。使用这个强制推导f类型为String类型:fundoSth(text:String){valf=Utils.format(text)!!//throwsNPEwhenformat()returnsnullprintln("f.len:"+f.length)}中我的观点来吧,Kotlin类型系统中所有这些类似scala的东西!,?和!!,太复杂了。为什么Kotlin会从Java的T类型推断为T!而不是T??Java的互操作性似乎打破了Kotlin的杀手级特性——类型推断。看起来您应该显式声明类型(如T?)以满足由Java方法填充的所有Kotlin变量。类文字类文字在使用Log4j或Gson等Java库时很常见。在Java中,我们写的类名带有.class后缀:Gsongson=newGsonBuilder().registerTypeAdapter(LocalDate.class,newLocalDateAdapter()).create();在Groovy中,类文字被简化为必需品。您可以省略.class,它是Groovy类还是Java类都没有关系。defgson=newGsonBuilder().registerTypeAdapter(LocalDate,newLocalDateAdapter()).create()Kotlin区分了Kotlin类和Java类,并为它们准备了不同的语法形式:valkotlinClass:KClass
