AndroidStudio3 和 Kotlin 学习手册(一)
一、Kotlin 入门
我们将介绍的内容:
-
Kotlin 语简介
-
如何得到 Kotlin
-
在 macOS、Windows 和 Linux 上安装 Kotlin
-
在命令行中运行 Kotlin 程序
-
在 IntelliJ IDEA 中创建和运行项目
本章介绍了 Kotlin 语言,并详细介绍了如何建立一个开发环境。你可以找到如何在 macOS、Windows 和 Linux 上安装 Kotlin 的说明。您还可以找到关于如何使用简单的命令行安装 Kotlin 环境的说明。每个开发人员都被某种设置所吸引,您的开发人员也不例外。这是我在整本书中使用的设置:
-
IntelliJ 2018 运行在 macOS 上(高 Sierra)。我在第 1 到 7 章中一直使用这个
-
macOS 上的 Android Studio 3(高 Siera)。我在这本书的其余部分使用了这个
你不需要完全按照我的设置来。我们已经尽力确保本书中的说明在 Linux 和 Windows 中的效果和在 macOS 中一样好。还有,我说的 Linux,并不是指 Linux 的所有发行版。事实是,我只在 Lubuntu 17 中测试了这些代码。为什么呢?因为那是我最熟悉的 Linux 发行版。我相信本书的大多数读者(使用 Linux 的)也会熟悉这个 Linux 发行版(或者它的任何近亲)。
Android Studio 3 和 IntelliJ 可以在 Windows 7、8 和 10 (32 位和 64 位)上运行,但我只测试了 Windows 10 64 位上的练习——这是我唯一可以使用的机器;我相信大多数使用 Windows 的读者也使用这种设置。
最后,让我们讨论一下 JDK 版本。在撰写本文时,JDK 10 号正处于早期访问阶段。因此,JDK 版本的选择是 8 或 9(因为 JDK 7 在 2015 年的某个时候结束了它的生命)。我选择了 9 分——没有特别的原因,我认为 8 分也可以。
关于 Kotlin
Kotlin 是一种针对 Java 平台的新语言;它的程序运行在 JVM (Java 虚拟机)上,这使它与 Groovy、Scala、Jython 和 Clojure 等语言齐名。
Kotlin 来自 JetBrains,它是 IntelliJ、PyCharm、WebStorm、ReSharper 和其他优秀开发工具的创造者。2011 年,JetBrains 推出了 Kotlin 第二年,他们在 Apache 2 许可下开源了 Kotlin。在 Google I/O 2017 上,Google 宣布了在 Android 平台上对 Kotlin 的一流支持。如果你想知道 Kotlin 这个名字是从哪里来的,那是圣彼得堡附近的一个岛屿的名字,Kotlin 团队的大多数成员都在那里。据 JetBrains 的安德烈·布雷斯拉夫(Andrey Breslav)介绍,Kotlin 是以一个岛屿命名的,就像爪哇是以印度尼西亚的爪哇岛命名的一样。然而,您可能记得 Java 语言的历史中提到它是以咖啡而不是岛命名的。
Kotlin 作为一种语言有许多特征和能力,我们有这本书的整个第一部分来探索这些,但这里有一些事情使它变得有趣。
-
和 Java 一样,是面向对象的。所以,你在 Java 的 OOP 和设计模式上投入的所有时间都不会浪费。Kotlin 类、接口和泛型的外观和行为与 Java 非常相似。这绝对是一个优势,因为与其他 JVM 语言(例如 Scala)不同,Kotlin 看起来并不太陌生。它不会疏远 Java 程序员;相反,这让他们可以发挥自己的优势。
-
静态强类型。Kotlin 与 Java 共享的另一个领域是类型系统。它还使用静态和强类型。然而,与 Java 不同,在使用变量之前,不必总是声明变量的类型。Kotlin 使用式推理。
-
不如 Java 隆重。我们不必(总是)写一个类;顶级函数也可以。我们不需要为数据对象显式地编写 getters 和 settersKotlin 中有一些语言特性,这使得我们可以抛弃这种锅炉板代码。此外,用 Kotlin 编写代码的自然方式阻止我们将 null 赋给变量。如果你想显式地允许一个值为 null ,你必须以一种深思熟虑的方式去做。
-
这是一种 函数式语言。函数不仅仅是命名的语句集合;你可以在任何可能用到变量的地方使用它们。您可以将参数输入中的函数传递给其他函数,甚至可以从其他函数返回函数。这种编码方式允许不同的抽象方式。
-
与 Java 的互操作性。Kotlin 可以使用 Java 库,您也可以从 Java 程序中使用它。这降低了 Kotlin 的准入门槛;与 Java 的互操作性使得决定使用 Kotlin 启动一个新项目变得不那么困难。
在你的下一个项目中使用 Kotlin 有很多理由,但也有反对的理由。我们不会列出在你的下一个项目中为什么应该或者不应该使用 Kotlin 的利弊;但是我将讨论一个为什么我会建议你在狂热之前慢下来并停下来的原因。
还是比较新的。一些人确信,它正在接近“膨胀的期望的顶峰”,并将很快进入“幻灭的低谷”。他们的主要论点是,如果你现在押注于 Kotlin,你将背负着学习曲线问题,你将有义务维护代码库——即使 Kotlin 消失在一股烟雾中。换句话说,你可能会把它当作技术债务。
收养 Kotlin 也要付出一些代价。你必须训练你的团队如何使用它。不管你的团队有多有经验,他们肯定会在过程中降低速度——这是一个项目管理问题。此外,因为 Kotlin 是新的,所以还没有“有效的 Kotlin”的指导文章,而 Java 程序员总是有他们的“有效的 Java”
这将归结为你的赌注。如果你打赌 Kotlin 会坚持到底,而不是在黑暗中悄悄消失,那么这个赌注就有回报了。如果你赌错了,那么你就走上了维护一门废弃语言的代码库的艰难道路——技术债务。要么这样,要么你把它重写回 Java。
谷歌已经在 Android Studio 中正式支持该语言,越来越多的开发者也加入了这一行列。收养率正在上升。这些都是好迹象,表明 Kotlin 不会悄无声息地倒下,实际上可能会坚持到底。另外,这是一种很酷的语言。
注意
“膨胀的期望的顶峰”和“幻灭的低谷”是“炒作周期”的一部分。炒作周期是由美国研究、咨询和信息技术公司 Gartner 开发和使用的品牌图形表示,用于表示特定技术的成熟、采用和社会应用。可以在 https://gtnr.it/cycleofhype 了解更多。
让我们继续为自己构建一个开发环境。
安装 Java SDK
在使用 Kotlin 之前,我们需要安装 JDK。如果您已经安装了 Java 开发工具包,那么您可以跳过这一节,跳到下一节(安装 Kotlin)。JDK 安装程序可用于 Windows、Linux 和 macOS。你可以从甲骨文网站 http://bit.ly/java9download 下载目前稳定的版本。 1
图 1-1 显示了甲骨文 JDK 的下载页面。选择适合您平台的安装程序,然后单击“接受许可协议”继续。
图 1-1
Oracle JDK 下载页面
在 macOS 上安装
要在 macOS 上安装 JDK,双击下载的 dmg 文件,按照提示进行操作。安装程序负责更新系统路径,因此安装后您不需要执行任何进一步的操作。
当你完成安装后,你可以通过启动“Terminal.app”并尝试Java命令来测试 JDK 是否已经安装好(见列表 1-1 )。
$ java –version
$ javac –version
Listing 1-1Test the JDK tools on a macOS Terminal
如果终端输出如图 1-2 所示的java和javac版本,你就知道你已经安装了 JDK,没有问题。
图 1-2
终端上的 java 和 javac . app
在 Windows 10 上安装
可以在 Windows 7/8/10 (32 位和 64 位)安装 Android Studio 3;但出于本书的目的,我只用了 Windows 10 64 位。
要在 Windows 上安装 JDK,请双击下载的压缩文件,然后按照提示进行操作。与 macOS 不同,您必须在设置后执行额外的配置。你需要(1)在你的系统路径中包含 Java/bin ,( 2)在 Windows 的环境变量中包含一个类路径定义。表格 1-1 将带您了解如何做到这一点的步骤。
表 1-1
Windows 中的 JDK 配置
| one | 将 JAVA_HOME/bin 包含到系统路径中 | 1.点击**开始** ➤ **控制面板** ➤ **系统**2.点击**高级** ➤ **环境变量。**有两个变量框,上面的框写着“用户变量”,下面的框写着“系统变量”,系统`PATH`将在“系统变量”框中。3.将 bin 文件夹的位置添加到系统变量`PATH`中。4.典型的路径变量如下所示:`C:\WINDOWS\system32;C:\WINDOWS;C:\Program Files\Java\jdk-9\bin;` | | Two | 在 Windows *环境变量*中创建一个*类路径*定义 | 当**环境变量**窗口仍然打开时,点击“用户变量”部分的“新建”按钮。将弹出另一个对话框,其中有两个文本框,允许您添加一个新变量。使用下面的值填充文本框。1 .名称 2.价值➤ `C:\WINDOWS\system32;C:\WINDOWS;C:\Program Files\Java\jdk-9\jre\lib\rt.jar;` |关闭环境变量窗口,得到一个 cmd 窗口,这样我们可以测试我们的更改是否已经生效。当 cmd 窗口打开时,键入如清单 [1-2 所示的命令。
C:\Users\yourname>java –version
C:\Users\yourname>javac –version
Listing 1-2Test the JDK tools on a Windows cmd shell
如果 cmd shell 向您显示了 java 和 javac 的版本,那么您已经成功地安装和配置了 JDK。另一方面,如果您看到一条错误消息(例如,“错误的命令或文件名”),这意味着 JAVA_HOME\bin 仍然不是系统路径的一部分。您应该重新访问表 1-1 并重新检查您的条目,然后重新测试。
在 Linux 上安装
如果您是 Linux 用户,您可能已经在下载中看到了 tar ball 和 rpm 选项,您可以像在您的 Linux 平台上安装任何其他软件一样使用和安装它,或者您可以从存储库中安装 JDK(参见清单 1-3 )。本指令适用于 Debian 及其衍生工具(如 Ubuntu、Mint 等。).
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java9-installer
sudo update-alternatives --config java
Listing 1-3Installing the JDK in Ubuntu Using a PPA
下载完成后,您可以通过从命令行试用java和javac工具来测试安装(参见清单 1-4 )。打开你喜欢的终端模拟器(如 xterm、terminator、gnome-terminal、lxterminal 等)。).
$ java –version
$ javac –version
Listing 1-4Test the JDK Tools on Linux
如果安装成功,您应该能够在您的系统中看到 java 和 javac 的版本。一旦 JDK 建成并开始运行,我们现在可以得到 Kotlin。
安装 Kotlin
Kotlin 编码有几种入门方法。你可以使用在线 IDE,这是最快的,因为它不需要你安装任何东西。您也可以尝试下载一个带有 Kotlin 插件的 IDE(例如 IntelliJ、Android Studio 或 Eclipse)。最后,您可以下载 Kotlin 的命令行工具。如果你不想安装一个完整的 IDE,而只是简单地使用你最喜欢的编辑器,你当然可以使用命令行工具。我们不会探究这些选项中的每一个,但是我们将看看命令行工具和 IntelliJ。
注意
这本书是关于 Android Studio 的,所以你可能想知道为什么我们不使用 Android Studio 来试用 Kotlin。这是因为这本书的这一部分仅仅是关于 Kotlin 的,而不是关于 Android 编程的。我认为,当我们做一些编码练习时,最好更多地关注语言,不要被 Android 特定的主题所阻碍。Android Studio 无论如何都是基于 IntelliJ 的,所以我们在本书这一部分学习的任何 IDE 技术在我们学习第二部分时都应该可以很好地继续下去。
安装命令行工具
即使您选择命令行工具,也有几种安装方法可供选择。我们可以通过(1)下载压缩文件来安装它;(2)如果你的操作系统和工具支持,使用 SDKMAN 或者(3)如果你在 macOS 上,使用自制软件或 MacPorts。你只需要选择这些方法中你最习惯的一种,然后去做。
自制或 macports
如果你在 macOS 上并且已经使用了brew或port,请参见清单 1-5 或 1-6 中获取 Kotlin 的终端命令。
$ sudo port install kotlin
Listing 1-6Install Kotlin Using MacPorts
$ brew update
$ brew install kotlin
Listing 1-5Install Kotlin Using HomeBrew
使用压缩安装程序
如果你去 Kotlin 网站, http://kotlinglang.org 然后“学习”➤“教程”《➤》入门“➤”使用命令行编译器”,你会发现一个网页 2 看起来可能如图 1-3 所示。压缩后的安装程序可以通过链接“GitHub releases”下载(如图 1-3 )。
图 1-3
Kotlin 命令行编译器页面
链接应该会把你带到 JetBrains/Kotlin3的 GitHub 页面(图 1-4 )。在撰写本文时,Kotlin 的版本是 1 . 2 . 10;当你读到这篇文章的时候,它可能是一个不同的版本,但是只要下载最新的稳定版本。
图 1-4
安装程序压缩文件的 GitHub 页面
下载完成后,解压缩安装程序文件,并将其放在系统中的某个位置,最好是您拥有读、写和执行权限的目录。文件应该解压到名为“kotlinc”的文件夹中。接下来要做的是将 kotlinc/bin 文件夹添加到系统路径变量中。下面几节将演示如何在 macOS、Linux 和 Windows 上做到这一点。
macOS 和 Linux
将下载的压缩文件复制到您的主目录,并在那里解压缩。清单 1-7 显示了该命令。
$ cd ~
$ unzip ~/kotlin-compiler-1.2.10.zip
Listing 1-7Unzip Kotlin Installer
注意
默认情况下,解压缩命令在 macOS 中是可用的,但是对于 Linux 系统,您可能必须首先从存储库中获取它。清单 1-8 显示了如何从存储库中提取它的命令。
$ sudo apt get update
$ sudo apt-get install unzip
Listing 1-8Getting the Unzip Tool
安装文件应该解压到一个名为“kotlinc”的文件夹中,如图 1-5 所示。
图 1-5
解压缩 Kotlin 安装程序
在使用命令行工具之前,我们需要将“kotlinc/bin”文件夹添加到系统路径变量中,如清单 1-9 所示。
$ export PATH=~/kotlinc/bin:$PATH
Listing 1-9Adding kotlinc/bin to the System Path
按下回车键,kotlinc命令现在应该可以工作了。您可以将清单 1-9 中所示的行添加到您的登录脚本中,这样每次打开终端窗口时都可以使用 Kotlin 工具。
Windows 10
将 Kotlin 安装程序的压缩文件复制到您的主目录,并在那里解压缩。使用你最喜欢的存档工具解压。它应该解压到下面的文件夹:C:\Users\yourname\kotlinc。在 kotlinc 文件夹中是 bin 文件夹,它包含了我们需要用于编译的各种脚本和批处理文件。这个 bin 文件夹就是我们需要添加的 Windows 系统路径。
要将 kotlinc\bin 文件夹添加到系统路径,点击 Windows 开始按钮➤ 控制面板 ➤ 系统。一旦系统对话框打开,点击高级 ➤ 环境变量。变量有两个框;上框显示“用户变量”,下框显示“系统变量”。系统PATH将出现在“系统变量”框中。追加kotlinc\bin到PATH变量。关闭系统对话框以保存更改。
使用 SDKMAN
SDKMAN 可以在 macOS、Linux、Cygwin (Windows)、FreeBSD 和其他 UNIX 系统上使用。如果您已经将它作为工具链的一部分,那么您可以使用它来获得 Kotlin 编译器。如果你还没有 SDKMAN,安装起来很简单。参见清单 1-10 安装 SDKMAN。
重要的
在从命令行安装 SDKMAN 之前,您需要获得curl工具。如果您还没有它,请使用您的平台软件包管理器来获取curl。
$ curl -s "https://get.sdkman.io" | bash
Listing 1-10Installing SDKMAN From the Command Line
按照屏幕上的说明完成安装。您需要关闭当前的终端窗口并启动另一个窗口,因为 SDKMAN 安装程序对登录脚本进行了更改。为了使这些更改生效,您需要打开一个新的终端窗口。完成后,我们现在可以安装 kotlin 了。安装命令见清单 1-11 。
$ sdk install kotlin
Listing 1-11Installing Kotlin via SDKMAN
使用命令行工具编码
无论您选择哪种方式来安装命令行工具,现在您都应该已经有了一个可以工作的 Kotlin 编译器。要尝试一下,打开一个终端窗口,输入命令kotlinc。这将改变你的终端提示为三个 v 形(大于符号);参见清单 1-12 。
$ kotlinc
Welcome to Kotlin version 1.2.10 (JRE 9.0.1+11)
Type :help for help, :quit for quit
>>>
Listing 1-12Kotlin REPL
这是 Kotlin REPL——读取、评估、打印、循环的缩写。它以交互方式执行 Kotlin 命令,并立即向您显示结果。如果您以前使用过现代浏览器的控制台功能来输入 JavaScript 命令,这与那个非常相似。REPL 是互动学习语言的好方法。它在开发过程中也非常有用,因为它允许您试验表达式和语句,而不必经历完整的编写-编译-运行周期。您可能想要尝试几个表达式和语句(参见清单 1-13 )。
>>> 5 * 3
15
>>> println("Hello there")
Hello there
for (i in 1 . . 3) {
. . .println(i)
. . .}
1
2
3
>>>
Listing 1-13Simple Expressions
REPL 对于测试语句甚至清单 1-13 中所示的小段代码非常有用,但是如果您需要测试更长的程序,将它编写在程序文件中、编译并运行它会更方便,就像您测试 Java 程序一样。让我们来看看在 Kotlin 是什么样子。
首先,创建一个文件,并将其命名为“hello . kt”——kot Lin 源文件的扩展名为”。kt”。hello.kt 的内容如清单 1-14 所示。
fun main(args: Array<String>) {
print("Hello")
}
Listing 1-14hello.kt
Kotlin 与 Java 有相似之处,所以清单 1-14 可能看起来很熟悉,但是您也会很快注意到一些明显的东西,所以现在让我们来解决这些问题。
-
没有类构造。Kotlin 不需要类来执行函数。如清单 1-14 所示的函数称为顶级函数;main 函数很特殊,因为与 Java 的
public static void main()一样,Kotlin 的fun main()是应用的入口点。当您运行一个 Kotlin 文件时,运行时将查找这个函数。 -
函数 main 与语法略有不同。用关键字
fun定义函数。类型声明在标识符(args)之后;你会习惯的。此外,Kotlin 没有特殊的语法来定义数组。数组只是 Kotlin 中的类型。 -
函数 main 没有返回值。其实是有的,只是我们没有写在例子里。函数的默认返回值是
Unit;,就像 Java 中的 void。 -
没有分号 - 冒号。这些已经没有必要了。
下一步是编译和运行我们的源文件。清单 1-15 显示了管理它的命令。
kotlin hello.kt –include-runtime –d hello.jar
java –jar hello.jar
Listing 1-15Compile and Run hello.kt
如果您能够像前面的清单和示例中所示的那样正确地输入所有内容,您应该会在屏幕上看到“Hello World”消息。
如果您觉得命令行工具不适合您,并且您更愿意使用功能更丰富的编程环境,您可以尝试其他 ide,如 Eclipse、IntelliJ 或 Android Studio 3 (AS3)。我们将在本书中介绍 IntelliJ 和 AS3 的安装和使用。下一节将带您了解 IntelliJ IDEA 的设置。
安装 IntelliJ
JetBrains 创建了 Kotlin,所以正如你所想象的,它有很好的支持。Android Studio 基于 JetBrain 的 IntelliJ IDEA CE(社区版);然而,Android Studio 是免费和开放源码软件,由谷歌而不是 JetBrains 维护。
我们甚至可以在本书的第一部分使用 AS3 然而,这样做需要我们同时处理 Kotlin 和 Android 组件。我选择不去做,而是专注于 Kotlin。无论如何,AS3 是基于 IntelliJ 理念的,所以我们在 IntelliJ 上获得的任何知识和技能都将很好地转化为 AS3。
你可以从 JetBrains 网站( http://www.jetbrains.com )下载 IntelliJ IDEA,然后上来工具,下来 IntelliJ IDEA (见图 1-6 )。它将带你到一个页面,在那里你可以为你的平台选择合适的安装程序。您还可以选择是下载“终极版”还是“社区版”。我们将下载社区版。
图 1-6
IntelliJ IDEA 下载页面
如果您使用的是窗口,您需要:
-
双击您下载的ideaIC.exe
-
按照屏幕提示完成安装
对于 macOS ,执行以下操作:
-
双击您下载的 ideaIC.dmg
-
将 IntelliJ IDEA 复制到应用文件夹
-
运行 IntelliJ IDEA。
对于 Linux ,安装说明如下:
-
将 tar.gz 安装程序文件复制到您拥有读取、写入和执行权限的目录中。出于我们的目的,我们将把它复制到主文件夹中(参见清单 1-16 )。
-
打开 ideaIC.tar.gz 的包装,如清单 1-17 所示。
$ cd
$ cp ~/Downloads/ideaIC-2017.3.2.tar.gz .
Listing 1-16Copy IntelliJ Installer to Your Home Folder
- 将 ideaIC/bin 添加到系统路径中,如清单 1-18 所示。
tar –xzvf ideaIC.tar.gz
Listing 1-17Untar the Installer
- 通过运行 idea.sh 脚本启动 IntelliJ IDEA,如清单 1-19 所示。
$ export PATH=~/ideaIC-2017.3.2/bin:$PATH:.
Listing 1-18Add ideaIC/bin to the System Path
$ sh idea.sh
Listing 1-19Start idea.sh
创建项目
如果您还没有启动 IntelliJ,请启动它。它从一个欢迎屏幕开始,如图 1-7 所示。首先,让我们创建一个项目。
图 1-7
欢迎来到 intellij idea
点击“创建新项目”会将我们带到“新项目”窗口(如图 1-8 所示)。选择“Kotlin/JVM ”,然后单击“下一步”按钮。
图 1-8
新的 Kotlin/JVM 项目
这将我们带到“New Project”向导的第二个窗口,在这里我们需要输入一些信息,但是大多数信息已经预先填充了默认条目,我们可以简单地接受默认值。我们确实需要提供“项目名称”,除非您想将您的项目命名为“未命名”(这是项目名称字段的默认值—可能不是一个好主意)。
在图 1-9 中,我使用了“kotlinproject”作为项目名称。我没有更改默认的项目位置,即主文件夹下的“IdeaProjects”。我也没有对“项目 SDK”做任何更改,这是 IntelliJ 在安装时检测到的。要完成项目创建向导,请单击“完成”按钮。
图 1-9
新项目
第一次启动 IntelliJ 时,你会看到“日积月累”窗口(图 1-10 )。提示对于学习 IDE 的功能非常有用,但是我更喜欢它们只在我召唤它们的时候出现,而不是每次启动 IDE 的时候都弹出来。您可以通过取消选中“启动时显示提示”来禁用启动时显示的“每日提示”窗口让我们暂时关闭它。
图 1-10
日积月累
当日积月累对话框关闭时,我们可以更全面地看到我们新创建的项目(图 1-11 )。ide 的左侧显示“项目工具窗口”;它现在没有多少,因为我们还没有创造任何东西。
图 1-11
我们在 IntelliJ 的 Kotlin 项目
项目工具窗口允许我们更改“视图”所有的视图都显示相同的项目,但是每个视图对内容的安排都有所不同。您可以通过单击下拉按钮来更改项目工具窗口的视图(参见图 1-12 )。您应该尝试几种视图来熟悉它们。
图 1-12
项目工具窗口,视图
对于本节的其余部分,我们将使用“项目”视图。这个视图以树状结构显示我们的文件,很像你操作系统中的文件管理器(见图 1-12 )。您可以向下钻取并展开以查看文件夹的内容,如图 1-13 所示。
图 1-13
项目工具窗口。项目视图
“src”文件夹(source 的缩写)是我们放置 Kotlin 源文件的地方。右击 src 文件夹,选择新建➤·Kotlin 文件/类,如图 1-14 所示。
图 1-14
从项目工具窗口新建 Kotlin 文件
我们现在将创建一个 Kotlin 文件,并将其命名为“Hello”;“我们不用写了。kt "扩展名名称字段(见图 1-15)—扩展名将自动为我们添加。确保在对话框的“种类”栏中选择了“文件”选项(参见图 1-15 )。单击“确定”按钮创建文件。
图 1-15
新 Kotlin 文件
当源文件被创建时,你会在项目工具窗口的 src 文件夹下看到它,它也会在主编辑器窗口中被打开(见图 1-16 )。
图 1-16
Hello.kt
IntelliJ 具有出色的代码提示和自动完成功能。当它识别出你正在输入的内容时,它会给你一些建议和提示(见图 1-15 )。一旦您键入了足够多的可能是 Kotlin 关键字或构造的字符模式,IDE 就会提供建议。您可以接受当前建议的选项(在弹出窗口中高亮显示,如图 1-15 所示)或使用鼠标或箭头键选择其他自动完成选项。
清单 1-20 显示了这个例子的完整代码清单。
fun main(args: Array<String>) {
println("Hello World")
}
Listing 1-20Hello.kt
下一步是运行这个程序;您可以通过调用 IntelliJ 主菜单栏上的 Run 菜单来实现这一点。主菜单栏位于 IDE 的顶部,顶层选项包括文件、编辑、查看和帮助。从主菜单栏中点击运行 ➤ 运行。你会注意到在主运行菜单上有两个运行选项,第一个运行选项是灰色的。选择另一个运行选项,它位于从顶部往下数第四个项目的位置。第一次运行选项是灰色的,因为我们还没有为项目定义任何运行时配置。我们本来可以编辑配置并提供运行时类的名称,但是我们不必这样做。选择第二个运行选项会弹出一个对话框窗口(见图 1-17 ),会要求我们输入当前项目的运行时类的名称。“HelloKt”是我们将选择作为该项目的运行时类的类。
注意
我们的源文件名是“Hello.kt”,但是 Kotlin 编译器不会生成“hello . class”;而是会生成字节码“HelloKt.class”。在处理 Kotlin 类文件时,您应该记住这一点。
图 1-17
运行 Hello.kt
IDE 将把“Hello.kt”编译成“HelloKt.class ”,然后运行。结果将显示在“运行”工具窗口中(参见图 1-18 )。
图 1-18
运行 Hello.kt 的结果
现在我们已经成功运行了一个顶级函数,让我们向应用添加一个类,并编写一个更加面向对象的代码示例版本。要添加一个类,右击项目工具窗口上的“src”文件夹(图 1-19 ,选择新建 ➤ Kotlin 文件/类。
图 1-19
向项目中添加新文件/类
当弹出“新建 Kotlin 文件/类”对话框窗口时,选择“类”(图 1-20);姑且称之为“迎宾”。
图 1-20
新 Kotlin 级
在主编辑器窗口中编辑欢迎类(图 1-21 )。
图 1-21
更大的阶级
然后编辑 Hello.kt 如图 1-22 所示。完成更改后,再次运行“Hello.kt”。从主菜单栏中,运行 ➤ 运行;或者,您可以使用Shift + F10来运行代码。
图 1-22
用更大的类运行 main
图 1-21 显示了我们更新代码的输出。本章的所有编码活动到此结束。现在您可能已经知道,IntelliJ 对 Kotlin 语言有很好的支持;如果你喜欢用不同的编辑器编写 Kotlin 程序,你可以不使用它。但是如果您选择使用它,我们不妨快速浏览一下 IDE,以便更好地使用它。这就是下一部分的内容。
智能集成电路
图 1-23 显示了 IDE 的各个部分。您需要有一个打开的项目,以便在桌面上看到类似的内容。
图 1-23
IntelliJ IDEA IDE
表 1-2 讨论了 IDE 的各个部分,因为它与图 1-22 相关。
表 1-2
IntelliJ IDE
| 主菜单栏 | 在 IDE 中有许多方法可以完成任何任务;您可以使用各种键盘快捷键或上下文菜单,但最全面的导航方式是在主菜单栏上。这个栏位于 IDE 的最顶端。 | | 工具窗口栏 | 工具窗口栏沿着 IDE 窗口的周边运行。它包含激活特定工具窗口所需的各个按钮。 | | 显示/隐藏工具窗口 | 这是查看 IDEA 中各种工具窗口的快捷方式。也可以从主菜单栏**查看** ➤ **工具窗口中查看或隐藏工具窗口。** | | 主编辑器窗口 | 这是最突出的窗口,它拥有最多的屏幕空间。在编辑器窗口中,您可以创建和修改项目文件和源文件。 | | 刀杆 | 工具栏允许您执行各种操作(例如,保存文件、运行应用、打开 AVD 管理器、打开 SDK 管理器、撤销、重做操作等)。). | | 导航栏 | 它允许您导航项目文件。这只是“项目文件”窗口的一个更紧凑的视图。这是一个水平排列的箭头框集合,类似于一些网站上可以找到的面包屑导航。您可以通过导航栏或项目工具窗口打开项目文件。 | | 项目工具窗口 | 显示项目中的文件。如果您想要打开一个特定的文件,在这个窗口中双击该文件,它将在主编辑器窗口中打开。您也可以在此窗口中的项目上使用上下文菜单。上下文菜单允许在 IDE 中完成任务的替代方法(例如,添加类文件、运行代码、调试等)。). |章节总结
-
Kotlin 是 Android 最新的编程语言,它在 Android Studio 3 上有一流的支持。
-
有许多方法可以在 macOS、Linux 和 Windows 上安装 Kotlin 命令行编译器和运行时。
-
各种 ide 都支持 Kotlin 语言;在其中的一些上,你必须得到一个插件,而在一些上,它是开箱即用的。
-
Kotlin 看起来和 Java 很像,但也有区别。
-
IntelliJ 对 Kotlin 有很好的支持——毕竟是 JetBrains 创造了 Kotlin。
在下一章中,我们将了解以下内容:
-
程序元素(例如,文字、变量、表达式、关键字、运算符等。)—构成我们代码的各种东西
-
在 Kotlin 中我们可以使用哪些类型的数据
-
Kotlin 中为什么会有可空类型,它首先是什么?
-
控制结构,以便您可以循环和分支
-
异常处理和为什么你不必再用 Kotlin 写 try-catch 了(剧透)
可用 http://www.oracle.com/technetwork/java/javase/downloads/jdk9-downloads-3848520.html
2
使用命令行编译器: https://kotlinlang.org/docs/tutorials/command-line.html
3
jetbrains/kotlin github page:https://github.com/JetBrains/kotlin/releases/tag/v1.2.10
二、Kotlin 基础知识
我们将介绍的内容:
-
程序元素
-
基本类型
-
不变
-
用线串
-
可空类型
-
控制结构
-
异常处理
Kotlin 与 Java 并没有什么不同。虽然它引入了相当多的特性,但您会发现 Kotlin 和 Java 的相似之处多于不同之处。这对 Java 程序员来说是个好消息,因为这意味着 Kotlin 的学习曲线不会太陡。
您需要习惯一些新事物,比如 Kotlin 中的表达式和语句(它们与 Java 完全相反;例如,在 Kotlin 中赋值是语句,但在 Java 中是表达式)。在这一章中,我们将介绍一些 Kotlin 基础知识,这些知识可以作为后续章节的基础知识。
程序元素
当学习一门新的语言时,一门合适的语言,如法语、西班牙语等。,你可能会从词类和支配它们的规则开始。如果我们对一门语言的各个部分是如何组合在一起有一些基本的了解,那么学习这门语言就容易多了。一个 Kotlin 程序包含文字、变量、表达式、关键字和许多其他东西,我们将在这一节探讨其中的一些。
文字
Kotlin 为基本类型(数字、字符、布尔、字符串)提供了文字。
var intLiteral = 5
var doubleLiteral = .02
var stringLiteral = "Hello"
var charLiteral = '1'
var boolLiteral = true
Listing 2-1Literal Examples
在清单 2-1 中,值5, .02, " Hello " , ' 1'和true分别是整数、双精度、字符串、字符和布尔类型的文字。
变量
变量是我们用来操作数据的东西,或者更准确地说,是一个值。值是可以存储、操作、打印、推送或从网络中提取的东西。为了能够处理值,我们需要将它们放入变量中。Kotlin 中的变量是通过使用关键字var后跟类型声明一个标识符来创建的,如语句中所示
var foo: Int
在该语句中,foo是标识符,Int是类型。Kotlin 通过将类型放在标识符的右边来指定类型,并用冒号将其隔开。
既然变量已经声明,我们就可以给它赋值了,就像这样:
foo = 10
然后,在函数中使用它,如下所示:
println(foo)
我们可以在同一行声明和定义变量,就像在 Java 中一样。这里是 var foo 的例子。
var foo: Int = 10
println(foo)
我们仍然可以通过省略 type ( Int)来缩短上面的赋值语句。参见示例代码:
var foo = 10
println(foo)
我们不一定要声明或写出变量的类型;Kotlin 很聪明,当你给变量赋值的时候,它能判断出变量的类型;这叫做式推理。在我们明确告诉 Kotlin 变量类型的时候,注意它在变量名(foo)的右边,而在 Java 中,正好相反,变量类型在标识符的左边。Kotlin 没有遵循将类型放在标识符左边的 Java 约定的原因是,在 Kotlin 中,我们并不总是编写类型。
var foo = 10 // compiler knows 10 is an integer literal
var boo = .02 // double literal makes boo a double type
Kotlin 使用另一个关键字来声明变量,即val关键字。用此关键字声明的变量只能在定义它们的执行块中初始化一次。这使得它们实际上是常数。把val想象成 Java 中的final关键字——一旦你把它初始化成一个值,你就不能再改变它,它们是不可变的。虽然使用var创建的变量是可变的,但是它们可以根据您的需要多次更改。
Val 变量的声明和初始化就像 var 变量一样:
val a = 10 // declaration and initialization on the same line
它们也可以在以后声明和初始化,如下所示:
val a: Int
a = 10
请记住,用val关键字声明的变量是最终变量,一旦你将它们初始化为一个值,就不能重新赋值。这里的代码片段不起作用:
val boo = "Hello"
boo = "World" // boo already has a value
如果您认为以后需要更改变量 boo 的值,请将声明从val更改为var。
智能 J 型
如果你试图重新分配一个使用 val 关键字声明的变量的值,IntelliJ 会给你足够的视觉提示“val 不能被重新分配”,甚至在你试图编译代码之前。
表达和陈述
表达式是运算符、函数、文字值、变量或常数的组合,并且总是解析为一个值。它也可以是更复杂表达式的一部分。一个语句可以包含表达式,但是就其本身而言,一个语句不能解析为一个值。它不能是其他语句的一部分。它总是其封闭块中的顶级元素。
在很大程度上,您在 Java 中学到的关于表达式和语句的知识在 Kotlin 中也是适用的,但是有一些细微的差别。随着我们的深入,我将指出 Java 和 Kotlin 在语句和表达式方面的区别。这些差异包括:
赋值在 Java 中是表达式,但是在 Kotlin 中是语句。这意味着你不能将赋值操作作为参数传递给像 while 这样的循环语句。见清单 2-2 。
while ((rem = a % b) != 0) {
a = b
b = rem
}
println(b)
Listing 2-2Assignment Operation As Argument to While
Kotlin 不让你编译,因为while语句需要一个表达式,而赋值不是表达式。为了让前面的代码示例(清单 2-2 )在 Kotlin 中工作,您必须用另一种方式编写它,如清单 2-3 所示。
var foundGcf = false
while(!foundGcf) {
rem = a % b
if (rem != 0) {
a = b
b = rem
}
else {
foundGcf = true
}
}
println(b)
Listing 2-3Using the While Loop in Kotlin
清单 2-3 比您可能习惯的(在 Java 中)要冗长一些,它有更多的字符需要键入,但是代码的意图更清晰易懂。
Kotlin 和 Java 在表达式和语句方面的另一个显著区别是,在 Kotlin 中,大多数控制结构(除了for, do,和do/while)是表达式,而在 Java 中是语句。
关键词
关键字是对编译器有特殊意义的保留术语,因此,它们不能用作任何程序元素(如类、变量名、函数名和接口)的标识符。
Kotlin 有硬、软和修饰符关键字。硬关键字总是被解释为关键字,不能真正用作标识符。例如,break, class, continue, do, else, false, while, this, throw, try, super, and when.
软关键字在它们适用的特定上下文中充当保留字;否则,它们可以用作常规标识符。软关键字的一些例子如下:file, finally, get, import, receiver, set, constructor, delegate, get, by, and where.
最后是修饰关键词。这些东西在声明的修饰符列表中充当保留字;否则,它们可以用作标识符。这些事情的一些例子如下:abstract, actual, annotation, companion, enum, final, infix, inline, lateinit, operator, and open.
智能 J 型
如果你使用 IntelliJ,你不必记住关键字列表。如果您不小心将关键字用作标识符,IDE 会给你足够的视觉提示。
空白
和 Java 一样,Kotlin 也是一种标记化语言;空白不重要,可以安全地忽略。您可以编写大量使用空格的代码,比如
fun main(args: Array<String>) {
println( "Hello")
}
或者你可以用很少的代码来写,就像下面的例子:
fun main(args: Array<String>) {println("Hello")}
不管是哪种方式,编译器都不会在意,所以为了那些可能不幸维护我们代码的人类的利益而写你的代码吧。忘了编译器吧——它根本不在乎空白。使用空格美化代码,使其可读,可能类似于
fun main(args: Array<String>) {
println("Hello")
}
经营者
像在 Java 和其他编程语言中一样,Kotlin 支持各种各样的操作符和符号,我们可以用它们来表达表达式和语句。表 2-1 显示了其中的一些。
表 2-1
Kotlin 算子和符号
|运算符或符号
|
什么意思
|
| --- | --- |
| +, -, *, /, % | 这些是常见的数学运算符——它们完全按照你的期望去做。跟 Java 一点区别都没有。但是我们需要注意,星号或星号()也用于将数组传递给 vararg 参数。 |
| = | 等号用于赋值语句(赋值在 Kotlin 中是一个语句,而在 Java 中是一个表达式)。 |
| +=, -=, *=, /=, %= | 这些是增强赋值运算符。+=可以这样使用a = a + 1;的简称a += 1,``-=可以这样使用a -= 1,是a = a -1的简称,以此类推。 |
| &&, ||, !逻辑'and'、'or', 'not'运算符 | 当您需要构造复杂的或复合的逻辑操作时,您将使用这些操作符。短路和* ( &&)的行为与 Java 类似。当其中一个操作数的计算结果为 false 时,将不再计算另一个操作数,整个表达式的计算结果为 false。而逻辑'and'不进行短路评估;可以把它想象成 Java 中的&操作符。短路 or ( ||)的作用与 Java 中的相同。Kotlin 没有单一管道运营商;取而代之的是'or'操作符,它执行逻辑“或”运算而不会短路。 |
| ==, != | 这些是等式运算符。因为 Kotlin 没有基本类型(像 Java 中那样),所以您可以使用这些操作符来比较任何类型,基本类型或其他类型:fun main(args: Array<String>) {``var a = "Hello"``var b = "Hello"``if (a == b) { // this evaluates to true``println("$a is equal to $b")``}``}在 Java 中,我们不能像这样使用 double equals 操作符进行对象比较。对象(如字符串)应该使用。如果我们想测试相等性。然而,在 Kotlin,我们不需要担心这些事情。我们使用双等号运算符来比较字符串。Kotlin 在内部将其翻译为调用。equals()方法。 |
| ===, !=== | 引用相等性由===运算(及其反运算)检查。==).当且仅当 a 和 b 指向同一个对象时,a === b 的计算结果为 true。例如,var p1 = Person("John")``var p2 = Person("John")``if(p1 === p2) { // false``println("p1 == p2")``}在上面的例子中,p1 和 p2 没有指向同一个对象;因此,三重等于不会评估为真。 |
| <, >, <=, >= | 比较运算符。Kotlin 将这些转换成对compareTo()的调用——没有原始类型,记得吗? |
| [ ]``[,] | 索引访问操作符是一种访问列表元素或 map 值的便捷方式。我们可以使用数组索引来检索条目,而不是使用 Java 风格的get(index)或get(key),。fun main(args: Array<String>) {``val fruits = listOf("Apple", "Banana", "Orange")``println(fruits.get(2)) // Banana``println(fruits[2]) // Banana``} |
阻碍
通常,你可能需要写一堆陈述,你需要把它们组合在一起。积木让我们可以做到这一点。块词汇符号是一对花括号;他们有时也被称为法国或弯曲的括号。块可以在许多 Kotlin 构造(如类)中找到,如下面的代码:
class Person(val name: String) {
}
当定义接口时,例如
interface Human {
fun walk()
fun talk()
}
在函数中,比如
fun main(args: Array<String>) {
greet("John")
}
fun greet(name:String) {
println("Hello $name")
}
在循环结构中,如 while 循环
var counter = 0
while (counter++ != 5) {
println("counter $counter")
}
当使用 try-catch 构造时
val num = "1"
val ans = try {
Integer.parseInt(num)
}
catch(e:Exception) {
e.printStackTrace()
}
以及可能需要对语句进行分组的任何其他控制结构。
评论
注释对编译器没有用;它忽略了它们。但是它们对阅读这些代码的其他人(和你)是有用的。这使它们成为使代码更容易理解的优秀工具,因为您可以在编写代码时使用注释来转储您的思维过程。它阐明并传达了你的意图。写评语有三种方式,它们是:
-
单行注释,也称行内注释。这些是用两个正斜杠写的。编译器将忽略斜线右侧的所有内容,直到该行结束,参见示例:
// This statement will be ignored var a = 0 // so will this line -
多行注释,也称 C 风格注释。之所以这样称呼它们,是因为它们主要来自 C 语言。如果您的注释跨越多行,这种样式很有用。参见示例:
/* Everything inside the pair of these slashes and asterisks will be ignored by the compiler */ -
KDoc 就像 Javadoc 一样,以
/**开始,以*/结束。这种形式的注释非常类似于多行注释(如上),但这是用来为 Kotlin 代码提供 API 文档的。清单 2-4 展示了如何使用 KDoc 语法。/** This is an example documentation using KDoc syntax @author Ted Hagos @constructor */ class Person(val name: String) { /** This is another KDoc comment @return */ fun foo(): Int{ } } Listing 2-4KDoc Syntax
智能 J 型
您可以在 IntelliJ 中注释多行代码,方法是选择要注释的行,并使用键盘快捷键之一来注释掉代码。
在 Windows 和 Linux 中,这些键是:
CTRL + / — comment using //
CTRL + Shift + / — comment using /* */
在 macOS 中,键是:
⌘ + / — comment using //
⌘ + ⌥ + / — comment using /* */
基本类型
Kotlin 有一些基本类型,但它们与 Java 的原始类型不一样,因为 Kotlin 中的所有类型都是对象。它们之所以被称为基本类型,是因为它们的用法非常普遍。这些类型是数字、字符、布尔值、数组和字符串——我们将在本节中讨论它们。
数字和文字常量
有处理数字的内置类型(如表 2-2 所示)。它们可能在运行时被表示为原始值,但是对于所有的意图和目的,它们在程序员看来并不是原始的。它们作为真正的对象出现,具有成员函数和属性。
表 2-2
Kotlin 数字内置型
|类型
|
位宽
| | --- | --- | | 两倍 | Sixty-four | | 浮动 | Thirty-two | | 长的 | Sixty-four | | (同 Internationalorganizations)国际组织 | Thirty-two | | 短的 | Sixteen | | 字节 | eight |
Kotlin 处理数字的方式非常接近 Java 处理数字的方式,但是有一些显著的不同。例如,扩大转换不再是隐式的;您需要谨慎地执行转换。
var a = 10L // a is a Long literal, note the L postfix
var b = 20
var a = b // this won't work
var a = b.toLong() // this will work
当整数用作文字常量时,它们会自动成为整数。要声明一个长文本,使用 L 后缀,比如
var a = 100 // Int literal
var b = 10L // Long literal
您可以在数字文本中使用下划线,以提高可读性。这个特性是在 Java 7 及其更高版本中引入的。
var oneMillion = 1_000_000
var creditCardNumber = 1234_5678_9012_3456
带有小数位置的文字会自动成为的双精度值。要声明浮点文字,请使用 F 后缀,比如
var a = 3.1416 // Double literal
var b = 2.54 // Float literal
每种数字类型都可以转换成任何一种数字类型。这意味着所有的 *Double、Float、Int、Long、Byte、*和 Short 类型都支持以下成员函数:
-
toByte() : Byte -
toShort() : Short -
toInt() : Int -
toLong() : Long -
toFloat() : Float -
toDouble() : Double -
toChar() : Char
特性
kot Lin 中的字符不能直接当作数字。你不能做如下事情:
fun checkForKey(keyCode:Char) {
if (keyCode == 97) { // won't work, keyCode is not a number
}
}
字符文字使用单引号创建,比如
var enterKey = 'a'
像在 Java 中一样,您可以使用转义序列,如\t, \b, \n, \r, \", \", \\,和\$,如果您需要编码任何其他字符,您可以使用 Unicode 语法(例如\uFF00))。
我们不要忘记字符是 Kotlin 中的对象,所以你可以在它们上面调用成员函数。清单 2-5 显示了演示一些使用场景的片段。
val a = 'a'
println(a.isLowerCase()) // true
println(a.isDigit()) // false
println(a.toUpperCase()) // A
val b: String = a.toString() // converts it to a String
Listing 2-5Member Functions of the Character Type
布尔运算
布尔值由文字true和false表示。与 Python 或 JavaScript 等其他语言不同,Kotlin 没有 truthy 和 falsy 值的概念。这意味着,对于需要一个布尔类型的构造,你必须提供一个布尔文字、变量或者表达式来解析true或者false。
var count = 0
if (count) println("zero") // won't work
if ("") println("empty") // won't work either
数组
Kotlin 没有像 Java 中使用方括号语法创建的数组对象。Kotlin 数组是一个泛型类,它有一个类型参数。我们使用 Kotlin 数组已经有一段时间了,因为前一章中的小代码片段和“Hello World”示例都使用了数组。主函数的参数实际上是一个由字符串组成的数组。让我们再来看看这个主函数,作为复习。
fun main(args:Array<String>) {
}
有几种方法可以创建数组。可以使用arrayOf()和arrayOfNulls()函数创建它们,最后,可以使用数组构造函数创建它们。清单 2-6 提供了一些如何使用它们的示例代码。
fun main(args: Array<String>) {
var emptyArray = arrayOfNulls<String>(2) ➊
emptyArray[0] = "Hello" ➋
emptyArray[1] = "World"
for (i in emptyArray.indices) println(emptyArray[i]) ➌
for (i in emptyArray) println(i) ➍
var arrayOfInts = arrayOf(1,2,3,4,5,6) ➎
arrayOfInts.forEach { e -> println(e) } ➏
var arrayWords = "The quick brown fox".split(" ").toTypedArray() ➐
arrayWords.forEach { item -> println(item) }
}
Listing 2-6Working With the Array Type
字符串和字符串模板
我们所学的关于 Java 字符串的很多知识在 Kotlin 中仍然适用;因此,这一节将会很短。
创建字符串最简单的方法是使用转义字符串——转义字符串实际上是我们从 Java 中知道的那种字符串。这些字符串可能包含转义字符,如\n, \t, \b,等。请参见下面的代码片段。
var str: String = "Hello World\n"
Kotlin 有另一种叫做原始弦的弦。使用三重引号分隔符创建原始字符串。它们可能不包含转义序列,但可以包含新行,比如
var rawStr = """Amy Pond, there's something you'd
better understand about me 'cause it's important,
and one day your life may depend on it:
I am definitely a mad man with a box!
"""
关于 Kotlin 琴弦,我们还需要知道以下几件事:
-
它们有迭代器,所以我们可以使用循环 :
val str = "The quick brown fox" for (i in str) println(i)中的遍历字符
-
它的元素可以被索引操作符(
str[elem]),访问,非常像数组println(str2)) // returns 'e' -
我们不能再简单地通过添加一个空的字符串文字来将数字(或其他任何东西)转换成字符串:
var strNum = 10 + "" // this won't work anymore var strNum = 10.toString() // we have to explicitly convert now
在 Kotlin 中我们仍然可以使用String.format和System.out.printf;毕竟,我们可以在 Kotlin 内部使用 Java 代码。仍然可以编写类似清单 [2-7 中所示代码片段的程序。
var name = "John Doe"
var email = "john.doe@gmail.com"
var phone = "(01)777-1234"
var concat = String.format("name: %s | email: %s | phone: %s", name, email, phone)
println(concat)
// prints
// name: John Doe | email: john.doe@gmail.com | phone: (01)777-1234
Listing 2-7Using String.format and printf
在 Kotlin 中编写字符串的首选方式是使用字符串模板,比如
var concat = "name: $name | email: $email | phone: $phone"
println(concat)
// prints
// name: John Doe | email: john.doe@gmail.com | phone: (01)777-1234
Kotlin 字符串可能包含模板表达式。这些是被评估的代码片段。评估的结果被插入(连接)到字符串中。模板表达式以美元符号($)开头,后跟一个表达式。参见清单 2-8 中的示例。
| -什么 | 显示模板字符串的基本用法。模板表达式是通过使用后面紧跟一个标识符的$符号创建的。对标识符的值进行计算、解析,最后插入到声明模板表达式的字符串体中。 | | ➋ | 在这个例子中,`name.length`用花括号括起来。这是因为$符号是右关联的——它将计算紧挨着它右边的表达式。这在我们的情况下是行不通的,因为我们不想对变量`name`求值;相反,我们想要解决的是`name.length`——因此,需要用花括号将它括起来。 | | ➌ | 我们不局限于简单的变量。我们甚至可以在模板表达式中编写函数。 |fun main(args:Array<String>) {
var name = "John Doe"
println("Hello $name") ➊
println("The name '$name' is ${name.length} characters long") ➋
println("Hello ${foo()}") ➌
}
fun foo(): String {
return "Boo"
}
Listing 2-8Using Template Expressions
控制程序流程
默认情况下,程序语句以线性方式依次执行。有些结构会导致程序偏离线性流程。有些可能会导致流程分叉或分支,而其他构造可能会导致程序流程像在循环中一样绕圈。这些结构是本节的主题。
使用 ifs
if 构造的基本形式是
if (expression) statement
其中表达式解析为布尔型。如果表达式为真,则执行该语句;否则,该语句将被忽略,程序控制将流向下一个可执行语句。当您需要执行多条语句时,您可以使用带有 if 结构的块,比如
if (expression) {
statements
}
让我们看看它在代码中的样子。
val theQuestion = "Doctor who"
val answer = "Theta Sigma"
val correctAnswer = ""
if (answer == correctAnswer) {
println("You are correct")
}
到目前为止,Kotlin 中的 if 构造的行为与 Java 中的完全一样。它还支持 else if 和 else 子句,如以下代码片段所示:
val d = Date()
val c = Calendar.getInstance()
val day = c.get(Calendar.DAY_OF_WEEK)
if (day == 1) {
println("Today is Sunday")
}
else if (day == 2) {
println("Today is Monday")
}
else if ( day == 3) {
println("Today is Tuesday")
}
关于 Kotlin 的 if 的新东西是它是一个表达式,这意味着我们可以这样做
val theQuestion = "Doctor who"
val answer = "Theta Sigma"
val correctAnswer = ""
var message = if (answer == correctAnswer) {
"You are correct"
}
else{
"Try again"
}
如果条件为真, if 构造的第一块上的字符串将返回到message变量;否则,第二个块上的字符串将是返回值。我们甚至可以省略块上的花括号,因为块只包含单个语句。
var message = if (answer == correctAnswer) "You are correct" else "Try again"
上面的代码示例可能会让您想起 Java 中的三元运算符。顺便说一句,Kotlin 不支持三进制运算符,但既然你不需要,也不用担心。if 构造是一个表达式,如果您觉得需要编写需要三元运算符的代码,只需遵循前面的代码示例即可。
when 语句
Kotlin 没有一个 switch 语句,但是它有一个构造时的*。其形式和结构与开关语句惊人的相似。最简单的形式是,它可以这样实现:*
val d = Date()
val c = Calendar.getInstance()
val day = c.get(Calendar.DAY_OF_WEEK)
when (day) {
1 -> println("Sunday")
2 -> println("Monday")
3 -> println("Tuesday")
4 -> println("Wednesday")
}
当对所有分支依次匹配自变量(变量day)直到遇到匹配;注意,与 switch 语句不同,当找到匹配时,它不会流过或级联到下一个分支——因此,我们不需要放置 break 语句。
when 构造也可以用作表达式,这样使用时,每个分支都成为表达式的返回值。请参见代码示例:
val d = Date()
val c = Calendar.getInstance()
val day = c.get(Calendar.DAY_OF_WEEK)
var dayOfweek = when (day) {
1 -> "Sunday"
2 -> "Monday"
3 -> "Tuesday"
4 -> "Wednesday"
else -> "Unknown"
}
只要记住当用作表达式时包含 else 子句即可。编译器会彻底检查所有可能的路径,并且需要做到详尽无遗,这就是为什么 else 子句成为一个要求的原因。
你不局限于数字文字;您可以为分支使用多种类型,如清单 2-9 所示。
| -什么 | `readLine()`从控制台读取输入。现在不要担心问号;我们将在接下来的章节中讨论这个问题。 | | ➋ | 分支条件可以用逗号组合。 | | ➌ | 我们可以检查它是某个系列还是某个系列的成员。 | | -你好 | 当用作表达式时*需要 *else* 子句。* |fun main(args: Array<String>) {
print("What is the answer to life? ")
var response:Int? = readLine()?.toInt() ➊
val message = when(response){
42 -> "So long, and thanks for the all fish"
43, 44, 45 -> "either 43,44 or 45" ➋
in 46 .. 100 -> "forty six to one hundred" ➌
else -> "Not what I'm looking for" ➍
}
println(message)
}
Listing 2-9How to Write Branches Inside the When Construct
while 语句
由而和来做。。虽然语句的工作方式与它们在 Java 中完全一样——而且与 Java 一样,它们也是语句而不是表达式。我们不会在上花太多时间,而和会。。而在这里循环。
这里显示了 while 循环的一个基本用法,作为复习。
fun main(args: Array<String>) {
var count = 0
val finish = 5
while (count++ < finish) {
println("counter = $count")
}
}
对于循环
Kotlin 没有 Java 7 及更低版本中传统的循环的*——如下所示:*
for (int i = 0; i < 10; i++) {
statements
}
相反,Kotlin 的 for 循环处理有迭代器的东西。如果你见过 JavaScript、C#或 Java 8 中每个循环的*,Kotlin 的更接近于此。清单 2-10 中显示了一个基本示例。*
| -什么 | String 类的`split()`方法返回一个 *ArrayList* 类型,我们可以迭代它。 | | ➋ | 对于集合(`words`)中的每一项(`word`),我们; | | ➌ | 打印项目。 |fun main(args: Array<String>) {
val words = "The quick brown fox".split(" ") ➊
for(word in words) { ➋
println(word) ➌
}
}
Listing 2-10Basic for Loop
如果您需要使用循环的上的数字,您可以使用范围*。范围是一种表示整数算术级数的类型。范围是用rangeTo()函数创建的,但是我们通常以它的操作符形式使用它。。).为了创建一个从 1 到 10 的整数范围,我们这样写:*
var zeroToTen = 0..10
我们可以使用in关键字来执行成员资格测试。
if (9 in zeroToTen) println("9 is in zeroToTen")
要在 for 循环中使用范围,我们可以从清单 2-11 中所示的代码开始。
fun main(args: Array<String>) {
for (i in 1..10) {
println(i)
}
}
Listing 2-11Using Ranges in for Loop
异常处理
Kotlin 的异常处理与 Java 非常相似:它也使用了 try-catch-finally 构造。我们所学到的关于 Java 异常处理的任何东西都可以很好地用于 Kotlin。然而,Kotlin 通过简单地使用未检查的异常简化了异常处理。这意味着编写 try-catch 块现在是可选的。你可以做,也可以不做。让我们考虑清单 2-12 中显示的代码。
| -什么 | 我们可以在 Kotlin 中使用 Java 的标准库。 | | ➋ | 这个可能会抛出“ *FileNotFoundException* ”。 | | ➌ | 这可能会抛出“ *IOException* ”,但是 Kotlin 很乐意让我们编码,而不处理可能抛出的*异常*。 |import java.io.FileReader ➊
fun main(args: Array<String>) {
var fileReader = FileReader("README.txt") ➋
var content = fileReader.read() ➌
println(content)
}
Listing 2-12I/O Operations Without Try-Catch Blocks
虽然 Kotlin 让我们不必处理异常,但我们仍然可以这样做,在某些情况下,我们可能真的必须这样做。当发生这种情况时,只需像在 Java 中那样编写异常处理代码;参见清单 2-13 中的示例。
import java.io.FileNotFoundException
import java.io.FileReader
import java.io.IOException
fun main(args: Array<String>) {
var fileReader: FileReader
try {
fileReader = FileReader("README.txt")
var content = fileReader.read()
println(content)
}
catch (ffe: FileNotFoundException) {
println(ffe.message)
}
catch(ioe: IOException) {
println(ioe.message)
}
}
Listing 2-13Kotlin’s Try-Catch Block
处理空值
Java 中错误和昂贵的返工活动的一个常见来源可能是程序员处理空值的方式。我们中的一些人真的很勤奋,这样的防御性程序员,这种讨论可能没有必要了。但是并不是所有的程序员都是生来平等的,对于我们大多数人来说,我们需要被提醒要记住 NullPointerExceptions 的可能性。空值的处理在 Java 中是一个很大的问题,以至于 Kotlin 非常慎重地决定引入一个可空类型的概念。在 Kotlin 中,当我们声明一个变量
var str: String = "Hello"
str = null // won't work
我们永远无法将这个变量的值设置为 null。我们可以给它分配一个不同的字符串值,但是 Kotlin 保证str永远不会为空。如果出于某种原因,你真的需要这个变量为空,你必须明确地告诉 Kotlin,str 是一个可空的类型。为了使字符串(或任何类型)可空,我们使用问号符号作为类型的后缀,比如
var str: String? = "Hello"
在将类型声明为 Nullable 之后,我们现在必须做一些 Kotlin 曾经为我们做过的事情。对于非可空的类型,Kotlin 确保在诸如赋值、打印、包含在表达式中等操作中使用它们是非常安全的。当我们使类型可空时,Kotlin 假设我们知道自己在做什么,并且我们足够负责任地编写必要的保护条件来防止 NullPointerExceptions 。Kotlin 假设我们会做类似清单 2-14 中所示的代码。
| -什么 | 我们声明`Array`是可空的。这意味着我们可以将 null 传递给`printArr().` | | ➋ | 因为`arr`不再保证为非空,所以我们必须在执行一些涉及到`arr`局部变量的操作之前,手动检查空值。 | | ➌ | 如果`arr`不为空,我们可以安全地执行这个操作。 |fun main(args: Array<String>) {
var a = arrayOf(1,2,3)
printArr(null)
}
fun printArr(arr: Array<Int>?) { ➊
if(arr != null) { ➋
arr.forEach { i -> println(i) } ➌
}
}
Listing 2-14Demonstration of Nullable Types
Kotlin 引入了一个操作符,我们可以用它来处理可空的类型。它被称为安全调用操作符,写为问号符号后跟一个点?。
我们可以用一条语句替换执行空值检查的整个 if 块:
arr?.forEach { i -> println(i) }
安全调用所做的是首先检查arr是否为空;如果是,就不会经过 forEach 操作。只有当arr不为空时,才会遍历数组。
清单 2-15 显示了清单 2-14 的重构代码。
fun main(args: Array<String>) {
var a = arrayOf(1,2,3)
printArr(null)
}
fun printArr(arr: Array<Int>?) {
arr?.forEach { i -> println(i) }
}
Listing 2-15
Safe Call Operator
Kotlin 关于对象可空性的默认行为应该可以防止我们中的许多人做出让自己丢脸的事情,因为它不允许变量默认为空。然而,如果我们认为我们知道我们在做什么,某些情况会迫使我们使用可空的类型,我们仍然可以这样做。只要记得使用安全呼叫接线员;与使用 ifs 执行空检查相比,这是惯用的方法。
章节总结
-
Kotlin 的程序元素和 Java 并没有那么大的区别;它还有运算符、块、语句、表达式等。然而,在 Kotlin 中,一些在 Java 中被认为是语句的结构在 Kotlin 中是表达式,而一些在 Java 中被认为是表达式的结构在 Kotlin 中是语句(例如,赋值操作)。
-
Kotlin 的基本类型与 Java 的原始类型不同。Kotlin 的一切都是物体。
-
在 Kotlin 中有两种方法声明变量。当使用 var 关键字时,变量是可变的。当使用 val 关键字时,变量是不可变的。
-
Kotlin 中的字符串有迭代器。此外,在模板表达式的帮助下,它们更容易组合和组合。
-
当在 Kotlin 中声明变量时,默认情况下它们是不可空的,除非我们另外声明它们。
-
Kotlin 没有 switch 语句,但是它有一个 when 构造。
-
在 Kotlin 中,我们不必再编写 try-catch,因为它基本上使用了未检查的异常。
在下一章,你会发现:
-
如何(轻松地)在 Kotlin 中创建函数
-
为什么我们不需要在 kotlin 中做大量的方法 overlordn
-
我们如何摆脱编写实用函数,转而使用 Kotlin 的扩展函数(Java 没有这种功能)
三、函数
我们将介绍的内容:
-
声明函数
-
默认参数
-
命名参数
-
扩展函数
-
中缀函数
-
中缀运算符
Kotlin 的函数几乎与 Java 方法相同,尽管它在行为上更接近于 JavaScript 中的函数,因为在 Kotlin 中,函数不仅仅是一个命名的语句集合。在 Kotlin,职能是一等公民;你可以在任何可以使用变量的地方使用函数。您可以将它们作为参数传递给其他函数,也可以从其他函数返回函数。但是在我们深入这个主题之前,我们需要从 Kotlin 函数的基础开始——例如,它们是如何声明的,它们如何处理参数,它们与 Java 方法有何不同(或相似),以及一些其他细节。这就是我们将在本章讨论的内容。
声明函数
函数可以写在三个地方。你可以把它们写在(1)一个类中,像 Java 中的方法一样——这些被称为成员函数;(2)外部类——这些被称为顶级函数;(3)它们可以被写在其他函数中——这些函数被称为局部函数。不管你把函数放在哪里,声明它的机制不会有太大的变化。基本表单 a 函数如下:
fun functionName([parameters]) [:type] {
statements
}
使用保留字 fun 声明函数,后跟一个标识符,即函数名。函数名包含圆括号,您可以在其中声明可选参数。您也可以声明函数将返回的数据类型,但这是可选的,因为 Kotlin 可以通过简单地查看函数体声明来推断函数的返回类型。接下来是一对花括号,以及函数体中的一些语句。
您应该按照与编写 Java 方法相同的准则来命名您的函数——也就是说,函数名(1)不应该是保留字;(2)不能以数字开头;和(3)不应该有特殊字符。最后,从文体的角度来看,它的名字应该包含一个动词或表示一个动作的东西——而不是当你命名一个变量时,它的名字包含一个名词。清单 3-1 显示了一个函数的基本声明,它带有一个字符串和 Int 参数。为了便于比较,清单 3-3 显示了清单 3-1 的等价 Java 代码。
fun displayMessage(msg: String, count: Int) {
var counter = 1
while(counter++ <= count ) {
println(msg)
}
}
Listing 3-1displayMessage Function
清单 3-1 中的displayMessage()为非生产函数;它不返回任何东西——注意在函数体中没有一个 return 关键字。在 Java 中,当一个函数不返回任何东西时,我们仍然指明返回类型是 void(参见清单 3-3 )。然而,在 Kotlin 中,我们并不真的必须这样做,因为 Kotlin 能够进行类型推断——它可以自己找出答案。但是作为一个学术练习,让我们重写清单 3-1 来完整地告诉编译器displayMessage()有哪种返回类型。参见清单 3-2 中的代码示例。
fun displayMessage(msg: String, count: Int) : Unit {
var counter = 1
while(counter++ <= count ) {
println(msg)
}
}
Listing 3-2displayMessage With an Explicit Return Type
清单 3-1 和 3-2 的唯一区别是displayMessage()函数的单元返回类型。单元对应 Java 的 void 。
public class DisplayMessage {
public static void main(String []args) {
displayMessage("Hello", 3);
}
static void displayMessage(String msg, int count) {
int counter = 1;
while(counter++ <= count) {
System.out.println(msg);
}
}
}
Listing 3-3DisplayMessage in Java
为了调用displayMessage()函数,我们通过它的名字调用它并传递适当的参数,如清单 3-4 所示。
fun main(args: Array<String>) {
displayMessage("Hello", 3) ➊ ➋
}
fun displayMessage(msg: String, count: Int) {
var counter = 1
while(counter++ <= count ) {
println(msg)
}
}
Listing 3-4Calling the displayMessage Function
为了使函数更有效(返回一些东西),只需在函数体的某个地方放一个 return 语句,并声明函数的返回类型。参见清单 3-5 中的示例。
fun main(args: Array<String>) {
println(getSum(listOf(1,2,3,4,5,6)))
}
fun getSum(values: List<Int>) : Int { // return type is Int
var total = 0;
for (i in values) total += i
return total // return value
}
Listing 3-5
getSum, A Productive Function
你可以从函数中返回任何东西;我们不局限于基本类型。另一个例子见清单 3-6 。
| -什么 | 这个函数告诉编译器它返回一个*对*。一个*对*是一个数据类,它表示一个通用对。如果您以前使用过 Python,这可能会让您想起元组。 | | ➋ | 如果参数 *a* 大于 *b* ,那么我们使用参数 *a* 作为第一个组件,b 作为第二个组件来创建*对*,然后我们将它返回给调用者。 | | ➌ | 如果参数 *a* 小于 *b* ,那么我们使用参数 *b* 作为第一个组件,使用 *a* 作为第二个组件来创建*对*,然后我们将它返回给调用者。 | | -你好 | 可以将一对返回给赋值语句左侧的两个命名变量。这个析构声明允许我们一次将多个值保存到多个变量中。在这种情况下,变量 *x* 将接收返回对的第一个分量,变量 *y* 将接收*对的第二个分量*。 |fun bigSmall(a: Int, b:Int) : Pair<Int, Int> { ➊
if(a > b) return Pair(a,b) ➋
else {
return Pair(b,a) ➌
}
}
fun main(args: Array<String>) {
var (x,y) = bigSmall(5,3) ➍
println(x)
println(y)
}
Listing 3-6Using Pairs As a Return Type
单一表达式函数
在本章的前面,我们确实说过函数遵循基本形式
fun functionName([parameters]) [:type] {
statements
}
在 Kotlin 中有第二种形式的编写函数,它允许更简洁的语法。有些情况下我们可以省略(1)return语句;(2)花括号;(3)将返回式合在一起。这第二种形式叫做单表达式函数。正如您可能已经从其名称中推断出的那样,该函数只包含一个表达式,如下面的代码片段所示:
fun sumInt(a: Int, b: Int) = a + b
单个表达式函数省略了这对花括号,而是使用赋值运算符来代替。它也不再需要 return 语句,因为赋值右边的表达式自动成为返回值。最后,像这样的函数不需要显式的返回类型,因为编译器可以从 expression 的值推断出返回的类型。省略显式返回类型无论如何都不是一个硬性规则。如果你喜欢的话,你仍然可以写一个明确的返回,就像这样:
fun sumInt
(a: Int, b: Int): Int = a + b
默认参数
在 Kotlin 中,函数参数可以有默认值,这允许(函数的)调用者在调用点省略一些参数。通过为函数的参数赋值,可以将默认值添加到函数的签名中。清单 3-7 显示了这样一个函数的例子。
fun connectToDb(hostname: String = "localhost",
username: String = "mysql",
password:String = "secret") {
}
Listing 3-7
connectToDb
请注意,“localhost”、“mysql”和“secret”分别被分配给主机名、用户名和密码。可以像这样调用该函数:
connectToDb("mycomputer","root")
调用 connectToDb()函数的所有参数都可以省略,因为它的所有参数都有默认值。但在这种情况下,我们只省略了第三个。
我们甚至可以不传递任何参数就调用函数,就像这样:
connectToDb()
Kotlin 为函数提供默认参数的能力允许我们避免创建函数重载。我们不能在 Java 中做到这一点,这就是为什么我们不得不求助于方法重载。在 Kotlin 中重载函数仍然是可能的,但是我们现在可能没有什么理由这么做了,这都要感谢默认参数。
命名参数
让我们回到清单 3-7 。如果我们调用该函数并提供所有参数,调用可能如下所示:
connectToDb("neptune", jupiter", "saturn")
这是一个有效的调用,因为 connectToDb()的所有参数都是字符串,并且我们传递了三个字符串参数。你能发现问题吗?从调用站点看不出哪一个是用户名、主机名或密码。在 Java 中,这个不明确的问题通过各种变通方法得到了解决,包括注释调用站点。
connectoToDb(/* hostname*/, "neptune,
/* username*/ "jupiter",
/*password*/ "saturn")
我们不必在 Kotlin 中这样做,因为我们可以在调用点命名参数。
connecToDb(hostname = "neptune",
username = "jupiter",
password = "saturn")
重要的是要记住,当我们开始指定参数名称时,为了避免混淆,我们需要在其后指定所有参数的名称。此外,如果我们这样做,Kotlin 不会让我们编译。例如,像这样的呼叫
connectToDb(hostname = "neptune",
username = "jupiter",
"saturn")
因为一旦我们命名了第二个参数(username),我们就需要提供跟在它后面的所有参数的名称。在上面的调用示例中,第二个参数被命名,但第三个没有。另一方面,像这样的电话
connectToDb("neptune",
username = "jupiter",
password = "saturn")
是允许的。没关系,我们没有命名第一个参数,因为 Kotlin 会把它当作一个常规的函数调用,并使用参数的位置值来解析参数。然后我们给剩下的所有参数命名。
可变数量的参数
与 Java 一样,Kotlin 中的函数也可以接受任意数量的参数。语法与 Java 稍有不同,我们没有在类型...后使用三个点,而是使用 vararg 关键字。清单 3-8 展示了一个如何声明和调用 vararg 函数的例子。
fun<T> manyParams(vararg va : T) { ➊
for (i in va) { ➋
println(i)
}
}
fun main(args: Array<String>) {
manyParams(1,2,3,4,5) ➌
manyParams("From", "Gallifrey", "to", "Trenzalore") ➍
manyParams(*args) ➎
manyParams(*"Hello there".split(" ").toTypedArray()) ➏
}
Listing 3-8Demonstration of a Variable Argument Function
扩展函数
在 Java 中,如果我们需要向类添加功能,我们可以向类本身添加方法,或者通过继承来扩展它。Kotlin 中的扩展函数允许我们在不使用继承的情况下将行为添加到现有的类中,包括用 Java 编写的类。它本质上让我们定义一个可以作为类成员调用的函数,但是这个函数是在类之外实现的。为了演示这一点,让我们从一个简单的代码开始,chanthofy,terminatorify(如清单 3-9 所示);这是一个人为的应用,但它应该为我们探索扩展函数奠定基础。
fun main(args: Array<String>) {
val msg = "My name is Maximus Decimus Meridius"
println(homerify(msg))
println(chanthofy(msg))
println(terminatorify(msg))
}
fun homerify(msg: String) = "$msg -- woohoo!"
fun chanthofy(msg: String) = "Chan, $msg , tho"
fun terminatorify(msg: String) = "$msg -- I'll be back"
Listing 3-9homerify, chanthofy, terminatorify
清单 3-9 中的应用有三个函数,它们接受一个字符串参数,向它添加一些字符串,然后将它们返回给调用者;很简单。它本身是可用的,但是我们可以通过将所有这三个函数放在一个公共类中来进一步整合它,这个类将成为我们的工具类。这样一个类可能看起来像清单 3-10 中的代码。
fun main(args: Array<String>) {
val msg = "My name is Maximus Decimus Meridius"
val util = StringUtil()
println(util.homerify(msg))
println(util.chanthofy(msg))
println(util.terminatorify(msg))
}
/*
The StringUtil class consolidates our three methods as member functions.
This is a very common Java practice
*/
class StringUtil {
fun homerify(msg: String) = "$msg -- woohoo!"
fun chanthofy(msg:String) = "Chan, $msg , tho"
fun terminatorify(msg: String) = "$msg -- I'll be back"
}
Listing 3-10Our Very Own StringUtil Class
我们已经可以使用清单 3-10 中的代码;事实上,这是 Java 中非常常见的做法。虽然 Java 程序员可能已经将homerify(), chanthofy(),和terminatorify()实现为静态方法,而不是实例方法,就像我们在这里所做的那样,但是将有些相关的方法合并到一个工具类中(就像清单 3-10 中我们自己的 StringUtil 类)被认为是一个好主意。那是小事,我们可以放心地忽略它。关键是,在 Kotlin 中,我们可以用更简单的方式重写我们的方法,而不是为我们的三个方法编写一个工具类(参见清单 3-11 )。
fun String.homerify() = "$this -- woohoo!"
Listing 3-11homerify As an Extension Function
这看起来似乎很简单,但这确实是编写一个扩展函数所需要的。扩展函数引入了接收者类型和接收者对象的概念。清单 3-11 中接收器类型为串;这是我们想要添加扩展函数的类。接收器对象是该类型的实例,在我们的例子中是“我的名字是 Maximus Decimus Meridius ”。当您将一个扩展函数附加到一个类型上时,比如在我们的例子中是一个字符串,扩展函数可以使用关键字 this 引用 receiver 对象。对于所有意图和目的,扩展函数看起来就像在接收器类型上定义的任何成员函数。因此,扩展函数能够引用这个是有意义的。清单 3-12 显示了我们的扩展字符串类的完整代码。
fun main(args: Array<String>) {
val msg = "My name is Maximus Decimus Meridius"
println(msg.homerify())
println(msg.chanthofy())
println(msg.terminatorify())
}
fun String.homerify() = "$this -- woohoo!"
fun String.chanthofy() = "Chan, $this , tho"
fun String.terminatorify() = "$this -- I'll be back"
Listing 3-12Extended String Class
用 Kotlin 编写实用函数完全没问题,但是有了扩展函数,使用它们似乎更自然,因为它增加了代码的语义价值。使用扩展函数语法感觉更自然。
中缀函数
“中缀”符号是数学和逻辑表达式中使用的符号之一。它是操作数之间运算符的位置(例如,a+b;加号是“不固定的”,因为它在操作数 a 和 b 之间。相反,操作可以遵循“后固定”符号,其中表达式可以写成这样 (+ a b) ,或者它们可以是“后固定”,其中我们的表达式可以写成这样 (a b +)。
在 Kotlin 中,成员函数可以是“固定的”,这允许我们编写如下代码:
john say "Hello World"
如果约翰是一个变量,它指向一个类型为人的对象(我们一会儿会看到定义),并且说是一个采用字符串参数的方法,就像"Hello World",一样,那么上面的语句是一种更自然的写法
john.say("Hello World")
为了开始我们对中缀函数的探索,让我们从实现允许我们使用传统的点符号调用say()成员函数的代码开始。然后我们会写代码让我们使用这个内嵌版本。清单 3-13 展示了 Person 类的经典实现,我们可以用点符号来调用它。
fun main(args: Array<String>) {
val john = Person("John Doe")
john.say("Hello World")
}
class Person(val name : String) {
fun say(message: String) = println("$name is saying $message")
}
Listing 3-13Person Class Without infix Function
这并不奇怪,这种调用是我们大多数人在 Java 编程中接触到的。这不需要任何进一步的评论。现在,让我们看看允许我们以“内嵌”方式调用 say 方法的实现。
fun main(args: Array<String>) {
val john = Person("John Doe")
john say "Hello World"
}
class Person(val name : String) {
infix fun say(message: String) = println("$name is saying $message")
}
Listing 3-14Person Class With an infix Function
为了以“中缀”的方式使用say()函数,你需要做的唯一一件事就是在函数的开头添加中缀关键字,如清单 3-14 所示。话虽如此,你不能把每个函数都转换成中缀。只有在以下情况下,函数才能转换为中缀
-
它是一个成员函数(类的一部分)或扩展函数,并且
-
它只接受一个参数。如果你在考虑一个漏洞,比如“我可能会在函数中定义一个参数,然后使用 vararg”,那就不行。不允许将变量参数转换为中缀函数。
顺便说一下,您不能像这样使用命名参数调用中缀函数
john say msg = "Hello World" // won't work
记住中缀函数只接受一个参数;在调用点命名参数没有太大意义。
明智地使用中缀函数可以实现更直观的编码,因为它们可以将程序逻辑隐藏在类似关键字的语法后面。你可以用中缀符号创建某种元语言;小心不要过度。
运算符重载
在这一章里,运算符重载的话题似乎有点不合适,因为这一章讲的都是函数。但是在 Kotlin 中,这个主题很好地融入了中缀函数的讨论,因为它们在实现中有共同的机制,我们很快就会看到。
运算符重载允许我们适当地使用一些标准运算符,如数学运算符的加法、、除法、、乘法、和模。例如,我们可以编写一段代码,允许使用加号来添加两个 Employee 对象或任何其他自定义类型。考虑清单 3-15 中的代码。
fun main(args: Array<String>) {
var e1 = Employee("John Doe")
var e2 = Employee("Jane Doe")
var e3 = e1 + e2
println(e3.name)
}
Listing 3-15Adding Two Employee Objects
不知何故,我们直觉地知道陈述e3 = e1 + e3是什么意思;如果我们将一个 employee 对象添加到另一个对象中,那么我们应该得到雇员 e1 和 e2 的组合信息或状态——如果这是您希望能够在代码中完成的事情。从程序上来说,我们知道这个语句不应该工作,因为加法运算符不知道任何关于 Employee 对象的信息,更不知道如何对它们执行加法操作。然而,在 Kotlin 中,我们可以教加法运算符如何将两个 Employee 对象相加。这显示在清单 3-16 中。
class Employee(var name: String) {
infix operator fun plus(emp: Employee) : Employee { ➊
this.name += "\n${emp.name}" //
return this
}
}
Listing 3-16
class Employee
我们已经知道 infix 关键字会对函数产生什么影响。事实上加是一个中缀的函数,允许我们像这样写代码(见清单 3-16 ):
var e1 = Employee("John Doe")
var e2 = Employee("Jane Doe")
var e3 = e1 plus e2
然而,函数名加上并不是一个普通的函数名。这不仅仅是我们想出来的另一个名字。这对 Kotlin 来说有着特殊的意义。加函数名是一个固定标识符,对应数学运算符+。而当这个特殊的函数名与关键字中缀和运算符组合在一起时,它允许我们编写这样的代码
var e3 = e1 + e2
Kotlin 允许我们覆盖相当多的操作符,而且不仅限于数学操作符。表 3-1 显示了其中的一些。这不是一个完整的列表,但它应该让你知道你可以超载多少。
表 3-1
可以重载的运算符及其对应的函数名
|操作员
|
函数名
|
表示
|
翻译成
|
| --- | --- | --- | --- |
| + | Plus | a + b | a.plus(b) |
| - | Minus | a – b | a.minus(b) |
| / | Div | a / b | a.div(b) |
| * | Times | a * b | a.times(b) |
| % | rem | a % b | a.rem(b) |
| .. | rangeTo | a .. b | a.rangeTo(b) |
| ++ | inc | a++ | a.inc() |
| -- | dec | a-- | a.dec() |
| += | plusAssign | a += b | a.plusAssign(b) |
| -+ | minusAssign | a -= b | a.minusAssign(b) |
| /= | divAssign | a /= b | a.divAssign(b) |
| *= | timesAssign | a *= b | a.timesAssign(b) |
| %= | remAssign | a %= b | a.remAssign(b) |
| > | compareTo | a > b | a.compareTo(b) > 0 |
| < | compareTo | a < b | a.conpareTo(b) < 0 |
| >= | compareTo | a >= | a.conpareTo(b) >= 0 |
| <= | compareTo | a<= b | a.conpareTo(b) <= 0 |
运算符重载是多态的一个特例,不同的运算符,如数学运算符,可以根据参数(或操作数的类型)有不同的实现,正如我们在清单 3-14 和 3-15 中看到的。正确使用操作符重载可以产生更容易理解的代码,因为它们是用业务或对象域的语言编写的。它们有更高的语义价值。
Kotlin 并不是第一种实现运算符重载的语言。以前像 C++ 这样的语言也做过。应该注意的是,运算符重载的使用,或者更恰当地说,过度使用和滥用已经招致了很多批评。正是因为如果你能重新定义像加号、减号等众所周知的运算符的动作和行为。,它会导致难以处理的代码。因此,当您选择操作符重载的路线时,请行使良好的判断力。
章节总结
-
Kotlin 函数可以在三个地方编写。像在 Java 中一样,它们可以是类的成员,但也可以作为顶级构造来编写。第三,它们可以嵌入到其他函数中——我们在本章中没有深入研究本地函数,但是我们将在后面的章节中详细讨论这个主题。
-
Kotlin 通过添加对默认参数、命名参数甚至可变数量的参数的支持,使得声明和调用函数变得更加容易。位置参数、命名参数和默认参数的组合允许我们避免过度使用参数重载,就像我们在 Java 中所做的那样。
-
扩展函数提供了一种新的方式来扩展现有类型的行为。我们可以在类之外添加额外的行为,但是我们可以调用扩展函数,就好像它是被烘焙到类定义中一样。
-
中缀函数和中缀运算符允许我们在不使用点符号的情况下编写函数调用,从而增加了代码的语义值。通过允许函数调用被中缀 -ed,结果代码变得更有表现力,更接近领域语言。
在下一章,我们将看看 Kotlin 面向对象的一面。我们将学习 Kotlin 如何处理类、构造函数和接口。我们还将了解 Kotlin 中新的数据类。