写了许久 Java,有没有发现其实你写了太多冗余的代码?html
后来你体验了一下 Python,有没有以为不写分号的感受真是超级爽?java
你虽然勤勤恳恳,可到头来却被 NullPointerException 折磨的死去活来,难道就没有受够这种日子么?android
直到有一天你发现本身已经写了好几十万行代码,发现竟然全是 getter 和 setter!git
哈哈,实际上你彻底能够不用这么痛苦,用 Kotlin 替代 Java 开发你的程序,不管是 Android 仍是 Server,你都能像以前写 Java 同样思考,同时又能享受到新一代编程语言的特性,说到这里你是否是开始心动了呢?下面我就经过这篇文章来给你们介绍一下 Kotlin 到底是何方神圣。程序员
话说,Kotlin 是 JetBrain 公司搞出来的,运行在 JVM 上的一门静态类型语言,它是用波罗的海的一个小岛的名字命名的。从外观上,乍一看还觉得是 Scala,我曾经琢磨着把 Scala 做为个人下一门语言,不过想一想用 Scala 来干吗呢,我又不作大数据,而它又太复杂了o(╯□╰)ogithub
用Kotlin建立一个数据类编程
最初是在 intelliJ 的源码中看到 Kotlin 的,那时候 Kotlin 的版本还不太稳定,因此源码老是编译不过,真是要抓狂啊,还骂『什么破玩意儿!为何又出来新语言了?Groovy 还没怎么学会,又来个 Kotlin!』话说,Kotlin,难道是『靠它灵』的意思??api
其实通过一年多的发展,Kotlin 1.0已经 release,feature 基本完善,api 也趋于稳定,这时候尝试也不会有那种被坑的感受了。过年期间也算悠闲,因而用 Kotlin 作了个 app,简单来讲,就是几个感受:安全
思路与写 Java 时同样,不过更简洁清爽bash
少了冗余代码的烦恼,更容易专一于功能的开发,整个过程轻松愉快
扩展功能使得代码写起来更有趣
空安全和不可变类型使得开发中对变量的定义和初始化倾注了更多关注
啊啊,我不再用写那个 findViewById 了,真的爽爆有木有!
Kotlin 开发固然使用 JetBrain 系列的 IDE,实际上 intelliJ idea 15 发布时就已经内置了 Kotlin 插件,更早的版本则须要到插件仓库中下载安装 Kotlin 插件——在安装时你还会看到有个 Kotlin Extensions for Android,不要管他,已通过时了。安装好之后,咱们就可使用 Kotlin 进行开发了。
接下来咱们用 Android Studio 建立一个 Android 工程,好比叫作 HelloKotlin,在 app 目录下面的 build.gradle 文件中添加下面的配置:
这里添加了 Kotlin 对 Android 的扩展,同时也添加了 Kotlin 的 Gradle 插件。
接下来就能够编写 Kotlin 代码了——等等,Android Studio 会帮咱们生成一个MainActivity,你能够直接在菜单
Code -> Convert Java file to Kotlin file
将这个 Java 代码转换为 Kotlin 代码。截止到如今,你什么都不用作,程序就已经能够跑起来了。
咱们都知道 Jvm 上面的语言,像什么 Java、Groovy、Jython 啥的,都是要编成虚拟机的字节码的,一旦编成字节码,在必定程度上你们就都平等了。
英雄不问出身啊
有人作过一个很是形象的比喻:Java 虚拟机语言就是打群架。Kotlin 正是充分利用了这一点,它本身的标准库只是基于 Java 的语言框架作了许多扩展,你在Kotlin 当中使用的集合框架仍然跟你在Java当中同样。
举个例子,若是你想要在 Kotlin 中使用 ArrayList,很简单,Java 的 ArrayList 你能够随意使用,这个感受跟使用 Java 没有任何区别,请看:
固然,Kotlin 标准库也对这些作了扩展,咱们在享用 Java 世界的一切资源的同时,还能比原生 Java 代码更滋润,真是爽爆有木有:
2.2 与Java交互
Kotlin 的标准库更多的是对 Java 库的扩展,基于这个设计思路,你丝绝不须要担忧 Kotlin 对 Java 代码的引用,你甚至能够在 Kotlin 当中使用 Java 反射,反正只要是 Java 有的,Kotlin 都有,因而有人作出这样的评价:
Kotlin 就是 Java 的一个扩展
这样说 Kotlin 显然是不公平的,但就像微信刚面世那会儿要为 QQ 接收离线消息同样,总得抱几天大腿嘛。
有关从 Kotlin 中调用Java的官方文档在此Calling Java code from Kotlin (https://kotlinlang.org/docs/reference/java-interop.html#static-methods-and-fields),其中最多见的就是 Getter / Setter 方法对应到 Kotlin 属性的调用,举个例子:
准备一个Java类
下面是Kotlin代码
因此咱们在 Android 开发时,就能够这样:
view.background = ... textView.text = ...
反过来在 Java 中调用 Kotlin 也毫无压力,官方文档C alling Kotlin from Java 对于常见的状况做了比较详细的阐述,这里就再也不赘述。
最初学 Java 的时候,学到一个概念叫 JavaBean,当时就要被这个概念给折磨死了。明明很简单的一个东西,结果搞得很复杂的样子,并且因为当时对于这些数据类的设计概念不是很清晰,于是也并不懂得去覆写诸如 equals 和 hashcode 这样重要的方法,一旦用到 HashMap 这样的集合框架,老是出了问题都不知道找谁。
Kotlin 提供了一种很是简单的方式来建立这样的数据类,例如:
data class Coordinate(val x: Double, val y: Double)
仅仅一行代码,Kotlin 就会建立出一个完整的数据类,并自动生成相应的 equals、hashcode、toString 方法。是否是早就受够了 getter和setter?反正我是受够了。
第一次见到空类型安全的设计是在 Swift 当中,那时候还以为这个东西有点儿意思哈,一旦要求变量不能为空之后,因它而致使的空指针异常的可能性就直接没有了。想一想每次 QA 提的 bug 吧,说少了都得有三分之一是空指针吧。
Kotlin 的空安全设计,主要是在类型后面加?表示可空,不然就不能为 null。
val anInt: Int = null // 错误
val anotherInt: Int? = null // 正确
使用时,则:
而对于 Java 代码,好比咱们在覆写 Activity 的 onCreate 方法时,有个参数 savedInstanceState:
override fun onCreate(savedInstanceState: Bundle!)
这表示编译器再也不强制 savedInstanceState 是否可 null,开发者在覆写时能够本身决定是否可 null。固然,对于本例,onCreate 的参数是可能为 null 的,所以覆写之后的方法应为:
override fun onCreate(savedInstanceState: Bundle?)
一般来说,教科书式的讲法,到这里就该结束了。然而直到我真正用 Kotlin 开始写代码时,发现,有些需求实现起来真的有些奇怪。
仍是举个例子,我须要在 Activity 当中建立一个 View 的引用,一般咱们在 Java 代码中这么写:
在 Kotlin 当中呢?
每次用 aTextView 都要加俩!,否则编译器不能肯定它到底是不是 null,因而不让你使用。。这尼玛。。。究竟是为了方便仍是为了麻烦??
因此后来我又决定这么写:
这可如何是好??
其实 Kotlin 确定是有办法解决这个问题哒!好比上面的场景,咱们这么写就能够咯:
lazy 是 Kotlin 的属性代理的一个实例,它提供了延迟加载的机制。换句话说,这里的 lazy 提供了初始化 aTextView 的方法,不过真正初始化这个动做发生的时机倒是在 aTextView 第一次被使用时了。lazy 默认是线程安全的,你固然也能够关掉这个配置,只须要加个参数便可:
好,这时候确定有人要扔西红柿过来了(再扔点儿鸡蛋呗),你这 lazy 只能初始化 val 啊,万一我要定义一个 var 成员,又须要延迟初始化,关键还不为 null,怎么办??
lateinit 的使用仍是有不少限制的,好比只能在不可 null 的对象上使用,比须为var,不能为 primitives(Int、Float之类)等等,不过这样逼迫你必定要初始化这个变量的作法,确实能减小咱们在开发中的遗漏,从而提升开发效率。
至于 lazy 技术,其实是 Delegate Properties 的一个应用,也就是属性代理了。在 Kotlin 当中,声明成员属性,除了直接赋值,还能够用 Delegate 的方式来声明,这个 Delegate 须要根据成员的类型(val 或者 var)来提供相应的 getValue 和 setValue 方法,好比一个可读写的 Delegate,须要提供下面的方法:
好嘴皮不如来个栗子,下面咱们就看一个自定义 Delegate,用来访问 SharedPreference:
须要说明的是,这段代码是我从《Kotlin for Android Developer》的示例中摘出来的。有了这个 Delegate 类,咱们就能够彻底不须要关心 SharedPreference了,下面给出使用的示例代码:
因而咱们不再须要重复写那些 getSharedPreference,也不用 edit、commit,再见那些 edit 以后忘了 commit 的日子。有没有以为很是赞!
扩展类,就是在现有类的基础上,添加一些属性或者方法,固然扩展的这些成员须要导入当前扩展成员所在的包才能够访问到。下面给出一个例子:
咱们已经介绍过 data class,Coordinate 有两个成员分别是 x 和 y,咱们知道一般表示一个二维平面,有这俩够了;然而咱们在图形学当中常常会须要求得其极坐标,因此咱们扩展了 Coordinate,增长了一个属性 theta 表示角度(反正切的值域为 -π/2 ~ π/2,因此这个式子不适用于二三象限,不过这不是重点了),增长了一个 R 方法来得到点的半径,因而咱们在 main 方法中就能够这么用:
那么这个扩展有什么限制呢?
在扩展成员当中,只能访问被扩展类在当前做用域内可见的成员,本例中的x 和 y 都是 public 的(Kotlin 默认 public,这个咱们后面会提到),因此能够在扩展方法和属性中直接访问。
扩展成员与被扩展类的内部成员名称相同时,扩展成员将没法被访问到
好的,基本知识就是这些了,下面咱们再给出一个实际的例子。
一般咱们在 Java 中会自定义一些 LogUtils 类来打日志,或者直接用 android.util.log 来输出日志,不知道你们是什么感觉,我反正每次由于要输入 Log.d 还要输入个 tag 简直烦的要死,并且有时候刚好这个类尚未 tag 这个成员,实践中咱们一般会把当前类名做为 TAG,但每一个类都要作这么个工做,是在是没有什么趣味可言(以前我是用 LiveTemplates 帮个人,即使如此也没有那种流畅的感受)。
有了 Kotlin 的这个扩展功能,日子就会好过得多了,下面我建立的一个打日志的方法:
有了这个方法,你能够在任何类的方法体中直接写:
debug(whatever)
而后就会输出以这个类名为 TAG 的日志。
嗯,这里须要简单介绍 Kotlin 在泛型中的一个比较重要的加强,这个在 Java 中不管如何也是作不到的:inline、reified。咱们再来回头看一下 debug 这个方法,咱们发现它能够经过泛型参数 T 来获取到T的具体类型,而且拿到它的类名——固然,若是你愿意,你甚至能够调用它的构造方法来构造一个对象出来——为何 Kotlin 能够作到呢?由于这段代码是 inline 的,最终编译时是要编译到调用它的代码块中,这时候T的类型其实是肯定的,于是 Kotlin 经过 reified 这个关键字告诉编译器,T 这个参数可不仅是个摆设,我要把它当实际类型来用呢。
为了让你们印象深入,我下面给出相似功能的 Java 的代码实现:
而你若是说但愿在 Java 中也但愿像下面这样拿到这个泛型参数的类型,是不能够的:
就算咱们在调用处会写道 debug < Date >(“blabla”),但这个 Date 在编译以后仍是会被擦除。
Java 8 已经开始能够支持 Lambda 表达式了,这种东西对于 Java 这样一个『根红苗正』的面向对象编程语言来讲还真是显得不天然,不过对于 Kotlin 来讲,就没那么多顾忌了。
一般咱们须要执行一段异步的代码,咱们会构造一个 Runnable 对象,而后交给 executor,好比这段 java 代码:
用 Kotlin 怎么写呢?
executor.submit({ //todo})
一会儿省了不少代码。
那么实际当中咱们可能更常见到下面的例子,这是一段很常见的 Java 代码,在 Android 的 UI 初始化会见到:
那么咱们用 Kotlin 怎么写呢?
textView.setOnClickListener{ /*todo*/ } handler.post{ /*todo*/ }
在 Anko 这个 Android 库的帮助下,咱们甚至能够继续简化 OnClickListener 的设置方式:
textView.onClick{ /*todo*/ }
固然,好玩的不止这些,若是结合上一节咱们提到的扩展方法,咱们就很容易看到 Kotlin 的标准库提供的相似 with 和 apply 这样的方法是怎么工做的了:
咱们一般会在某个方法体内建立一个对象并返回它,可咱们除了调用它的构造方法以外还须要作一些其余的操做,因而就要建立一个局部变量。。。有了 apply 这个扩展方法,咱们就能够这么写:
这样返回的 StringBuilder 对象其实是包 "whatever" 这个字符串的。
至于说 Kotlin 对于 RxJava 的友好性,使得我忽然有点儿相信缘分这种东西了:
3.5 Pattern Matching
记得以前在浏览 Scala 的特性时,看到:
object HelloScala{ // do something
}
以为很新鲜,这时候有个朋友不屑的说了句,Scala 的模式匹配才真正犀利——Kotlin 当中也有这样的特性,咱们下面就来看个例子:
咋一看感受 when 表达式就是一个加强版的 switch——Java 7 之前的 switch 实际上支持的类型很是有限,Java 7 当中增长的对 String 的支持也是基于 int 类型的——咱们能够看到 when 再也不像 switch 那样只匹配一个数值,它的子式能够是各类返回 Boolean 的表达式。
when 表达式还有一种写法更革命:
只要是返回 Boolean 的表达式就能够做为 when 的子式,这样 when 表达式的灵活性可见一斑。固然,与 Scala 相比,Kotlin 仍是要保守一些的,下面给出一个 Scala 相似的例子,你们感觉一下,这实际上也能够体现出 Kotlin 在增长 Java 的同时也尽可能保持简单的设计哲学(你们都知道,毕竟 Scala 须要智商o(╯□╰)o)。
运行结果以下:
a tuple with : 1 , 3
[I@2d554825
3.0, 4.0
我曾经作过一段时间的 SDK 开发,SDK 的内部有不少类实际上是须要互相有访问权限的,但一旦类及其成员是 public 的,那么调用方也就能够看到它们了;而 protected 或者 default 这样的可见性对于子包倒是不可见的。
用了这么久 Java,这简直是我惟一强烈感到不满的地方了,甚至于我忽然明白了 C++ 的 friend 是多么的有用。
Kotlin 虽然没有提供对于子包可见的修饰符,不过它提供了i nternal:即模块内可见。换句话说,internal 在模块内至关于 public,而对于模块外就是 private 了——因而乎咱们若是开发 SDK,那么能够减小 api 层的编写,那些用户不可见的部分直接用 internal 岂不更好。固然有人会说咱们应当有 proguard 作混淆,我想说的是,proguard 天然是要用到的,不过那是 SDK 这个产品加工的下一个环节了,咱们为何不能在代码级别把这个事情作好呢?
关于Kotlin的默承认见性到底是哪一个还有人作出过讨论,有兴趣的能够参考这里:Kotlin’s default visibility should be internal (https://discuss.kotlinlang.org/t/kotlins-default-visibility-should-be-internal/1400)。
其实咱们对 DSL 确定不会陌生,gradle 的脚本就是基于 groovy 的 DSL,而 Kotlin 的函数特性显然也是能够支持 DSL 的。好比,咱们最终要生成下面的 xml 数据:
咱们能够构建下面的类:
咱们看到在 main 方法当中,咱们用 Kotlin 定义的 dsl 写出了一个 Project 对象,它有这与 xml 描述的一致的结构和含义,若是你愿意,能够构造相应的方法来输出这样的 xml,运行以后的结果:
固然,这个例子作的足够的简陋,若是你有兴趣也能够抽象出 "Element",并为之添加 "Attributes",实际上这也不是很难。
写了不少代码,却发现它们干不了多少事情,终究仍是会苦恼的。好比我一直比较痛苦的一件事儿就是:
Button button = (Button) findViewById(R.id.btn);
若是我须要不少个按钮和图片,那么咱们要写一大片这样的 findViewById。。妈呀。。。这活我干不了啦。。
不过用 Kotlin 的 Android 扩展插件,咱们就能够这样:
先上布局文件:
main.xml
在 Activity 中:
注意到:
import kotlinx.android.synthetic.main.load_activity.*
导入这一句以后,咱们就能够直接在代码中使用 start、textView,他们分别对应于 main.xml 中的 id 为 start 的按钮和 id 为 textView 的 TextView。
因而你就发现你不再用 findViewById 了,多么愉快的一件事!!!固然,你还会发现 Toast 的调用也变得简单了,那其实就是一个扩展方法 toast();而 startActivity 呢,其实就是一个 inline加reified 的应用——这咱们前面都提到过了。
还有一个恶心的东西就是 UI 线程和非 UI 线程的切换问题。也许你会用 handler 不断的 post,不过说真的,用 handler 的时候难道你不颤抖么,那但是一个很容易内存泄露的魔鬼呀~哈哈,好吧其实我不是说这个,主要是用 handler 写出来的代码 实在 太 丑 了 !!
原来在 Java 当中,咱们这么写:
而在 Kotlin 当中呢,咱们只须要这么写:
本身感觉一下吧。
下面咱们再来提一个有意思的东西,咱们从作 Android 开发一开始就要编写 xml,印象中这个对于我来讲真的是一件痛苦的事情,由于它的工做机制并不如代码那样直接(以致于我如今不少时候竟然喜欢用 Java 代码直接写布局)——固然,最主要的问题并非这个,而是解析 xml 须要耗费 CPU。Kotlin 有办法能够解决这个问题,那就是 DSL 了。下面给出一个例子:
一个 LinearLayou t包含了一个 Button,这段代码你能够直接写到你的代码中灵活复用,就像这样:
这样作的好处真是很多:
比起 xml 的繁琐来,这真是要清爽不少
布局自己也是代码,能够灵活复用
不再用 findViewById 了,难道你不以为在这个上面浪费的生命已经足够多吗
事件监听很方便的嵌到布局当中
DSL 方式的布局没有运行时的解析的负担,你的逻辑代码怎么运行它就怎么运行
Anko还增长了更多好玩的特性,有兴趣的能够参考:Anko@Github (https://github.com/Kotlin/anko)
我曾经尝试用 Scala 写了个 Android 的 HelloWorld,一切都配置好之后,仅仅引入了 Scala 常见的几个库,加上 support-v4 以及 appcompat 这样常见的库,结果仍是报错了。是的,65K。。。并且用 Scala 开发 Android 的话,基于 gradle 的构建会让整个 app 的 build 过程异常漫长,有时候你会以为本身悟出了广义相对论的奥义,哦不,你必定是晕了,时间并无变慢。
相比之下,Kotlin 的标准库只有 7000 个方法,比 support-v4 还要小,这正反映了 Kotlin 的设计理念:100% interoperable with Java。其实咱们以前就提到,Java 有的 Kotlin 就直接拿来用,而 Scala 的标准库要有 5W 多个方法,想一想就仍是想一想算了。
目前 Kotlin 1.0 已经 release,尽管像 0xffffffff 识别成 Long 类型这样的 bug 仍然没有解详情 (https://youtrack.jetbrains.com/oauth?state=%2Fissue%2FKT-4749):
val int: Int = 0xffffffff // error
val anotherInt: Int = 0xffffffff.toInt() // correct
不过,Kotlin 的教学资源和社区建设也已经相对成熟,按照官方的说法,Kotlin能够做为生产工具投入开发,详情能够参考:Kotlin 1.0 Released: Pragmatic Language for JVM and Android (http://blog.jetbrains.com/kotlin/2016/02/kotlin-1-0-released-pragmatic-language-for-jvm-and-android/)。
勇于吃螃蟹,多少有些浪漫主义色彩,咱们这些程序员多少能够有些浪漫主义特质,不过在生成环境中,稳定高于一切仍然是不二法则。追求新技术,一方面会给团队带来开发和维护上的学习成本,另外一方面也要承担将来某些状况下由于对新技术不熟悉而产生未知问题的风险——老板们最怕风险了~~
基于这一点,毫无疑问,Kotlin 能够做为小工具、测试用例等的开发工具,这是考虑到这些代码一般体量较小,维护人数较少较集中,对项目总体的影响也较小;而对于核心代码,则视状况而定吧。
就我我的而言,长期下去,Kotlin 很大可能会成为个人主要语言,短时间内则仍然采用温和的改革方式慢慢将Kotlin 渗透进来。
一句话,Kotlin 是用来提高效率的,若是在你的场景中它作不到,甚至成了拖累,请放开它。
若是您以为咱们的内容还不错,就转发到朋友圈,和小伙伴一块儿分享吧~
原文:http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=404087761&idx=1&sn=d80625ee52f860a7a2ed4c238d2151b6