fun inc(num : Int) {
val num = 2
if (num > 0) {
val num = 3
}
println ("num: " + num)
}
当你调用 inc(1) 会输出什么呢?在 Kotlin 中, 方法的参数无法修改,因此在本例中你不能改变 num。这个设计很好,因为你不应该改变方法的输入参数。但是你可以用相同的名称定义另一个变量并对其进行初始化。这样一来,这个方法作用域中就有两个名为 num 的变量。当然,你一次只能访问其中一个 num,但是 num 值会被改变。在 if 语句中再添加另一个 num,因为作用域的原因 num 并不会被修改。于是,在 Kotlin 中,inc(1) 会输出 2。同样效果的 Java 代码如下所示,不过无法通过编译:
void inc(int num) {
int num = 2; //error: variable ‘num’ is already defined in the scope
if (num > 0) {
int num = 3; //error: variable ‘num’ is already defined in the scope
}
System.out.println ("num: " + num);
}
名字遮蔽并不是 Kotlin 发明的,这在编程语言中很常见。在 Java 中我们习惯用方法参数来映射类字段:
public class Shadow {
int val;
public Shadow(int val) {
this.val = val;
}
}
在 Kotlin 中名称遮蔽有些严重,这是 Kotlin 团队的一个设计缺陷。IDEA 团队试图通过向每个遮蔽变量显示警告信息来解决这个问题。两个团队在同一家公司工作,或许他们可以互相交流并就遮蔽问题达成共识。我从个人角度赞成 IDEA 的做法因为我想不到有哪些应用场景需要遮蔽方法参数。类型推断在Kotlin中,当你声明一个var或是val,你通常会让编译器从右边的表达式类型中猜测变量类型。我们称之为局部变量类型推断,这对程序员来说是一个很大的改进。它允许我们在不影响静态类型检查的情况下简化代码。例如,这个Kotlin代码:
var a = “10”
Kotlin 编译器会将其翻译成:
var a : String = “10”
Java 同样具备这个特性,Java 10中的类型推断示例如下:
var a = “10”;
实话实说,Kotlin 在这一点上确实更胜一筹。当然,类型推断还可应用在多个场景。关于 Java 10中的局部变量类型推断,点击以下链接了解更多:
- [https://medium.com/@afinlay/java-10-sneak-peek-local-variable-type-inference-var-3022016e1a2b](
)
Null 安全类型
Null 安全类型是 Kotlin 的杀手级功能。这个想法很好,在 Kotlin 中,类型默认不可为空。如果你需要添加一个可为空的类型,可以像下列代码这样:
val a: String? = null // ok
val b: String = null // compilation error
假设你使用了可为空的变量但是并未进行空值检查,这在 Kotlin 将无法通过编译,比如:
println (a.length) // compilation error
println (a?.length) // fine, prints null
println (a?.length ?: 0) // fine, prints 0
那么是不是如果你同时拥有不可为空和可为空的变量,就可以避免 Java 中最常见的 NullPointerException 异常吗?事实并没有想象的简单。当 Kotlin 代码必须调用 Java 代码时,事情会变得很糟糕,比如库是用 Java 编写的,我相信这种情况很常见。于是第三种类型产生了,它被称为平台类型。Kotlin 无
【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
法表示这种奇怪的类型,它只能从 Java 类型推断出来。它可能会误导你,因为它对空值很宽松,并且会禁用 Kotlin 的 NULL 安全机制。看看下面这个 Java 方法:
public class Utils {
static String format(String text) {
return text.isEmpty() ? null : text;
}
}
假如你想调用 format(String)。应该使用哪种类型来获得这个 Java 方法的结果呢?你有三个选择。第一种方法:你可以使用 String,代码看起来很安全,但是会抛出 NullPointerException 异常。
fun doSth(text: String) {
val f: String = Utils.format(text) // compiles but assignment can throw NPE at runtime
println ("f.len : " + f.length)
}
那你就需要用 Elvis 来解决这个问题:
fun doSth(text: String) {
val f: String = Utils.format(text) ?: “” // safe with Elvis
println ("f.len : " + f.length)
}
第二种方法:你可以使用 String,能够保证 Null 安全性。
fun doSth(text: String) {
val f: String? = Utils.format(text) // safe
println ("f.len : " + f.length) // compilation error, fine
println ("f.len : " + f?.length) // null-safe with ? operator
}
第三种方法:让 Kotlin 做局部变量类型推断如何?
fun doSth(text: String) {
val f = Utils.format(text) // f type inferred as String!
println ("f.len : " + f.length) // compiles but can throw NPE at runtime
}
馊主意!这个 Kotlin 代码看起来很安全、可编译,但是它容忍了空值,就像在 Java 中一样。除此之外,还有另外一个方法,就是强制将 f 类型推断为 String:
fun doSth(text: String) {
val f = Utils.format(text)!! // throws NPE when format() returns null
println ("f.len : " + f.length)
}
在我看来,Kotlin 的所有这些类似 scala 的类型系统过于复杂。Java 互操作性似乎损害了 Kotlin 类型推断这个重量级功能。类名称字面常量使用类似 Log4j 或者 Gson 的 Java 库时,类文字很常见。Java 使用 .class 后缀编写类名:
Gson gson = new GsonBuilder().registerTypeAdapter(LocalDate.class, new LocalDateAdapter()).create();
Groovy 把类进行了进一步的简化。你可以忽略 .class,它是 Groovy 或者 Java 类并不重要。
def gson = new GsonBuilder().registerTypeAdapter(LocalDate, new LocalDateAdapter()).create()
Kotlin 把 Kotlin 类和 Java 类进行了区分,并为其提供了语法规范:
val kotlinClass : KClass = LocalDate::class
val javaClass : Class = LocalDate::class.java
因此在 Kotlin 中,你必须写成如下形式:
val gson = GsonBuilder().registerTypeAdapter(LocalDate::class.java, LocalDateAdapter()).create()
这看起来非常丑陋。反向类型声明C 系列的编程语言有标准的声明类型的方法。简而言之,首先指定一个类型,然后是该符合类型的东西,比如变量、字段、方法等等。Java 中的表示方法是:
int inc(int i) {
return i + 1;
}
Kotlin 中则是:
fun inc(i: Int): Int {
return i + 1
}
这种方法有几个原因令人讨厌。首先,你需要在名称和类型之间加入这个多余的冒号。这个额外角色的目的是什么?为什么名称与其类型要分离?我不知道。可悲的是,这让你在 Kotlin 的工作变得更加困难。第二个问题,当你读取一个方法声明时,你首先看到的是名字和返回类型,然后才是参数。在 Kotlin 中,方法的返回类型可能远在行尾,所以需要浏览很多代码才能看到:
private fun getMetricValue(kafkaTemplate : KafkaTemplate<String, ByteArray>, metricName : String) : Double {
…
}
或者,如果参数是逐行格式的,则需要搜索。那么我们需要多少时间才能找到此方法的返回类型呢?
@Bean
fun kafkaTemplate(
@Value("${interactions.kafka.bootstrap-servers-dc1}") bootstrapServersDc1: String,
@Value("${interactions.kafka.bootstrap-servers-dc2}") bootstrapServersDc2: String,
cloudMetadata: CloudMetadata,
@Value("${interactions.kafka.batch-size}") batchSize: Int,
@Value("${interactions.kafka.linger-ms}") lingerMs: Int,
metricRegistry : MetricRegistry
): KafkaTemplate<String, ByteArray> {
val bootstrapServer = if (cloudMetadata.datacenter == “dc1”) {
bootstrapServersDc1
}
…
}
第三个问题是 IDE 中的自动化支持不够好。标准做法从类型名称开始,并且很容易找到类型。一旦选择一个类型,IDE 会提供一些关于变量名的建议,这些变量名是从选定的类型派生的,因此你可以快速输入这样的变量:
MongoExperimentsRepository repository
Kotlin 尽管有 IntelliJ 这样强大的 IDE,输入变量仍然是很难的。如果你有多个存储库,在列表中很难实现正确的自动补全,这意味着你不得不手动输入完整的变量名称。
repository : MongoExperimentsRepository
伴生对象一位 Java 程序员来到 Kotlin 面前。
“嗨,Kotlin。我是新来的,我可以使用静态成员吗?"他问。 “不行。我是面向对象的,静态成员不是面向对象的。” Kotlin 回答。 “好吧,但我需要 MyClass 的 logger,我该怎么办?” “这个没问题,使用伴生对象即可。” “那是什么东西?” “这是局限到你的类的单独对象。把你的 logger 放在伴生对象中。”Kotlin解释说。 “我懂了。这样对吗?”
class MyClass {
companion object {
val logger = LoggerFactory.getLogger(MyClass::class.java)
}
}
“正确!” “很详细的语法,”程序员看起来很疑惑,“但是没关系,现在我可以像 MyClass.logger 这样调用我的 logger,就像 Java 中的一个静态成员?” “嗯…是的,但它不是静态成员!这里只有对象。把它看作是已经实例化为单例的匿名内部类。事实上,这个类并不是匿名的,它的名字是 Companion,但你可以省略这个名字。看到了吗?这很简单。"
我很欣赏对象声明的概念——单例很有用。但从语言中删除静态成员是不切实际的。在 Java 中我们使用静态 Logger 很经典,它只是一个 Logger,所以我们不关心面向对象的纯度。它能够工作,从来没有任何坏处。因为有时候你必须使用静态。旧版本 public static void main() 仍然是启动 Java 应用程序的唯一方式。
class AppRunner {
companion object {
@JvmStatic fun main(args: Array) {
SpringApplication.run(AppRunner::class.java, *args)
}
}
}
集合字面量在Java中,初始化列表非常繁琐:
import java.util.Arrays;
…
List strings = Arrays.asList(“Saab”, “Volvo”);
初始化地图非常冗长,很多人使用 Guava:
import com.google.common.collect.ImmutableMap;
…
Map<String, String> string = ImmutableMap.of(“firstName”, “John”, “lastName”, “Doe”);
在 Java 中,我们仍然在等待新的语法来表达集合和映射。语法在许多语言中非常自然和方便。
JavaScript:
const list = [‘Saab’, ‘Volvo’]
const map = {‘firstName’: ‘John’, ‘lastName’ : ‘Doe’}