这一章节将会为你介绍每一个Kotlin程序的基本组成元素:函数和变量。你将会看到Kotlin是如何让你省略许多的类型声明以及如果鼓励你使用不可变的,而不是可变数据的。

2.1.1 Hello, World!

让我们以一个经典的例子开始吧:一个打印“Hello,world”的程序。在Kotlin中,它只是一个函数:

fun main(args: Array<String>) {
    println("Hello, world!")
}

你在这个简单的代码块中观察到什么样的特性和语法呢?核对下面这个列表:

  • fun关键词被用来声明一个函数。事实上,用Kotlin来编程是非常有趣的!
  • 参数类型写在参数名后面。正如你在后面看到的那样,这对于变量什么也是适用的。
  • 函数可以声明在文件的顶层。你不需要把它放入一个类中。
  • 数组只是一个类。不同于Java,Kotlin没有特殊的语法来声明数组类型。
  • 你可以写成println而不是System.out.println。Kotlin标准库使用更加精简的语法来为标准的Java库函数提供了众多的包装器。println函数就是其中之一。
  • 你可以省略行末的分号,就像许多其他的现代语言那样。

到目前为止,一切看起来都很好!后续我们将会更详细的讨论其中的一些话题。现在,让我们一起来深入探讨函数声明语法吧!

2.1.2 函数

你已经看到了如何声明一个没有任何返回值的函数。但是你应该在何处为拥有有意义的结果的函数添置一个返回类型呢?你可以猜到它应该在参数列表后面的某个位置:

fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}

>>> println(max(1, 2))
2

在这个案例中,函数声明以fun关键字为开始,接着是函数名:max。接着是圆括号中的参数列表。返回类型跟在参数列表后面,以冒号分隔。  图2.1向你展示了函数的基本结构。注意,在Kotlin中,if是一个带有结果值的表达式。这跟Java中的三元操作符很相似:(a > b) ? a: b图2.1
 图2.1 Kotlin函数声明

NOTE  声明和表达式
 在Kotlin中,if 是一个表达式,并不是一个声明。两者的区别在于,表达式有值。它可以用作另一个表达式的一部分。然而,一个声明却总是闭合块中的一个顶层元素,而没有自己的值。在Java中,所有的控制结构都属于声明(statement)。而在Kotlin中,循环以外的大多数控制结构都是表达式。正如你在书中后续将会看到的那样,将控制结构和其他表达式结合起来的能力让你更精简地表达许多常见的模式。  另一方面,赋值在Java中是表达式,但在Kotlin中却是声明。这有助于避免比较和赋值之间的困惑。而这种困惑是错误的常见源头。

表达式主体

你可以进一步简化之前的函数。因为它的内容部分仅仅是有一个表达式组成的,你可以移除大括号和return声明,使用表达式作为整个函数的主体。

fun max(a: Int, b: Int): Int = if (a > b) a else b

如果一个函数拥有大括号,我们说这个函数有一个块主体。如果它直接返回一个表达式,我们说它有一个表达式主体。

TIP  IntelliJ IDEA Tip
 IntelliJ IDEA 提供了意图动作来转换两种类型的函数:"转换为表达式主体"和"转换为块主体"

通常有表达式主体的函数能够在Kotlin代码中快速的找出来。这种风格不仅仅用在琐碎的单行函数,也用在一个单独的、更复杂的求值表达式中,例如if, when,或者try。当我们讨论when构造时,你可以在这个章节的后续部分看到这样的函数。
你可以更加简化max函数并忽略返回类型:

fun max(a: Int, b: Int) = if (a > b) a else b

为什么这里的函数没有返回类型声明呢?Kotlin,作为一个静态类型的语言,不需要每个表达式在编译时都有一个类型吗?事实上,每一个变量和表达式都有一个类型。每一个函数都有一个返回类型。但是对于表达式主体函数来说,编译器能够分析用做主函数主体的表达式,并使用表达式的类型作为函数返回类型,即使没有拼写出来。这种类型的分析通常叫做类型推断。
 注意,只有表达式函数才允许忽略返回值。对于有一个返回值的块主体的函数来说,你必须制定返回类型并且显示的写上return声明。这是一个明智的选择。一个真实世界的函数经常是很长的。它可能包括多个返回值。有一个显示的返回类型和返回声明有助于你快速的理解函数将会返回什么内容。接下来让我们来看看变量声明的语法。

2.1.3 变量

在Java中,你是以一个类型来开始变量声明的。在Kotlin中,这是无效的,因为Kotlin让你从许多的变量声明中忽略类型。因此,在Kotlin中,你是以一个关键词开始的。你可能(也许不会)在变量名后面放置类型。让我们来声明两个变量:

val question = "The Ultimate question of Life, the Universe, and Everything"
val answer = 42

这个例子忽略了类型声明,但是你也可以按你想的那样显式的声明指定类型:

val answer: Int = 42

正如有着表达式主体的函数,如果你不指定类型,编译器分析初始化器表达式并使用它的类型作为变量类型。在这个案例中,初始化器,42,有一个Int类型。因此变量将会有同样的类型。
如果你使用一个浮点类型的常量,变量将会是Double类型:

val yearsTocompute = 7.5e6        // 7.5 * 106 = 7500000.0

数字类型在 XREF ID_主要类型一节中深入探讨。
如果一个变量没有一个初始化器,你需要显式的指定它的类型:

val answer: Int
answer = 42

如果你没有给出能够推断出这个变量的类型的值,编译器无法推断出它的类型。

可变的和不可变的变量

这里有两个关键词来声明一个变量:

  • val(从一个值) 不可变的引用。用val声明一个变量不能在初始化后重新分配值。它对应于Java中的final变量。
  • var(从一个变量) 可变的引用。它的值是可以改变的,比如变量。这个声明对应于Java中的常规(非final)变量。

默认的,在Kotlin中,你应该尽量使用val声明所有的变量。仅在你必要的时候将val改为var。使用不可变引用、不可变对象和函数没有副作用,这让你的代码更加接近函数式风格。我已经在第一章简要的接触了它的好处。我们也将在第五章回到这个主题。
 一个val变量必须在定义块执行时被初始化而且只能一次。但是如果编译器能够确保唯一的初始化声明能够其中一个被执行,你可以根据情况用不同的值初始化变量:

val message: String
if ( canPerformOperation()) {
    message = "Success"
    // ... perform the operation
}
else {
    message = "Failed"
}

注意,尽管一个val引用本身是不可变的,也不可被改变。但是它指向的对象可能是可变的。例如,下面的代码是完全有效的:

val languages = arraylistOf("Java")   // 1 声明一个不可变的引用
languages.add("Kotlin")               // 2 引用指向可变的对象

在书中后续部分,第六章,我们将会更详细的讨论可变和不可变对象。
尽管var关键字允许一个变量改变自己的值,但是他的类型是固定的。例如,下面的代码不能成功编译:

var answer = 42
answer = "no answer" // 错误:类型匹配错误

这是一个字面量错误。因为它的类型(String)不是预期的(Int)。编译器仅仅从初始化器推断变量类型。在决定变量类型时,编译器并没有把考虑后面的赋值。
 如果你需要把值存储到一个类型不匹配的变量中,你需要手动转换强制把值变为正确的类型。我们将会在XREF ID_数值转换章节讨论原始类型转换。
现在,你已经知道如何定义一个变量了。是时候看看推断变量值的新的技巧了。

2.1.4 更简单的字符串格式:字符串模板

让我们回到开篇的“Hello World”的例子。下面是典型习题的下一步已经用Kotlin的方式来通过人名来问候:

fun main(args: Array<String>) {
    val name = if (args.size > 0) args[0] else "Kotlin"
    println("Hello, $name!")   // 打印"Hello, Kotlin",或者"Hello, Bob"如果你传递"Bob"作为一个声明
}

这个示例介绍了叫做字符串模板的特性。在代码中,你声明了一个name变量,然后在接下来的字符串值中使用了它。向很多脚本语言一样,Kotlin允许你在字符串值中通过在变量名前面放置$字符来引用局部变量。这相当于Java的字符串串拼接("Hello, " + name + "!"),但是更加紧凑和高效。当然,表达式是静态检查的。如果你尝试着去应用一个不存在的变量,代码不会通过编译。
 如果你需要在一个字符串中包含$符号,你可以使用转义功能:println("\$x"),打印$x但不会将x解析为变量引用。
你不会被局限于简单的变量名。你也可以使用更复杂的表达式。表达式所有的内容将放在大括号内:


fun main(args: Array<String>) {
    if (args.size > 0) {
        println("Hello, ${args[0]}!") // 使用`${}`语法来插入args数组的第一个元素
    }
}

你也可以在双引号内部嵌套双引号,就像在一个表达式内部一样:


fun main(args: Array<String>) {
    println("Hello, ${if (args.size > 0) args[0] else "someone"}!")
}

在使用字符串一节,我们将会回到字符串这个主题并讨论更多你能用它来做什么。
 现在你知道如何声明函数和变量了。让我们走进继承层级并看看类这个主题。这次,你将会使用Java到Kotlin转换器来帮助你开始使用新语言的特性。

results matching ""

    No results matching ""