Kotlin入门与实战

978-7-115-49876-2
作者: 向治洪
译者:
编辑: 陈聪聪

图书目录:

详情

本书内容由基础知识和项目实例构成,基础知识主要围绕基础语法和常见的概念进行讲解,让读者对Kotlin的语法结构有一个大概的了解,每个语法点的讲解都配备相应的实例。全书共分为18章,包括Kotlin入门与基础部分、Kotlin语法和Kotlin项目实例三部分。通过本书的讲解,读者可以对Kotlin语言有一个全面的了解,并将它运用到实际的项目中。

图书摘要

版权信息

书名:Kotlin入门与实战

ISBN:978-7-115-49876-2

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。


著    向治洪

责任编辑 陈聪聪

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


本书从Kotlin的发展史讲起,将理论知识和项目实例相结合,全面介绍Kotlin的基础与应用。通过阅读本书,读者可以迅速掌握Kotlin的基本操作,轻松应对使用Java开发所带来的技术难题。

本书内容共分为18章,由Kotlin入门与基础、Kotlin语法和Kotlin项目实例3个部分组成。第一部分(第1~3章)主要介绍了Kotlin的发展、Kotlin开发环境搭建、JVM语系和Kotlin各版本的重要特性。第二部分(第4~16章)详细解析Kotlin的基础语法。第三部分(第17、18章)集中演示了基础知识的实战运用。

本书适合前端开发人员、Android/iOS开发人员以及Java从业人员阅读。


JetBrains作为时下流行的IDE开发商,为软件领域提供了诸多的集成开发环境,比较著名的如IntelliJ IDEA、WebStorm和PyCharm等,这些IDE几乎覆盖了目前主流的编程语言。Kotlin正是由JetBrains团队开发的。作为一门现代的、多平台的静态编程语言,可以说Kotlin语言从一诞生就自带光环。

作为一门现代编程语言,Kotlin语言充分借鉴了Java、Scala、Groovy、JavaScript和Swift等诸多语言的优秀特性,可以说是集多家语言之大成的编程语言。同时,作为一款多平台的编程语言,开发人员可以使用Kotlin语言编写JVM、Android、iOS和浏览器应用程序。Kotlin可以将代码编译成Java字节码运行在JVM环境中,也可以编译成JavaScript运行在浏览器中,而且它可以编译成机器码直接运行在没有JVM的嵌入式设备上。

Kotlin语言的历史最早可追溯到2010年,当时JetBrains团队使用Java开发应用程序的时候遇到诸多问题,于是Kotlin语言应运而生。Kotlin项目在2011年开源并在2016年发布了第一个稳定运行的版本Kotlin 1.0,而Kotlin语言真正为人所熟知是在2017年的Google I/O大会上。在那次大会上,Google宣布将Kotlin作为Android开发的首选语言,逐步取代Java语言在Android开发中的地位。另外,作为当前最为流行的服务端开发框架之一,Spring框架也在5.0版本引入了对Kotlin的支持,再加上Kotlin对JavaScript环境的支持,可以说Kotlin为时下非常流行的多平台开发语言。

伴随着2018年Google I/O大会的召开,Kotlin已得到开发者社区的广泛认可,并在Android开发中扮演着越来越重要的角色。据Google官方统计,自2017年宣布支持Kotlin以来,超过35%的开发人员开始使用Kotlin开发Android应用程序,而且这个数字正在逐月递增,而Play Store中用Kotlin开发的应用也增长了6倍之多。同时,Google官方表示将会继续改善Kotlin在支持库、工具、运行时、文档以及培训中的开发体验,而且在新版本的Android P系统中对运行时(Android Runtime)进行了微调,以此加快Kotlin编写的应用的运行时间。

众所周知,在面向对象编程的年代,Java作为一门非常优秀的面向对象编程语言而为开发人员所熟知;但是在当前函数式编程的热潮中,和其他函数式编程语言相比,Java就显得有些笨重了。虽然Java在1.8版本之后逐步在向函数式编程靠拢,但是Java的历史包袱实在太重了,而作为一门新兴的现代编程语言,Kotlin不仅支持面向对象编程,而且支持函数式编程,另外Kotlin还可以100%地兼容Java程序,这对传统的Java开发人员来说是非常有吸引力的。

正如大家所熟知的那样,Google与Oracle的版权之争或许是Google选择Kotlin的原因之一,但Google选择Kotlin更多的还是因为Kotlin相比Java的技术优势,比如容易上手、语法简洁、空指针安全和跨平台开发等,可以说Kotlin就是为“颠覆”Java而创造的。说了这么多,相信大家对Kotlin的强大有了一定的认识。

“路漫漫其修远兮,吾将上下而求索”,通过本书的写作,我深刻地意识到学无止境的含义。写书的过程也是一个自我总结和学习的过程。如果本书对大家有所帮助和启发,我将不胜欣慰。

本书共分为18章,力图通过详细的讲解,帮助读者全面了解Kotlin语言,并将它运用到实际的项目中。本书主要内容如下。

Kotlin入门与基础部分(第1~3章)

这部分内容主要包括Kotlin语言简介、Kotlin开发环境搭建、JVM语系和Kotlin各版本重要特性。通过这部分知识的学习,读者可以对Kotlin有一个基本的认识。

Kotlin语法(第4~16章)

这部分内容是本书的核心内容,主要介绍Kotlin语言的基础语法,包含的内容有类与接口、扩展函数与属性、集合与泛型、对象与委托、反射与注解、Lambda表达式、协程、Kotlin DSL和Kotlin Native开发等。本部分配备了大量的实例,通过对这部分知识的学习,相信读者能够对Kotlin的语法有一个全面的认识。

Kotlin项目实例(第17、18章)

这部分内容是Kotlin实战的部分,是对前面章节所讲基础知识的实战运用。通过和Spring Boot框架的集成来介绍Kotlin在Web服务器开发方面的应用,通过Android视频播放器项目来介绍Kotlin在Android开发方面的应用。

本书是一本Kotlin入门与实战的图书,不管是前端开发者、Android/iOS开发者还是Java开发者,本书都适合读者阅读参考。同时,本书配备了大量的实例,在讲解理论的同时与实例相结合。相信通过本书的阅读,读者一定有所收获。


本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。

本书提供如下资源:

要获得以上配套资源,请在异步社区本书页面中点击,跳转到下载界面,按提示进行操作即可。注意:为保证购书读者的权益,该操作会给出相关提示,要求输入提取码进行验证。

作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。

当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,点击“提交勘误”,输入勘误信息,单击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。

我们的联系邮箱是contact@epubit.com.cn。

如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。

如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/selfpublish/submission即可)。

如果您是学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。

如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。

“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。

“异步图书” 是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。

异步社区

微信服务号



Kotlin是由JetBrains开发的针对JVM、Android和浏览器的静态编程语言,目前,在Apache组织的许可下已经开源。使用Kotlin,开发者可以很方便地开发移动Android应用、服务器程序和JavaScript程序。Kotlin可以将代码编译成Java字节码,也可以编译成JavaScript,以便在没有JVM的设备上运行。

Kotlin来源于一个岛屿的名字,这个小岛位于俄罗斯的圣彼得堡附近。之所以要命名为Kotlin,是因为Kotlin的主要开发工作就是由位于圣彼得堡的分公司团队完成的。

在纯命令式编程时代,Sun公司创造了Java和标准库,但一直以来Sun都没有为Java提供一款好用的IDE。在这样的局面下,IBM适时地推出了一款名为Eclipse的IDE,它成为对抗Sun公司的利器。

近十几年来,Eclipse凭借着开源和可扩展平台(OSGi),战胜了一个又一个对手(JBuilder、NetBeans等),奠定了Java标准开发环境的地位。然而,就像所有成功的产品一样,Eclipse一路荣光走来的同时也遇到了一系列的问题。所谓船大调头难,近年来,在JetBrains公司一系列产品的冲击下,Eclipse的市场份额正在慢慢下降。

作为目前广受欢迎的IDE提供商,JetBrains向开发者提供主流的软件开发环境:JavaScript、

.NET和Java等,如图1-1所示。这些IDE几乎覆盖了目前主流的编程语言。

在开发Kotlin之前,JetBrains团队一直使用Java来创建他们的IDE并进行业务逻辑开发。之所以开发Kotlin,是因为JetBrains 的工程师们在使用Java开发应用程序的过程中发现了大量的问题。为了提升开发效率,同时解决使用Java开发带来的问题,在借鉴了Scala、Groovy等语言后,他们决定开发一款致力于解决Java问题的编程语言Kotlin。

图1-1 JetBrains支持的编程环境

JetBrains深谙开发者的需求和痛处,在孜孜不倦地为开发者提供实用、高效的IDE的同时,也为开发者提供全新的编程语言以解决目前的技术问题。Kotlin的开发环境如图1-2所示。

图1-2 Kotlin支持的开发环境

作为一门全新的编程语言,从项目创建到1.2版本,Kotlin共经历了6年的发展,其发展历史大致如下。

面向过程的编程语言也称为结构化程序设计语言,是高级语言的一种,C语言是常见的面向过程语言。在面向过程程序设计中,问题被看作一系列需要完成的任务,函数则用于完成这些任务,解决问题的焦点集中于函数。

面向过程的概念最早由E.W.Dijkstra在1965年提出,是软件发展史上的一个重要里程碑。面向过程程序设计的主要特点是采用自顶向下、逐步求精的程序设计方法,使用3种基本控制结构构造程序,即任何程序都可由顺序、选择、循环3种基本控制结构构成。

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计流程,面向过程往往需要把一个复杂的函数切分为若干易于控制和处理的子函数,即通过把大块函数切割成小块函数来降低系统的复杂度。

面向过程的程序设计语言往往具有如下一些特点。

1.严格的语法

面向过程语言的每一条语句的书写格式都有着严格的规定。

2.接近自然语言

机器语言程序晦涩难懂的原因主要有两个,一是机器语言使用二进制数来表示机器指令的操作码和存放操作数的存储单元地址,二是每一条机器指令只能执行简单操作。而面向过程语言为了达到简化程序设计过程的目的,对语法做了大量的改进和创新,使语句的格式尽量接近自然语言的格式,同时能够用一条语句来描述运算的过程。因此,接近自然语言的语法格式和描述运算过程的方法是面向过程语言的一大特色。

3.模块化设计

在面向过程编程设计中,一个程序可以分解为多个函数,通过函数调用过程,可以用一条函数调用语句来实现复杂函数的运算过程,从而降低系统的复杂度。

4.与编译器相关

虽然面向过程语言与计算机硬件结构无关,但用于将面向过程语言程序转换成机器语言程序的编译器与计算机硬件是有关的,在计算机硬件系统中,每一种计算机都有一个独立的编译器用于将面向过程语言程序转换成对应的机器语言程序。

因此,只有具备了将面向过程语言程序转换成对应的机器语言程序的编译器,面向过程语言程序才能在该计算机上运行。对于同一面向过程语言程序,只要经过不同计算机对应编译器的编译,就可在其他计算机上运行,这种特性称为程序的可移植性。

5.解决问题的步骤

虽然面向过程语言中的语句功能比机器指令和汇编指令要强得多,但还是无法用一条语句来完成复杂运算过程所需的全部步骤,仍然需要将完成复杂运算的过程细化为一系列步骤,每一个步骤可以用一条语句描述执行。简单来说,面向过程语言程序设计就是用语句来描述解决问题过程的一系列步骤。

相比于面向对象的编程语言,面向过程的编程语言执行效率更高,因而被大量应用在单片机、嵌入式和Linux/UNIX系统开发中。而不足的是,面向过程语言难复用、易扩展,因而不适合开发大型软件系统。

面向对象编程(Object Oriented Programming,OOP)是一种程序设计思想,OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。在面向对象的程序设计语言中,一切事物皆对象,通过面向对象的方式,将现实世界的事物抽象成对象,现实世界中的关系抽象成类、继承,帮助人们实现对现实世界的抽象与数字建模。

面向对象编程作为一种新的程序设计思想,其本质是通过建立模型来体现抽象思维过程和面向对象的方法。该模型作为现实世界中事物的抽象表现,不可能反映客观事物的一切具体特征,只能对具有相同特征和表现的事物进行抽象。

面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息并处理它们,计算机程序的执行就是一系列消息在各个对象之间传递的过程。在面向对象编程中,主要的内容包括对象、类、数据抽象、继承、动态绑定、数据封装、多态性和消息传递等。

谈到面向对象编程,就不得不说一说面向对象的三大特征:封装性、继承性和多态性。

1.封装性

所谓封装,就是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部的信息,而只能通过该类所提供的方法来实现对内部信息的操作与访问。

封装隐藏了数据的内部信息,是保证程序和数据不受外部干扰和破坏的一种安全机制,同时,封装后代码可供多处调用,减少了程序的耦合度。

2.继承性

继承是面向对象的又一重要特征,一个对象可以直接使用另一个对象的属性和方法,继承可以实现代码的重用。和大多数编程语言一样,Kotlin只能单继承,但允许有多层继承。同时,子类只能继承父类中非私有的属性和方法,构造方法不能被继承,子类可以重写父类的方法,也可以命名与父类同名的成员变量。

3.多态性

多态又称为多种形态或者多种实现,是面向对象程序设计的一个重要特征,是同一函数在不同情况下的不同实现。多态是对继承的一种扩展,增强了程序的灵活性和重用性,减少了代码的耦合度。

面向对象编程作为一种程序设计范型,同时也是一种程序开发的方法,已经被大量应用到软件生产中。正如面向过程程序设计对于结构化程序设计的技术提升,面向对象程序设计方法使得设计模式的契约式设计和建模语言技术也得到了一定改善。

在这个程序复杂的编程语言时代,很多人大谈特谈Java语言,殊不知JVM(Java虚拟机缩写)才是Java生态的核心。JVM是一个优秀的VM环境,在这个基础上诞生了很多优秀的编程语言(如Groovy、Scala和Kotlin等),它们都声称要“变革Java”,Kotlin就是其中的一个。

在设计之初,JVM只是为了支持Java编程,然而,随着时间的推移,越来越多的语言开始运行在JVM之上。在最新的JVM语系排名中(Java除外),Kotlin的市场份额仅次于Groovy 和Scala,再加上近年来Google对Kotlin的大力推动,Kotlin的发展潜力势必不可小觑,如图1-3所示。

图1-3 JVM语系使用率占比

下面简要介绍JVM语言家族的几个明星语言,为大家学习Kotlin提供一些参考。

1.Groovy

作为一款基于JVM开发的敏捷语言,Groovy结合了Python、Smalltalk和Ruby等语言的强大特性,同时Groovy代码能够与Java代码实现很好的融合。由于它运行在JVM之上,因此Groovy还可以使用其他JVM语系语言编写的库。

2.Scala

Scala是一门多范式的静态类型编程语言,它混合了面向对象编程(OOP)和函数式编程(FP)的诸多特点。这意味着Scala程序能够被编写成许多不同的风格:纯函数式风格、非纯函数式风格或混合式风格。同时,使用Scala编写的代码可以编译成Java字节码,方便运行于JVM之上,并可以调用现有的Java类库。

3.Kotlin

Kotlin是由JetBrains团队开发的静态类型编程语言,作为JVM语系的重要成员,Kotlin除了可以运行在JVM环境上之外,还可以绕过JVM直接编译成机器码供系统运行。与其他语言相比,Kotlin借鉴了很多Java的语法规则和思想,对于熟悉Java环境的开发者来说是非常容易上手的。JetBrains推荐以循序渐进的方式来开发项目,这就意味着允许项目中同时存在Java和Kotlin代码文件,而且允许Java和Kotlin之间的互调。这使得Java开发者可以很方便地在已有项目中引入Kotlin,从而循序渐进地完成Java向Kotlin的过渡。

Kotlin是一门依赖Java虚拟机运行的计算机语言,因此初学者有必要了解一下Java虚拟机的知识及其运作原理。

Java应用程序能够跨平台运行,主要是通过Java虚拟机实现的。如图1-4所示,不同软硬件平台的Java虚拟机是不同的,Java虚拟机向下是不同的操作系统和CPU硬件,使用或开发时需要下载不同的JRE或JDK版本;Java虚拟机向上是Java应用程序,Java虚拟机屏蔽了不同软硬件平台,Java应用程序不需要作出任何修改,也不需要重新编译就可以直接在其他平台上运行。

图1-4 Java虚拟机模型

想要了解Kotlin应用程序的运行过程,需要先了解Java应用程序的运行过程。

对于Java应用程序而言,首先由Java编译器将Java源文件(.java文件)编译成为字节码文件(.class文件),这个过程可以通过JDK提供的javac命令来完成;当需要运行Java字节码文件时,由Java虚拟机中的解释器将字节码解释成为机器码后执行,这个过程通常由JRE提供的Java命令行来完成,如图1-5所示。

与Java程序的运行过程类似,Kotlin应用程序的运行过程如图1-6所示。首先由Kotlin编译器将Kotlin源文件编译成为字节码文件(* Kt.class文件),注意,这个过程中文件名会发生变化,会增加Kt后缀。编译过程可以通过Kotlin编译器提供的kotlinc命令来完成。当运行Kotlin字节码文件时,由Java解释器将字节码解释成为机器码去执行,这个过程也是通过Java命令完成的,但需要Kotlin运行时库支持才能正常运行。

图1-5 Java应用程序执行过程

图1-6 Kotlin应用程序执行过程

如今,在软件开发领域,除了现在比较热门的人工智能技术和大数据技术之外,移动开发依然是受到大家关注的领域分支,而移动开发中非常重要的一门技术就是Android开发。Java作为Android开发的官方标准语言,其语法的设计思想还停留在纯命令式语言时代,在函数式语言的大背景下,Java显得有点落伍。虽然随着Java 8和Java 9版本的发布,Java也在向函数式语言靠近,但是Java 8和Java 9的市场份额并不高,很多项目还停留在Java 6的时代。特别是对于Android开发者来说,想体验函数式编程的优势似乎太过遥远。所以,使用Kotlin成为一个不错的选择。

作为一款明星语言,在被Android官方宣布为支持语言之前,Kotlin早已是Android开发中的热门话题了,很多开源的项目都使用Kotlin开发。也正是基于这些原因,在2017年的Google I/O大会上,Google将Kotlin列为Android开发的官方支持语言。在最近TIOBE的热门编程语言排行榜中,它一度进入编程语言前50名,在Github上也获得了很高的关注度。相信随着版本的持续发布和性能的优化,Kotlin一定会走得更远。

在创造Kotlin的过程中,其作者从很多编程语言那里获得了灵感,比如Java、Scala、Groovy,可以说是博采众长。使用Kotlin编写程序,不仅编译运行速度快,而且实现相同功能的代码量远远小于Java。

使用Kotlin主要有以下优势。

1.跨平台开发能力

因为Kotlin是基于JVM开发的,所以它同时具备了Android 开发、Web浏览器开发和原生Native开发的能力。在原生Native开发方面,目前Kotlin官方在Github上开源了Native的源码。在Web开发方面,Kotlin可以结合Spring框架使用,也可以编译生成JavaScript模块,便于在一些JavaScript的虚拟机上编译运行。

2.开源

20世纪80年代,自由软件运动拉开序幕,开源软件为软件行业的快速发展提供了源源不断的动力。众人拾柴火焰高,对于一门新技术、新语言,开源可以帮助开发者更快速地发现Bug,从而利用开源的力量更快地推动新技术的发展。在开源领域方面,Java就是一个很好的例子,从语言标准到Core API、虚拟机、开发者工具,都能找到开放的影子。

3.空指针安全

在使用Java编程的过程中,大家聊得最多的话题莫过于如何避免空指针异常(NullPointerException)。针对空指针问题,Kotlin有专门的语法来避免。

4.完全兼容Java

Kotlin能够和Java达到100%互通,也就是说,使用Kotlin,依旧可以调用 Java已有的代码或库,也可以同时使用Java和Kotlin来混合编写代码。同时,为了方便项目的过渡,JetBrains提供的开发工具可以很简单地实现Java代码到Kotlin的转换。

5.语言简洁,学习成本低

Kotlin语法简洁直观,看上去非常像Scala,但更简单易学。同时,Kotlin使用了大量的语法糖,使得代码更加简洁。Kotlin并不遵循特定的编程规范,它借鉴了函数式风格和面向对象风格的诸多优点。

6.支持Lambda表达式

函数式编程是软件技术的发展方向,而Lambda是函数式编程的基础内容,因此,在Java 8版本中加入Lambda表达式本身是符合技术发展方向的。引入Lambda,一个直观的改进是,不用再写大量的匿名内部类。事实上,还有更多由函数式编程本身特性带来的性能。比如,代码的可读性会更好、高阶函数引入了函数组合的概念等。

除此之外,因为Lambda的引入,集合操作也得到了极大的改善,比如引入Stream API,把map、reduce、filter这样的基本函数式编程概念与Java集合结合起来。在大多数情况下,Java程序员在处理集合时,可以告别繁杂的for、while、if等逻辑语句。

不过,使用Lambda表达式并非只有好处,根据C#之父Anders Hejlsberg所说,未来的编程语言将逐渐融合各自的特性,而不会只存在单纯的声明式语言或者函数编程语言。

7.类型推断

使用Kotlin编程,开发人员不必为每个变量明确指定类型,编译器可以在编译的时候推导出某个参数的数据类型,从而使得代码更为简洁。Java在Java 8版本中也推出这一功能。

8.支持自定义的DSL

DSL(Domain-Specific Language),中文含义是领域特定语言,指的是专注于特定问题领域的计算机语言。不同于通用的计算机语言,领域特定语言只用于某些特定的领域。

DSL语言能让开发者以一种更优雅、更简洁的方式来表达和解决领域问题。例如,Gradle就是一种用Groovy定义的DSL。Kotlin的Lambda系统使其成为创建DSL的理想选择。

9.IDE环境的支持

作为JetBrains旗下的产品,JetBrains旗下众多的IDE可以为Kotlin开发提供无缝支持,并相互协作,协同发展。

可以用图1-7来大致总结Kotlin的主要优势。

图1-7 使用Kotlin的主要优势

正是因为Kotlin的这些优势,在2017年的Google I/O大会上,Google将Kotlin列为Android开发的第一语言。在旧金山举行的第一届Kotlin专题会议上,Kotlin官方宣布将会在1.2版本中添加支持iOS开发的功能。这难道是要成为全栈语言的节奏吗?

作为面向对象编程时代的明星编程语言,Java在开放的生态环境下,赢得了Oracle、Google、Apache、Eclipse基金会等各大厂商的支持,这些厂商的支持加快了Java生态圈的建设。一时间Java的生态圈异常繁荣,各种优秀的开源框架层出不穷,较为著名的有Spring Boot、Spring Cloud、Hadoop、Spark和Kafka等。

虽然Java的生态圈异常强大,但是作为纯命令式语言时代的产物,Java和当前流行的函数式编程语言相比,在类型、语法与编程范式方面显得越来越落后。所以,寻找一种既可以突破Java的这些局限,又可以与Java和谐共处的语言成为软件行业努力的方向。不过庆幸的是,Java之父詹姆斯·高斯林在创造Java语言时就想到了这些问题,所以在设计之初,就有意将Java语言与运行时环境JVM进行分离。JVM负责与操作系统的交互,屏蔽了具体操作系统的细节,这使得基于JVM开发的系统可以运行在任何操作系统之上。如今众多的新兴语言都运行在JVM上,Groovy、Scala、Kotlin、Clojure算得上是其中的佼佼者。

那么,Kotlin 相比Java有哪些优势呢?

当然,除了上面提到的一些优势之外,Kotlin还具有很多现代静态编程语言的编程特点,如类型推断、多范式支持、可空性表达、扩展函数、DSL 支持等,而这些功能Java在最近的版本才陆续添加。

另外,对于Android开发来说,Kotlin还提供了Kotlin Android扩展和Anko库。其中,Kotlin Android扩展是编译器扩展,可以让开发者摆脱代码中繁杂的 findViewById() 调用并将其替换为合成的编译器生成的属性。Anko 是JetBrains开发的围绕Android API的包装器库,目的是替代传统XML方式构建UI布局。

Kotlin是JetBrains推出的针对JVM、Android和Web浏览器的静态编程语言。作为一个真正的跨平台语言,Kotlin不仅可以编译成Java字节码,还可以编译成JavaScript,甚至还能作为运行脚本被编译成二进制程序,方便在没有JVM的设备上运行。

考虑到Android和Java之间的版权问题,一直以来Google都在寻找一门可以替换Java的编程语言,而Kotlin就是不二之选,随着Google将Kotlin确认为Android开发的第一语言,Kotlin的发展步入了快车道。相信随着更多开发者的加入,背靠大山的Kotlin一定会发展得越来越好。


很多使用过Java或者其他编程语言的读者都知道,学习这些语言之前,必须搭建好相关的开发和编译环境,这让很多初学者摸不着头脑,甚至可能会放弃学习。不过,Kotlin提供的在线运行环境有效地解决了初学者入门的问题,使用官方提供的在线运行环境,开发者不需要进行任何配置就可以在线运行和测试Kotlin代码。

读者可以在线体验Kotlin语言的魅力,运行效果如图2-1所示。

图2-1 Kotlin在线运行体验

Kotlin提供的在线运行环境可以说异常强大。在这个页面左侧,官方为初学者提供了大量实用的例子,单击左边的某个示例代码,然后单击右上角的【Run】按钮就会看到运行结果了。

除此之外,初学者还可以通过【Run】按钮前面的下拉列表框来选择运行环境,并且使用页面右下角的下拉列表框来选择Kotlin代码的版本。为了让不熟悉Kotlin语法的初学者尽快上手,在线运行环境还提供了Convert from Java的功能。下面我们举一个例子,首先准备一段代码,用来求数组的最大值。

public class Main {  

    public static void main(String[] args) {  
         int a=3,b=8;  
         System.out.println("最大值为:"+maxData(a,b));  
    }  
    //判断最大值  
    public static int maxData(int a,int b){  
         int max=a>b?a:b;  
         return max;  
    }  
}

现在将Java代码复制到窗口左边的文本框中,然后单击【Convert to Kotlin】按钮,后台系统就会将Java代码转换成Kotlin代码了,如图2-2所示。然后将转换后的Kotlin代码复制到图2-3的区域中,单击【Run】按钮即可看到运行结果,执行结果如图2-3所示。

图2-2 Java代码转换为Kotlin代码

图2-3 执行转换后的代码运行结果

当然,这种方式只是为了辅助初学者学习Kotlin,如果想要更深入地学习Kotlin相关的知识并使用Kotlin进行项目开发,还是建议读者从基础语法开始学习。

自2016年3月Kotlin 1.0正式版本发布以来,Kotlin的发展进入了黄金时期并先后发布了1.1和1.2两个版本,带来了一些新的功能和特性。

从Kotlin 1.1版本开始,JavaScript不再被当作是实验性的目标平台, Kotlin开始全面支持前端开发环境,从以下几点可以体现。

1.统一的标准库

目前,Kotlin标准库的大部分代码都可以编译成JavaScript来使用,一些关键类和方法都可以在Kotlin依赖包中找到,如集合(ArrayList、HashMap)、异常(IllegalArgumentException)和其他关键类(StringBuilder、Comparator)。

2.external修饰符

如果想要以类型安全的方式在Kotlin中访问JavaScript的实现类,可以使用 external 修饰符来声明Kotlin。与JVM目标平台不同,JavaScript平台允许对类和属性使用external修饰符,这是Kotlin对JavaScript平台提供的特有功能。使用external修饰符声明一个Kotlin的代码如下。

external class Node { 
val firstChild: Node
fun appendChild(child: Node): Node 
fun removeChild(child: Node): Node 
 … //其他
}

3.改进的导入操作

Kotlin优化了JavaScript模块的导入方式,可以通过在外部声明上添加@JsModule(模块名)注解来导入声明,该注解会在编译期间将模块导入到模块系统中。例如,要想在CommonJS中将声明作为模块或者全局的JavaScript对象导入,可以使用@JsNonModule 注解。下面是将JQuery声明导入Kotlin模块的相关代码。

external interface JQuery {
   fun toggle(duration: Int = definedExternally): JQuery
   fun click(handler: (Event) -> Unit): JQuery
}

@JsModule("jquery")
@JsNonModule
@JsName("$")
external fun jquery(selector: String): JQuery

在这种情况下,Kotlin的JQuery模块被注解声明为jquery ,如果要在其他程序中使用Kotlin的JQuery模块,可以使用jquery 的模块导入。示例代码如下。

fun main(args: Array<String>) {
    jquery(".toggle-button").click {
        jquery(".toggle-panel").toggle(300)
    }
}

在Java 1.7和1.8版本中,JVM引入了很多新的功能和概念,Kotlin也在第一时间对这些功能提供了支持。

1.提供Java 8字节码支持

在不改变字节码语义的情况下,使用命令行选项“-jvm-target 1.8”,可以将Kotlin编译生成Java 8字节码。

2.Java 8标准库支持

在此版本中,Kotlin新增了全面支持 Java 7和Java 8标准库的独立版本,如果想要访问这些新的API,可以使用kotlin-stdlib-jre7或kotlin-stdlib-jre8 的Maven构件,而不是使用标准的 kotlin-stdlib。

3.字节码中的参数名

现在,Kotlin支持在字节码中存储参数名,如果要使用这个功能,可以通过命令行选项-java-parameters来开启。

4.可变闭包变量

在Lambda表达式中,捕获可变闭包变量的装箱类不再需要volatile字段,虽然可变闭包变量可以提高性能,但在一些罕见的情况下可能会导致新的竞争条件,在这种情况下,可以使用同步机制来访问变量。

5.javax.script支持

Kotlin集成了javax.script 的相关API,该API允许在运行时进行赋值操作。下面是示例代码。

val engine = ScriptEngineManager().getEngineByExtension("kts")!!
engine.eval("val x = 3")
println(engine.eval("x + 2"))  // 输出结果为 5

6.kotlin.reflect.full

为了在以后的版本中支持Java 9,kotlin-reflect.jar库中的扩展函数和属性已移动到 kotlin.reflect.full 包中,旧的kotlin.reflect中的内容将在1.2版本中被弃用。

Kotlin 1.1带来的关键特性之一就是协程,它带来了async/await、 yield 以及类似的编程风格。对于一些耗时的操作(如CPU或GPU密集型任务等),协程提供一种既可避免阻塞又廉价可控的操作方式:协程挂起(coroutine suspension)。协程将复杂的异步操作放入底层库中,程序逻辑可顺序表达,以此来简化异步编程。该底层库可以将用户的代码包装为回调/订阅事件,在不同线程(甚至不同机器)上调度执行。

和线程/进程相比,协程是通过编译技术来实现的,不需要虚拟机VM和操作系统OS的支持。更重要的是,协程挂起可由用户来控制,也无须上下文切换和OS操作。而协程和线程/进程另一个不同的地方是,协程不能在随机指令中挂起,而只能在挂起点(即调用标记函数)挂起。

下面是kotlinx.coroutines 外部库中 async/await的部分实现代码。

// 在后台线程池中运行代码
fun asyncOverlay() = async(CommonPool) {
    // 启动两个异步操作
    val original = asyncLoadImage("original")
    val overlay = asyncLoadImage("overlay")
    // 然后应用叠加到两个结果
    applyOverlay(original.await(), overlay.await())
}
// 在页面上下文中启动新的协程
launch(UI) {
    // 等待异步叠加完成
    val image = asyncOverlay().await()
    // 然后在页面中显示
    showImage(image)
}

在上面的代码中,async函数启动一个协程。当调用asyncOverlay().await()时,挂起执行的协程,转而执行正在等待的操作,在操作完成后协程恢复。

除此之外,通过对yield和yieldAll函数的使用还可以支持惰性生成序列。在生成的序列中,每个元素在被取回前挂起代码块,并在下一次元素请求时恢复。下面是使用yieldAll 函数生成序列的例子。

import kotlin.coroutines.experimental.*

fun main(args: Array<String>) {
    val seq = buildSequence {
       for (i in 1..4) {
          // 产生一个 i 的平方
          yield(i * i)
       }
       // 产生一个区间
       yieldAll(26..28)
    }

    // 输出该序列
    println(seq.toList())
}

上面的例子输出结果如下。

[1, 4, 9, 16, 26, 27, 28]

协程是Kotlin的重要特性之一,本节只是粗略地介绍了协程相关的知识,后面的章节会重点介绍。

Kotlin 1.1版本添加了很多有用的标准库,为Kotlin开发带来了很多的便利。

1.字符串到数字的转换

String类中添加了一些新的扩展,比如使用它将String转换成数字而不会抛出异常,提供的方法有String.toIntOrNull():Int?、String.toDoubleOrNull(): Double?等。代码示例如下。

val port = System.getenv("PORT")?.toIntOrNull() ?: 80

除此之外,该标准库还提供了整数转换函数,如String.toIntOrNull()。

2.onEach()

onEach 是一个小而精的扩展函数,对于集合和序列很有用处,它允许对操作链中的每个元素执行一些特殊的操作,也可以使用forEach函数进一步返回可迭代实例。

inputDir.walk()
        .filter { it.isFile && it.name.endsWith(".txt") }
        .onEach { println("Moving $it to $outputDir") }
        .forEach { moveFile(it, File(outputDir, it.toRelativeString(inputDir))) 
}

3.groupingBy()

此API可以用于按照键对集合进行分组,同时折叠每个组。例如,下面的例子使用groupingBy()统计文字的使用频次。

fun main(args: Array<String>) {
    val words = "one two three four five six seven eight nine ten".split(' ')
    val frequencies = words.groupingBy { it.first() }.eachCount()
    println("Counting the letters: $frequencies.")
}

上面的代码输出结果如下。

[Counting the letters: {o=1, t=3, f=2, s=2, e=1, n=1}]

4.map.toMap()和map.toMutableMap()

这两个函数专门用来复制映射,代码示例如下。

class ImmutablePropertyBag(map: Map<String, Any>) { 
private val mapCopy = map.toMap() 
}

5.列表实例化函数

类似于Array构造函数,下面是创建 List 和 MutableList 的实例。

fun main(args: Array<String>) {
    val squares = List(10) { index -> index * index }
    val mutable = MutableList(10) { 0 }
    println("squares: $squares")  //输出结果:[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    println("mutable: $mutable")  //输出结果:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}

6.抽象集合

这些抽象类可以在实现Kotlin集合类时用作基类。在只读集合实现上主要有AbstractCollection、AbstractList、AbstractSet和AbstractMap;在可变集合实现上主要有AbstractMutableList、AbstractMutableSet、AbstractMutableMap和AbstractMutableCollection。

7.数组处理函数

现在,Kotlin标准库提供了一组用于逐个元素操作数组的函数,例如,比较(contentEquals和contentDeepEquals)、散列码计算以及数组转换为字符串操作。代码示例如下。

fun main(args: Array<String>) {
    val array = arrayOf("a", "b", "c")
    println(array.toString())  //输出结果:[Ljava.lang.String;@5305068a
    println(array.contentToString())  //格式化列表,输出结果:[a, b, c]
}

Kotlin 1.2版本的一个重要的特性是跨平台,它增加了Kotlin在JVM和JavaScript之间重用代码的可能性,以及Kotlin 1.1版本对JavaScript的支持。也就是说,使用Kotlin编写的公共代码,可以在所有的应用程序(包括后端、JavaScript前端和Android移动应用程序)中重复使用。

多平台是Kotlin 1.2中的一个全新实验性功能,允许开发者使用相同的代码库来构建不同平台下的应用程序。这些平台包括Kotlin-JVM、JavaScript和Android移动客户端。在这个多平台项目中,主要有3种模块。

通用(common)模块:包含可以在多个平台运行的公共代码,以及不附带依赖于平台的API实现的声明。

平台(platform)模块:包含特定平台的通用模块与平台相关声明的实现,以及其他平台相关代码。

常规(regular)模块:针对特定平台,既可以是平台模块的某些依赖,也可以是依赖的平台模块。

通用模块只能依赖于其他常见的模块和库,而且通用模块仅包含Kotlin代码,不能有任何其他语言的代码。平台模块可以依赖给定平台的任何可用模块和库(包括用于JVM和JavaScript库中的Java库),其中Kotlin多平台项目复用模型如图2-4所示。

图2-4 Kotlin多平台项目复用模型

编译通用模块将生成一个特殊的元数据文件,该文件包含所有通用模块的声明。编译平台模块则包含平台模块代码以及它为通用模块生成的特定于目标平台的代码(如JVM字节码或JavaScript代码)。

从Kotlin 1.2版本开始,多平台项目必须用Gradle来进行构建,而不支持其他构建方式。具体来说,在IntelliJ IDEA中依次选择【File】→【New Project】→【Gradle】→【Kotlin (Multiplatform…】,新建一个Gradle多平台项目,如图2-5所示。

如果需要添加其他模块,可以在项目上单击右键来添加,如图2-6所示。

如果使用手动的方式搭建多平台项目,可以参考以下步骤。

图2-5 使用Gradle构建多平台项目

图2-6 为多平台项目添加Module

(1)将Kotlin Gradle插件添加到buildscript脚本路径中,例如classpath "org.jetbrains.kotlin: kotlin-gradle-plugin:$ kotlin_version"。

(2)将kotlin-platform-common插件应用于通用模块中。

(3)将kotlin-stdlib-common相关性添加到公共模块中。

(4)将kotlin-platform-jvm和kotlin-platform-js插件应用到JVM和JavaScript的平台模块中。

(5)expectedBy从平台模块向通用模块添加范围从属关系。

以下是公共模块build.gradle的完整配置脚本。

buildscript {
    ext.kotlin_version = '1.2.20'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin-platform-common'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
    testCompile "org.jetbrains.kotlin:kotlin-test-common:$kotlin_version"
}

以JVM平台模块为例,下面是一个JVM模块的build.gradle配置文件,需要特别注意配置中的expectedBy行。

buildscript {
    ext.kotlin_version = '1.2.20'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin-platform-jvm'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    expectedBy project(":")   //依赖关系
    testCompile "junit:junit:4.12"
    testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
    testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
}

Kotlin多平台项目的一个关键功能是通用代码依赖于特定平台的声明方式。在其他语言中,可以通过在通用代码中构建一组接口,并在平台特定的模块中实现这些接口来实现。然而,对于Kotlin这种跨平台项目来说,这种方法并不理想。为了达到多平台代码复用的目的,Kotlin提供了一种预期和实际的声明机制。通过这种机制,通用模块可以定义一个预期的声明,然后在平台模块根据提供的预期声明来实现实际声明。

具体使用时,可以通过expected以及actual方式来声明通用代码与平台特定部分的依赖关系,其中,expected用于声明一个指定的API(类、接口、注释、顶层声明等),actual用于声明与API相关的平台实现或是用于实现在外部库中对API的现有别名引用。下面是官方提供的一个关于多平台项目实现的例子。

//通用模块
expect class Foo(bar: String) {
    fun frob()
}

fun main(args: Array<String>) {
    Foo("Hello").frob()
}

相应的JVM平台模块的实现如下。

//JVM特定模块
actual class Foo actual constructor(val bar: String) {
    actual fun frob() {
        println("Frobbing the $bar")
    }
}

学习Kotlin的多平台特性时应注意以下几点。

预期声明不仅限于接口和接口成员,还可以使用expect修饰符修饰其他声明,包括顶级声明和注释等。如果有一个特定平台库,现在希望在通用代码中使用这个库并为另一个平台提供自己的实现,则可以使用typealias来修饰实际声明。代码示例如下。

expect class AtomicRef<V>(value: V) {
  fun get(): V
  fun set(value: V)
  fun getAndSet(value: V): V
  fun compareAndSet(expect: V, update: V): Boolean
}

actual typealias AtomicRef<V> = java.util.concurrent.atomic.AtomicReference<V>

如果需要为项目编写测试程序,以便在每个平台项目中进行编译和运行,可以使用Kotlin提供的测试库。其中,kotlin-test-annotations-common库作用于通用模块,kotlin-test-junit库作用于JVM模块,kotlin-test-js库则作用于JavaScript模块。

1.artifacts及拆分包

现在,Kotlin的标准库可以完全兼容Java 9的模块系统,它禁止对包进行拆分(多个JAR包文件在同一个包中声明类)。为了支持这一点,需要引入新的artifacts kotlin-stdlib-jdk7和kotlin-stdlib-jdk8来取代旧的kotlin-stdlib-jre7和 kotlin-stdlib-jre8。

从Kotlin的角度来看,新artifacts中的声明在相同包名下可见,但是对Java而言它们则位于不同的包中。因此,切换到新的artifacts后,不需要对代码进行任何更改。

另外,新模块系统移除了kotlin.reflect包中的声明,如果要在新版本中使用它们,则需要使用kotlin.reflect.full包中的声明,自Kotlin 1.1版本就开始支持该包的内容。

2.chunked、windowed、zipWithNext

在新版本中,Iterable<T>、Sequence<T>和CharSequence等扩展了诸如缓冲或批处理(chunked)、滑动窗口和滑动平均值计算(windowed)以及处理subsequent item对(zipWithNext)等用例。

fun main(args: Array<String>) {

    val items = (1..9).map { it * it }
    val chunkedIntoLists = items.chunked(4)
    val points3d = items.chunked(3) { (x, y, z) -> Triple(x, y, z) }
    val windowed = items.windowed(4)
    val slidingAverage = items.windowed(4) { it.average() }
    val pairwiseDifferences = items.zipWithNext { a, b -> b - a }

    println("items: $items ")
    println("chunked into lists: $chunkedIntoLists")
    println("3D points: $points3d")
    println("windowed by 4: $windowed")
    println("sliding average by 4: $slidingAverage")
    println("pairwise differences: $pairwiseDifferences")
}

在Kotlin 1.2版本的JVM中执行上面的代码,输出结果如下。

items: [1, 4, 9, 16, 25, 36, 49, 64, 81]
chunked into lists: [[1, 4, 9, 16], [25, 36, 49, 64], [81]]
3D points: [(1, 4, 9), (16, 25, 36), (49, 64, 81)]
windowed by 4: [[1, 4, 9, 16], [4, 9, 16, 25], [9, 16, 25, 36], [16, 25, 36, 49], [25, 36, 49, 64], [36, 49, 64, 81]]
sliding average by 4: [7.5, 13.5, 21.5, 31.5, 43.5, 57.5]
pairwise differences: [3, 5, 7, 9, 11, 13, 15, 17]

3.fill、replaceAll、shuffle/shuffled

除此之外,Kotlin 1.2版本还添加了一系列扩展函数用于处理列表,这些函数包括针对MutableList的fill、replaceAll、shuffle以及针对只读List的shuffled。

fun main(args: Array<String>) {
    val items = (1..5).toMutableList()
    items.shuffle()
    println("Shuffled items: $items")

    items.replaceAll { it * 2 }
    println("Items doubled: $items")

    items.fill(5)
    println("Items filled with 5: $items")
}

在Kotlin 1.2版本的JVM中执行上面的代码,输出结果如下。

Shuffled items: [1, 5, 2, 3, 4]
Items doubled: [2, 10, 4, 6, 8]
Items filled with 5: [5, 5, 5, 5, 5]

4.kotlin-stdlib中的数学运算

为了方便开发者进行数学公式计算,Kotlin 1.2增加了一些用于数学运算的API。这些API对于JVM和JavaScript是通用的。

5.BigInteger和BigDecimal的运算与转换

Kotlin 1.2引入了一组用于操作BigInteger和BigDecimal,以及使用其他数字类型进行转换的函数。这些函数主要包括以下几个。

6.浮点数转换

Kotlin 1.2版本添加了一些新函数,用于将Double和Float转换成位表示形式。

7.可序列化的Regex类

kotlin.text.Regex类是一个可序列化的类,可以直接在可序列化的层次结构中使用它。

1.构造函数调用标准化

自1.0版本开始,Kotlin就支持复杂控制流表达式,例如try-catch表达式和内联函数调用,这也符合Java的虚拟机规范。不幸的是,当构造函数调用的参数中存在try-catch表达式时,字节码处理工具并不能很好地处理这些代码,从而造成一些编译上的错误。

为了减少字节码处理工具带来的问题,Kotlin为开发者提供了一个命令行选项(-Xnormalize-constructor-calls=MODE),此选项会告诉编译器为这样的结构生成更多的Java辅助字节码,其中,MODE的值有以下几种。

2.Java默认方法调用

在Kotlin 1.2版本之前,接口成员在使用JVM 1.6版本重写Java默认方法时会在父调用中产生警告:“Super calls to Java default methods are deprecated in JVM target 1.6. Recompile with '-jvm-target 1.8'”。在Kotlin 1.2中,调用默认方法时将会报错,因此需要使用JVM 1.8来编译这些代码。

3.x.equals(null)一致行为

Kotlin 1.2版本之前,在映射到Java原语的平台类型上调用“x.equals(null)”时,如果x为null,则不会正确地返回true或false。从Kotlin 1.2版本开始,如果在平台类型的空值上直接调用“x.equals(...)”,则会抛出NPE异常;但调用“x == ...”并不会产生异常。

如果要使用Kotlin 1.2版本之前的行为,可以在配置脚本中将“-Xno-exception-on- explicit-equals-for-boxed-null”标志传递给编译器。

4.内联扩展接收器修复null转义

在平台类型空值上调用内联扩展函数时并没有检查接收器是否为null,这也就意味着允许null转义到其他代码中,而这往往会造成空指针异常。Kotlin 1.2版本修复了这一问题,在调用点强制执行空检查时,如果接收方为空,则抛出异常。

如果要切换到旧版本,请将fallback标志“-Xno-receiver-assertions”传递给编译器。

Kotlin 1.2版本启用类型化数组支持,也就是说,支持将Kotlin基本数组转换为JavaScript的类型数组。在Kotlin 1.2版本之前这是一个可选功能,现在这个功能默认情况下已开启。

现如今,各种编程语言铺天盖地,有函数式语言、面向对象的语言、动态语言、解释性语言以及脚本语言等。作为JetBrains旗下优秀的编程语言,Kotlin吸收了现代函数式语言的诸多优点,和其他JVM语言相比,它可以更好地兼容Java,而不是重建整个平台。

然而,一门新语言想要在短时间内获得开发者的认可并不是那么容易的。Kotlin创建之初,提供的API并不是那么完善,因而发展相对缓慢。不过随着版本迭代速度的加快,Kotlin功能越来越完善,正在被广大开发者所接受。


“工欲善其事,必先利其器”。在进行正式开发之前,首先需要搭建好相关的开发环境。搭建Kotlin开发环境,需要准备如下的工具或软件。

作为一款基于JVM的编程语言,要想运行Kotlin程序,就必须添加JDK环境的支持。所以,搭建Kotlin开发环境的第一步就是安装JDK环境。

打开Java官网主页,如图3-1所示,选择对应的JDK版本下载。

Java提供了很多版本,此处选择Java SE 1.8版本进行下载,JDK下载列表如图3-2所示,根据操作系统版本,选择对应的版本下载即可。

下载完成之后,单击安装即可,安装过程很简单,这里不再详述。安装完成后,需要配置相应的环境变量,主要是PATH和CLASSPATH,其中PATH环境变量指向JDK的bin目录,JAVA_HOME环境变量指向JDK的根目录,如图3-3所示。在Windows 10系统中,右键单击【此电脑】选择【属性】,然后依次单击【高级系统设置】→【环境变量】,打开环境变量设置框。

图3-1 Java下载主页面

图3-2 JDK下载列表

图3-3 Windows系统Java环境变量配置

相较于Windows系统下Java环境变量的配置,macOS系统下的环境变量配置就复杂许多。默认情况下,Mac环境下的JDK安装在/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/ Contents/Home中,可以在终端使用命令/usr/libexec/java_home –V来查看。

根据安装的JDK版本不同,目录中dk1.8.0_112.jdk的部分也会有所不同,虽然Mac系统默认安装了JDK 1.6版本,但实际开发过程中,会使用更高的版本来进行开发。知道JDK的安装路径后,就可以设置JAVA_HOME和CLASSPATH环境变量了,打开终端输入如下命令。

sudo vim /etc/profile

输入密码后按回车键,如果出现E325错误警告,则输入“E”并按回车键。按下字母“i”,显示insert,进入输入模式。在输入模式下,进行如下配置。

JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_112.jdk/Contents/Home"
export JAVA_HOME
CLASS_PATH="$JAVA_HOME/lib"
PATH=".$PATH:$JAVA_HOME/bin"

添加完后,按下【Esc】键退出输入模式,再输入“:wq!”保存并退出vim模式。执行命令如图3-4所示。

图3-4 在Mac上配置Java环境变量

为了检查环境变量是否配置成功,可以使用下面的命令来查看。

echo $JAVA_HOME 
echo $PATH

如果配置正确,则输入上面的命令后会输出如图3-5所示的路径。

图3-5 检查Java环境变量

目前,支持Kotlin开发的IDE主要有3种:IntelliJ Idea、Android Studio和Eclipse。IntelliJ IDEA作为JetBrains旗下的产品,早早地就添加了对Kotlin的支持;而Android Studio作为IntelliJ IDEA系列的产品,也在最近的版本中集成了Kotlin的相关环境;对于Eclipse用户来说,想要体验Kotlin可以通过插件的方式来集成相关环境。

IntelliJ IDEA作为目前广受欢迎的Java IDE,在智能代码助手、代码自动提示、J2EE支持等方面都异常强大,被业界公认为是一款优秀的Java开发工具。使用浏览器打开JetBrains的官网,选择IntelliJ IDEA并打开,下载对应版本的IntelliJ IDEA即可。IntelliJ IDEA分为两个版本:社区版(Community)和商业版(Ultimate)。社区版免费、开源,但功能较少;商业版功能多,需要付费,但可以试用30天。建议大家选择Ultimate版本进行下载,下载界面如图3-6所示。

图3-6 下载IntelliJ IDEA

IntelliJ IDEA支持Kotlin的最低版本是2016,如果读者本地的IntelliJ IDEA版本低于2016,但是又想体验Kotlin,那么只需要安装一个Kotlin插件即可。在【Plugins】中搜索Kotlin插件,如图3-7所示,然后安装插件,之后重启计算机即可。

图3-7 在IntelliJ IDEA中安装Kotlin插件

Android Studio从3.0版本开始集成了Kotlin环境支持,开发者可以很容易地使用Android Studio开发Android应用。如果读者的Android Studio还停留在3.0以下版本,也不用担心,可以使用Kotlin插件来解决问题。

在Android Studio中安装Kotlin插件的方式和在IntelliJ IDEA中安装插件的方式类似,如图3-8所示。

图3-8 在Android Studio中安装Kotlin插件

除了IntelliJ IDEA和Android Studio之外,Eclipse也支持Kotlin开发,下面重点介绍Eclipse集成Kotlin环境。

首先到Eclipse官网下载对应的版本,安装完成之后,依次选择【Help】→【Eclipse Marketplace】,然后在搜索框输入“Kotlin”并搜索,如图3-9所示。

图3-9 搜索Kotlin插件并安装

当然,如果读者的Kotlin版本过低,还可以进行更新。

插件安装完成之后,就可以使用Eclipse来开发Kotlin应用程序了,如图3-10所示。

图3-10 在Eclipse中创建Kotlin项目

到此,Kotlin所依赖的集成开发环境就搭建完成了,接下来就让我们一起进入Kotlin的编码之旅吧。

目前,IntelliJ IDEA、Android Studio和Eclipse都支持Kotlin开发,如果读者从事服务器端开发,则使用IntelliJ IDEA是比较好的选择;而如果读者从事移动端Android开发,则使用Android Studio比较好。

IntelliJ IDEA作为目前很受欢迎的Java IDE,也是目前为止对Kotlin支持较好的一款工具。

打开IntelliJ IDEA,首先显示的是如图3-11所示的欢迎界面。然后,我们选择【Create New Project】选项来创建一个新的项目,单击【Next】按钮,在【New Project】窗口的左侧导航栏中选择【Kotlin】,在窗口的右侧列表中有两个选项:【Kotlin/JVM】和【Kotlin/JS】,如图3-12所示。Kotlin/JVM选项会将Kotlin源代码编译成.class文件,Kotlin/JS会将Kotlin源代码编译成JavaScript代码供浏览器解析运行。这两者的区别在于,使用Kotlin/JVM方式编写Kotlin的代码时既可以使用Kotlin原生的API,也可以使用JDK提供的API;而选择Kotlin/JS方式编写的Kotlin代码只能使用Kotlin提供的原生API。

图3-11 IntelliJ IDEA欢迎界面

图3-12 使用IntelliJ IDEA创建Kotlin项目

新建一个项目后,src文件默认是空的。学习一门新语言时,大家喜欢以一个“Hello,World!”来开启新语言的学习旅程。在src目录上单击鼠标右键,依次选择【New】→【Kotlin File/Class】菜单项来新建一个Kotlin文件,在程序中输出“Hello,World!”。

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

单击右上角的【Run/Debug Configurations】窗口,可以打开IntelliJ IDEA的运行配置功能,如图3-13所示。

在这个面板中,【Main class】是运行时的主类,它是必须配置的,【JRE】是Kotlin运行时需要的环境,单击【OK】按钮,完成配置。

如果选择的是“Kotlin/JS“方式,则按照项目创建的过程创建即可。需要注意的是,按照这种方式新建的Kotlin文件,只能调用Kotlin原生的API,而不能调用JDK的API。

图3-13 IntelliJ IDEA运行配置窗口

自从Google将Kotlin列为Android开发的第一语言后,Kotlin可以说是声名鹊起,再加上Jake Wharton发表推特文章称已正式加入Google的Android框架团队,从事Kotlin方面的开发工作,这无疑为Google Android Framework Team增添一大助力,同时也吸引着更多的开发者加入Kotlin开发阵营。而在移动Android应用开发上,各大公司纷纷试水Kotlin,甚至在一些创业项目里面90%的代码都使用Kotlin来编写。

现在开发Android移动应用,Android Studio是很好的选择,Eclipse已经退出了历史舞台。Android Studio 3.0已经默认添加对Kotlin的支持,即使在老版本的Android Studio 2.x中,也可以通过集成Kotlin插件的方式来实现。读者可以打开Android Studio的主页面或者使用国内的镜像地址下载,Android Studio官方主页如图3-14所示。

目前,Android Studio支持Windows、macOS和Linux三大主流系统版本,读者根据实际情况,选择合适的版本下载安装。

安装完成后,启动Android Studio,选择【Start a new Android Studio project】选项创建一个新的Android工程,如图3-15所示。

图3-14 Android Studio官方主页

图3-15 Android Studio欢迎界面

使用Android Studio创建Kotlin项目的时候,需要选中【Include Kotlin support】来添加对Kotlin相关环境的支持,然后单击【Next】按钮来完成项目的创建,如图3-16所示。如果读者依然使用的是Android Studio 2.x版本,可以通过如下的方式来添加Kotlin环境支持。

首先,打开Android Studio,依次选择【Setting】→【Plugins】→【Browse Repositories】,搜索Kotlin插件来安装,如图3-17所示。

然后,在工程的build.gradle文件的buildscript中添加如下配置。

buildscript {
ext.kotlin_version = '1.1.51'
…//省略其他配置
}

图3-16 在Android Studio 3.0版本中添加Kotlin环境支持

图3-17 在Android Studio 2.x版本中安装Kotlin插件

在工程的App层次的build.gradle脚本文件中添加如下配置。

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
…  //省略其他配置
dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }

除了手动配置方式之外,还可以借助Kotlin插件来实现一键添加Kotlin环境配置。在Android Studio的工具栏上依次选择【Tools】→【Kotlin】→【Configure Kotlin in Project】即可完成Kotlin环境的配置。

创建项目的时候,系统默认建立一个MainActivity的文件类,该类既是Android App主窗口类,也是程序的启动入口,代码如下。

class MainActivity : AppCompatActivity() {  
      override fun onCreate(savedInstanceState: Bundle?) {  
            super.onCreate(savedInstanceState)  
            setContentView(R.layout.activity_main)  
     }  
}

功能编写完成后就可以运行了,既可以选择在真机上运行,也可以选择使用模拟器运行。如果使用模拟器运行,需要先启动一个AVD(Android模拟器),然后单击工具栏中的运行按钮即可看到项目的运行效果,如图3-18所示。

图3-18 使用模拟器运行项目

作为一门跨平台的语言,Kotlin的运行方式也比Java丰富得多。除了可以使用可视化工具直接运行之外,Kotlin还支持命令行、REPL、脚本和JavaScript等编译运行方式。

使用命令行方式编译运行Kotlin,首先需要下载一个编译器,Kotlin为每个release都分配了一个独立的编译器版本。读者可以打开Kotlin官网。将页面滚动到如图3-19所示的位置,单击【Download Compiler】按钮即可下载对应的编译器。

图3-19 Kotlin支持的IDE及编译器下载

Kotlin提供了多种方式来配置编译器,例如下载二进制压缩包、从源代码编译、在macOS环境下使用brew命令方式安装等,不过比较简单方便的还是二进制压缩包方式。在该页面找到如图3-20所示的文字链接,或者直接使用链接来下载对应的二进制压缩包。

图3-20 下载二进制压缩包

下载完成后,将二进制压缩包解压到合适的位置,如/Users/×××/Kotlin文件夹下,那么Kotlin编译器的根目录就是/Users/×××/Kotlin/kotlinc。为了在任何位置都可以使用Kotlin编译器,还可以将编译器的根目录路径添加到系统的环境变量中。然后,在控制终端输入“kotlinc –version”命令,如果打印版本信息,则说明编译器配置正确。例如,下面是macOS系统下的Kotlin环境变量配置。

export KOTLIN_HOME=/Users/×××/Kotlin/kotlinc   //×××改为用户名
export PATH=${PATH}:${KOTLIN_HOME}/bin

在Mac系统上查看,版本信息如图3-21所示。

图3-21 Mac系统上查看Kotlin版本信息

如果读者使用Homebrew的方式来安装Kotlin,则可以使用如下的命令。

brew update
brew install kotlin

接下来,创建一个名为Hello.kt的文件并输入以下代码。

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

Kotlin编译器提供了一个kotlinc命令行,kotlinc命令的作用和javac命令类似,是将源代码编译为class字节码文件。接下来使用kotlinc命令编译Hello.kt文件,命令如下。

kotlinc hello.kt

命令执行完成之后,会在该目录下生成一个HelloKt.class和一个META-INF文件,其中,HelloKt.class就是字节码文件,如果使用Java命令来运行HelloKt字节码文件,结果会报NoClassDefFoundError错误。根据官网的介绍,需要使用如下的命令来运行字节码文件。

kotlin HelloKt

或许读者有个疑问,为什么直接使用java HelloKt命令会报错呢?这是因为Kotlin代码中的println函数是定义在依赖库中的,所以,要在Java中使用命令来调用Kotlin原生的API,就必须添加依赖库。

那么,要想使用Java命令调用Kotlin代码需要怎么做呢?建议开发者使用命令先将Kotlin源码编译成可运行的JAR包,然后再执行调用命令。Kotlin编译器的kotlinc命令提供了一个-include-runtime命令行参数,可在编译时引用Kotlin需要的依赖库,命令如下。

kotlinc Hello.kt -include-runtime -d Hello.jar

使用上面的命令编译Kotlin源代码后,会在该目录下生成一个Hello.jar文件,在上面的命令中涉及两个命令行参数。

然后使用命令java -jar hello.jar,即输出字符串“Hello,Kotlin!”。除此之外,如果生成的JAR包需要供其他Kotlin程序使用,则无须包含Kotlin的运行库。采用这种方式,请确保它被调用时运行在classpath环境上。

//编译Kotlin代码成JAR库,该包不含Kotlin运行时类库
kotlinc Hello.kt -d Hello.jar
//被其他程序调用时,需要在路径中提供编译时的Kotlin运行时库
kotlin -classpath Hello.jar HelloKt

需要注意的是,使用Java命令的classpath参数时,需要指明搜索class文件的路径,多个路径用分号分割,如果使用的是JAR包,则一定要精确到JAR包的名称。

到此,我们再来看一下Kotlin的完整编译流程:Kotlin源码通过Kotlin编译器编译生成.class文件,生成的.class文件会被打包,然后使用应用类型的标准流程运行应用程序。一个简单的编译流程如图3-22所示。

图3-22 Kotlin编译流程

使用Kotlin编译器编译代码需要依赖Kotlin的运行时库,它包含了Kotlin的标准类库和对Java标准库的扩展。

REPL(Read-Eval-Print Loop)环境是Kotlin自带的交互式编程命令行,其实就是一个控制台环境,其作用和Android Studio自带的Terminal命令行窗口差不多。在终端上执行kotlinc命令,可以直接进入REPL环境。

新版本的IntelliJ IDEA以及Android Studio,已经内置了对REPL的支持,可以在工具栏上依次单击【Tools】→【Kotlin】→【Kotlin REPL】来打开REPL窗口,如图3-23所示。

图3-23 Kotlin REPL窗口

某些情况下,我们并不希望直接使用IDEA内置的Kotlin REPL环境,这时候,独立使用Kotlin REPL命令行是再合适不过的选择。在REPL环境中,每输入一行Kotlin代码,就会在REPL控制台中输出相关的结果,如图3-24所示。

图3-24 在REPL窗口中执行Kotlin代码

Kotlin REPL提供了诸多有用的命令,退出REPL可以使用“:quit”命令,也可以使用“System.exit(0)”;使用“:load”命令可以装载Kotlin文件。

Kotlin 1.1版本添加了对JavaScript的支持,开发者可以使用Kotlin进行网页开发,Kotlin实现的是ECMAScript 5.1版本。Kotlin实现了一个kotlinc-js编译器,kotlinc-js编译器主要用来完成词法分析、语义分析和将Kotlin代码转换成JavaScript代码并交由JavaScript引擎执行。

使用Kotlin可以创建面向客户端JavaScript,实现与DOM元素的交互。另外,Kotlin还可以与现有的一些JavaScript框架配合使用(如ReactJS、JQuery等)。

接下来,创建一个名为kotlin_javascript.kt的文件并输入如下代码。

fun main(args: Array<String>) {  
    println("hello, kotlin_Javascript" )    
}

然后,使用kotlinc-js命令将Kotlin代码转换为JavaScript代码,完成命令后,会在该目录下生成相应的JavaScript文件。

kotlinc-js -output kotlin_javascript.js kotlin_javascript.kt

使用文本编辑器打开kotlin_javascript.js文件,代码如下。

if (typeof kotlin === 'undefined') {
throw new Error("Error loading module 'kotlin_javascript'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'kotlin_javascript'.");
}
var kotlin_javascript = function (_, Kotlin) {
  'use strict';
  var println = Kotlin.kotlin.io.println_s8jyv4$;
  function main(args) {
    println('hello\uFF0Cword!');
  }
  _.main_kand9s$ = main;
  main([]);
  Kotlin.defineModule('kotlin_javascript', _);
  return _;
}(typeof kotlin_javascript === 'undefined' ? {} : kotlin_javascript, kotlin);
/Users/xiangzhihong/Kotlin/workspace/kotlinDemo/kotlin_javascript.kt

对于转换后的JavaScript代码,不必深究里面的结构和语法,只需要明白一点,转换后的代码可供浏览器解析执行。不过,kotlin_javascript.kt中使用了println函数,因此需要引入Kotlin原生依赖库。对于JavaScript端来说,要执行原生的API,同样也需要一个JavaScript依赖库,这个文件就是kotlin.js。具体来说,在Kotlin编译器目录的lib文件夹下,有一个kotlin-jslib.jar文件,这个文件包含了JavaScript运行时需要的相关环境,使用解压工具将其解压,会发现目录中包含一个kotlin.js文件,将其复制到与kotlin_javascript.js同级的目录下。

接下来,新建一个名为javascript_test.html的文件并输入如下代码。

<head>
 <meta charset="UTF-8">
 <title>Kotlin JavaScript</title>
</head>
<body>
<script type="text/javascript" src="./kotlin.js"></script>
<script type="text/javascript" src="./kotlin_javascript.js">
</script>
</body>
</html>

使用浏览器打开javascript_test.html文件,就会在浏览器的Console面板打印相关的信息。

谈到JavaScript,就不得不提到Node.js。Node.js是一个基于Chrome V8 引擎的JavaScript运行环境,Node.js的优点在于完全异步的I/O模型。异步I/O模型极大地提高了Web服务的并发性。配合Node.js环境,JavaScript具备了和PHP、Java EE等同的地位,时下流行的Node微服务技术就是基于Node技术开发的。

要在Node.js中运行JavaScript程序,首先需要Node.js环境的支持。Node.js环境的搭建非常简单,读者可以到Node.js的官网下载。安装完成后,在终端输入命令“node –v”,如果正确输出版本信息,则说明Node.js安装成功。

使用Node.js提供的命令可以执行JavaScript脚本文件,例如,使用命令“node kotlin_ javascript.js”执行JavaScript脚本,如图3-25所示。

图3-25 在Node.js环境中运行JavaScript报错

很明显,这个异常是由于没有添加Kotlin依赖库导致的。在前端框架中,引用第三方库时需要使用require函数,在JavaScript中引用Kotlin库可以使用如下方式。

var kotlin = require("kotlin");

现在打开kotlin_javascript.js文件并在代码的最前面添加引用代码声明。同时,为了保证命令能正确运行,需要在项目文件夹目录下安装Kotlin依赖(即node_modules),安装命令如下。

npm install kotlin

npm是Node.js中的一个包管理工具,用来解决Node.js的代码部署问题。Kotlin依赖安装完成后,再次执行“node kotlin_javascript.js”命令,就可以正确输出字符串了,如图3-26所示。

图3-26 在Node.js环境中运行JavaScript

作为一款优秀的跨平台开发语言,Kotlin支持多种方式构建项目,常见的有Gradle、Maven和Ant等。

Gradle是构建Kotlin项目的推荐方式,Gradle具有灵活的项目模型,因为支持增量构建、长期构建过程和其他高级技术,所以得到了软件开发者的喜爱和追捧。

我们知道,Gradle是Android项目的标准构建系统,使用Groovy语法编写而成。不过,Gradle团队正在努力支持使用Kotlin来编写Gradle构建脚本。

构建Kotlin项目的标准Gradle脚本如下。

buildscript {
    ext.kotlin_version = '1.2.20'   //Kotlin版本
    repositories {
        mavenCentral()
    }

    dependencies {
        //给Kotlin Gradle增加构建脚本依赖
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

如果使用Gradle的DSL插件来构建Kotlin项目,则不需要进行上面的配置。

1.在JVM中使用Gradle

在JVM环境下使用Kotlin进行开发前,需要为应用添加Kotlin插件。

apply plugin: "kotlin"

或者,从Kotlin 1.1.1版本开始,使用Gradle提供的DSL插件来应用该插件。

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.2.20"
}

大多数情况下,Kotlin源码可以与同一文件夹或不同文件夹中的Java源码混用,系统默认使用不同的文件夹,如果要放到同一文件夹,需要对sourceSets设置如下属性。

sourceSets {
    main.kotlin.srcDirs += 'src/main/myKotlin'
    main.java.srcDirs += 'src/main/myJava'
}

2.在JavaScript中使用Gradle

在JavaScript中使用Kotlin进行开发前,需添加Kotlin的JS插件依赖。

apply plugin: "kotlin2js"

因为该插件只适用于Kotlin文件,所以建议将Kotlin和Java文件分开存放。除了正常输出JavaScript文件外,该插件还会额外创建一个带二进制描述符的JS文件,是否生成JS文件由kotlinOptions.metaInfo选项控制。

compileKotlin2Js {
    kotlinOptions.metaInfo = true
}

3.在Android中使用Gradle

和普通的Java环境相比,Android环境使用了不同的构建过程,因此需要使用不同的Gradle插件来完成构建。具体来说,在构建脚本时使用kotlin-android插件取代Kotlin插件。

apply plugin: 'kotlin-android'

其他设置和非Android应用的设置一样,完整的脚本配置如下。

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

buildscript {
    ext.kotlin_version = '1.2.20'
    ……
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

使用Android Studio来开发Android应用时,如果希望将Kotlin源代码放在特定目录下(如src/main/kotlin),则需要向系统注册它们,以便Android Studio将它们识别为Kotlin源代码目录。

android {
  ……

  sourceSets {
    main.java.srcDirs += 'src/main/kotlin'
  }
}

4.增量编译

从Kotlin 1.1.1版本开始,使用Gradle方式构建的Kotlin项目默认开启了增量编译,增量编译加快了编译速度,提高了用户体验。如果想要修改默认的增量编译设置,则可以使用下面的方式。

开启增量编译后,第一次项目构建不是增量的,编译速度依然会比较慢。

作为Web时代知名的项目构建工具,Maven的功能可以说非常强大。如果读者喜欢使用Maven来构建项目,Kotlin也是支持的。对于现有的Maven项目,可以通过Kotlin IntelliJ IDEA插件来完成。依次选择【Tools】→【Kotlin】→【Configure Kotlin】即可完成Kotlin的支持。

当然,除了借助工具之外,Kotlin还支持通过pom文件配置来添加环境支持,pom文件配置依赖关系如下。

<dependencies>
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-stdlib</artifactId>
        <version>${kotlin.version}</version>
    </dependency>
</dependencies>

如果JDK环境是JDK 7或JDK 8,则使用kotlin-stdlib-jre7或kotlin-stdlib-jre8 取代上面的kotlin-stdlib即可。Maven还支持指定Kotlin代码编译,只需要在pom文件的<build>标签中指定需要编译的源代码即可。

<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>   <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
</build>

当然,对于一些Java和Kotlin混合的项目,如果要进行混合代码的编译,那么必须在Java编译器调用之前调用Kotlin编译器。为了加快项目的编译速度,Kotlin从1.1.2版本开始启用了增量编译,相关属性设置如下。

<properties>
    <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
</properties>

Kotlin提供了3种任务来支持Ant方式构建项目:<kotlinc>用于编译纯Kotlin的模块,<kotlin2js>用于编译JavaScript相关的模块,而<withKotlin>作为javac的扩展用于编译混合的Kotlin/Java模块。下面是使用kotlinc任务编译项目的一个简单的示例。

<project name="Ant Task Test" default="build">
    <typedef resource="org/jetbrains/kotlin/ant/antlib.xml" classpath="${kotlin. lib}/kotlin-ant.jar"/>

    <target name="build">
        <kotlinc src="hello.kt" output="hello.jar"/>
    </target>
</project>

示例中,${kotlin.lib}指向Kotlin独立编译器所在文件夹的位置。如果项目由多个源代码组成,那么需要多个src作为元素路径。

<project name="Ant Task Test" default="build">
   …//省略其他配置
<target name="build">
        <kotlinc output="hello.jar">
            <src path="root1"/>
            <src path="root2"/>
        </kotlinc>
  </target>
  </project>

使用Ant任务方式构建Kotlin项目时,<kotlinc>会自动添加标准库依赖,不必在配置时添加额外的参数,使用<withKotlin>任务来构建混合的Java / Kotlin模块的示例如下。

<project name="Ant Task Test" default="build">
    <typedef resource="org/jetbrains/kotlin/ant/antlib.xml"   
        classpath="${kotlin.lib}/kotlin-ant.jar"/>

    <target name="build">
        <delete dir="classes" failonerror="false"/>
        <mkdir dir="classes"/>
        <javac destdir="classes" includeAntRuntime="false" srcdir="src">
            <withKotlin/>
        </javac>
        <jar destfile="hello.jar">
            <fileset dir="classes"/>
        </jar>
    </target>
</project>

如果要为<withKotlin>指定额外的命令行参数,则可以使用<compilerarg>参数,还可以为编译的模块指定moduleName名称属性。

<withKotlin moduleName="myModule">
    <compilerarg value="-no-stdlib"/>
</withKotlin>

和<kotlinc>不同的是,<withKotlin>并不支持自动打包编译的类,因此可以使用<jar>任务来替换。

本书说到的OSGi(Open Service Gateway initiative,开放服务网关协议)是指一系列用于定义Java动态化组件系统的标准,这些标准为大型分布式系统以及嵌入式系统提供一种模块化架构,以减少软件的复杂度。

要在Kotlin中启用OSGi支持,需要在项目中引入kotlin-osgi-bundle库而不是常规的Kotlin库。如果在Gradle构建的项目中引入Kotlin OSGi,则需要在配置文件中添加如下脚本。

compile "org.jetbrains.kotlin:kotlin-osgi-bundle:$kotlinVersion"

如果要排除项目中传递的依赖Kotlin库,可以使用以下方式。

dependencies {
 compile (
   [group: 'some.group.id', name: 'some.library', version: 'someversion'],
   ……) {
  exclude group: 'org.jetbrains.kotlin'
}

如果要在Maven构建的项目中使用Kotlin OSGi,则需要在pom配置文件中添加如下内容。

<dependencies>
   <dependency>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-osgi-bundle</artifactId>
      <version>${kotlin.version}</version>
   </dependency>
</dependencies>

如果要排除某些标准库,则可以添加如下配置。

<dependency>
    <!-- 需要排除的库 -->
    <groupId>some.group.id</groupId>
    <artifactId>some.library</artifactId>
    <version>some.library.version</version>

    <exclusions>
      <exclusion>
        <groupId>org.jetbrains.kotlin</groupId>
            <!--  *排除方式只在Maven 3起作用 -->
            <artifactId>*</artifactId>
            </exclusion>
      </exclusions>
</dependency>

在Kotlin项目开发中,借助Kapt(Kotlin annotation processing tool,Kotlin注解处理工具)编译器插件可以很轻松地实现注解处理功能。做过Android开发的读者都知道,Android提供了大量的注解工具,如Dagger、Butterknife和Data Binding等。注解作为一种自动生成的模板代码,有助于减少代码的冗余度,方便程序的阅读与维护。

1.在Gradle构建的项目中使用Kapt添加依赖库

在Gradle构建的项目中使用Kapt需要引入kotlin-kapt插件。

apply plugin: 'kotlin-kapt'

然后在配置文件的dependencies块中使用Kapt配置需要添加的依赖项。例如,使用Kapt引入Glide v4库。

apply plugin: 'kotlin-kapt' 

dependencies {
   kapt 'com.github.bumptech.glide:compiler:4.0.0-RC1'
}

在Gradle构建的项目中使用kotlin-kapt插件时应注意以下几点。

2.在Maven构建的项目中使用Kapt添加依赖库

在Maven构建的项目中使用Kapt插件添加其他依赖库的示例如下。

<execution>
    <id>kapt</id>
    <goals>
        <goal>kapt</goal>
    </goals>
    <configuration>
        <sourceDirs>
             <sourceDir>src/main/kotlin</sourceDir>
             <sourceDir>src/main/java</sourceDir>
        </sourceDirs>
        <annotationProcessorPaths>
             <!-- 此处指定你的注解处理器 -->
             <annotationProcessorPath>
                 <groupId>com.google.dagger</groupId>
                 <artifactId>dagger-compiler</artifactId>
                 <version>2.9</version>
             </annotationProcessorPath>
        </annotationProcessorPaths>
    </configuration>
</execution>

需要注意的是,IntelliJ IDEA自身的构建系统目前还不支持Kapt,想要在IntelliJ IDEA中使用Kapt,请参考上面的构建方式。

Kotlin提供了多种构建方式,我们可以使用像Maven、Gradle或者Ant这样的构建系统来构建项目,Kotlin跟这些构建系统都是兼容的。同时,这些构建系统也支持Kotlin和Java组成的混合项目。

Kotlin的默认类和方法都是final的,使用诸如Spring AOP这样的第三方框架的时候,直接为类添加open属性并不是很方便,all-open编译器插件则可以满足框架的这些需求。例如,使用Spring框架时,如果不需要编译所有的类,那么只需要使用特定的注解在需要开放编译的地方添加标注即可。

要想在项目中使用这些特殊的注解标注,首先必须配置all-open编译器插件环境,全开放编译插件提供Gradle和Maven两种IDE集成方式。

1.Gradle方式集成

Gradle方式集成的脚本配置如下。

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
    }
}

apply plugin: "kotlin-allopen"

还可以使用plugins块方式集成。

plugins {
  id "org.jetbrains.kotlin.plugin.allopen" version "1.2.20"
}

然后,再将需要开放的类添加到allOpen脚本配置中。代码如下。

allOpen {
     annotation("com.my.Annotation")
 …
}

此时,如果类或者超类使用com.my.Annotation注解,那么该类及其所有的成员将变为开放。

2.Maven方式集成

Maven是Apache组织下一个跨平台的项目管理工具,主要用来实现项目的构建、测试、打包和部署。Maven集成全开放的脚本配置如下。

<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>

    <configuration>
        <compilerPlugins>
            <plugin>all-open</plugin>
        </compilerPlugins>

        <pluginOptions>
            <!-- 每个注解都放在其相应的行上 -->
            <option>all-open:annotation=com.my.Annotation</option>
            <option>all-open:annotation=com.their.AnotherAnnotation</option>
        </pluginOptions>
    </configuration>
    //…
</plugin>

3.命令行中使用all-open插件

除了IDE集成方式,全开放编译器插件还提供JAR包方式依赖。如果采用命令方式编译Kotlin项目,则可以使用kotlinc命令的Xplugin选项来提供该JAR文件的路径。

-Xplugin=$KOTLIN_HOME/lib/allopen-compiler-plugin.jar

然后,使用annotation插件或者启用“预设”来直接指定需要全开放注解的范围。当前,能提供全开放的唯一预设是spring。

-P plugin:org.jetbrains.kotlin.allopen:annotation=com.my.Annotation
-P plugin:org.jetbrains.kotlin.allopen:preset=spring

无参编译器插件可以为具有特定注解的类生成一个零参数构造函数。因为生成的构造函数是合成的,所以不能在Java或Kotlin代码中直接调用,但可以使用反射调用。

和全开放插件的集成方式一样,无参编译器插件也提供了Gradle和Maven两种IDE集成方式。

1.Gradle方式集成

Gradle方式集成的脚本配置如下。

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
    }
}

apply plugin: "kotlin-noarg"

或者使用Gradle插件方式集成。

plugins {
  id "org.jetbrains.kotlin.plugin.noarg" version "1.2.20"
}

使用时,在noArg标签下指定无参注解列表即可,如果希望在合成的构造函数中运行其初始化逻辑,需将invokeInitializers选项设置为true。

noArg {
    annotation("com.my.Annotation")
    invokeInitializers = true    //合成函数中初始化
}

2.Maven方式集成

Maven方式集成无参编译器插件的脚本如下。

noArg {
<plugin>
    <artifactId>kotlin-maven-plugin</artifactId>
    <groupId>org.jetbrains.kotlin</groupId>
    <version>${kotlin.version}</version>

    <configuration>
        <compilerPlugins>
            <plugin>no-arg</plugin>
        </compilerPlugins>

        <pluginOptions>
            <option>no-arg:annotation=com.my.Annotation</option>
            <!-- 在合成的构造函数中调用实例初始化器 -->
            <!-- <option>no-arg:invokeInitializers=true</option> -->
        </pluginOptions>
    </configuration>

    //…
</plugin>

3.JPA方式

与kotlin-spring插件类似,kotlin-jpa插件是对no-arg插件的进一步包装。目前,该插件支持@Entity、@Embeddable和@MappedSuperclass等无参注解,该插件的Gradle集成方式如下。

//kotlin-jpa插件
apply plugin: "kotlin-jpa"
//DSL方式
plugins {
  id "org.jetbrains.kotlin.plugin.jpa" version "1.2.20"
}

在Maven项目中,则需要添加如下JPA插件配置。

<compilerPlugins>
    <plugin>jpa</plugin>
</compilerPlugins>

4.在命令行中使用

与全开放编译插件类似,命令行中使用无参编译器插件的命令如下。

-Xplugin=$KOTLIN_HOME/lib/noarg-compiler-plugin.jar
-P plugin:org.jetbrains.kotlin.noarg:annotation=com.my.Annotation
-P plugin:org.jetbrains.kotlin.noarg:preset=jpa

学习编程语言的第一步,就是搭建相关的编程环境并熟悉相关的集成开发和编译环境。作为一款多平台的开发语言,Kotlin的编译目标和运行方式都是多元化的。Kotlin还提供了多种构建方式,它和这些构建系统都是兼容的。

作为一门新语言,Kotlin提供了诸多有用的语法糖,这些语法糖构成了Kotlin的基础部分;同时,Kotlin提供的众多开发插件也提高了项目的开发速度。


相关图书

Android App开发入门与实战
Android App开发入门与实战
Android 并发开发
Android 并发开发
Android APP开发实战——从规划到上线全程详解
Android APP开发实战——从规划到上线全程详解
Android应用案例开发大全( 第4版)
Android应用案例开发大全( 第4版)
深入理解Android内核设计思想(第2版)(上下册)
深入理解Android内核设计思想(第2版)(上下册)
Kotlin程序开发入门精要
Kotlin程序开发入门精要

相关文章

相关课程