由Jetbrains在圣彼得堡的团队开发,得名于附近的一个Kotlin的小岛。
Jetbrains有多年的Java平台开发经验,他们认为Java编程语言有一定的局限性,而且由于需要向后兼容,它们很难得到解决。因此,他们创建了Kotlin项目,主要目标包括:
与其他JVM上的语言一样,编译成Java字节码
https://www.kotlincn.net/docs/tutorials/ 中文文档
https://kotlinlang.org/docs/reference/comparison-to-java.html 英文文档中对比Java的索引
对下文中提到的Kotlin做出的语言上的改进做一个总结:
最终归在一点: 集中精力 提高效率 减少错误
// 只读变量声明(更友好) 想想final val a: Int = 1 // 后置类型声明 // 一般利用类型推断,思维更加顺畅,不用再关心参数是什么类型的问题 val a = 5 val s = String() val clazz = s.getClass() val method = clazz.getDeclaredMethod("name", null) // 可变变量声明 var x = 5
不再有基本类型的概念,但运行时仍是基本类型表示(除了 Int?
…这类 nullable
变量,是包装类型)。
数组用 Array<>
类型表示,现在数组也是不可协变的了。
if else
语句除了与java一致的用法外,还取代了条件运算符 ?:
val max = if (a > b) a else b // int max = (a > b) ? a : b;
但用法更加灵活:
return if (a > b) { print("return a") a } else { print("return b") b }
for
的语法糖:
for (i in array.indices) { println(array[i]) } for ((index, value) in array.withIndex()) { println("the element at $index is $value") } for (i in 1..3) { println(i) } for (i in 6 downTo 0 step 2) { println(i) }
when
取代 switch
,更加强大的分支:
when (x) { 0, 1 -> print("x == 0 or x == 1") else -> print("otherwise") } when (x) { in 1..10 -> print("x is in the range") !in 10..20 -> print("x is outside the range") else -> print("none of the above") } // 有返回值 val hasPrefix = when(x) { is String -> x.startsWith("prefix") else -> false } when { x.isOdd() -> print("x is odd") x.isEven() -> print("x is even") else -> print("x is funny") }
引入属性的概念,隐式的 getter
与 setter
:
class Test { var a = 0 // has setter and getter val b = 1 // just has getter private c = 2 // no setter and getter } val test = Test() test.a = test.b // test.setA(test.getB())
再也不用写 setter/getter
逻辑了:
var stringRepresentation: String get() = this.toString() set(value) { setDataFromString(value) } // 相当于 public String getStringRepresentation() { return this.toString(); } public void setStringRepresentation(String value) { setDataFromString(value) }
java中已有的 getter/setter
会被“转换”成kotlin属性的形式
图灵奖得主托尼·霍尔把 Null
这个设计称为十亿美元错误:“它导致了数不清的错误、漏洞和系统崩溃,可能在之后 40 年中造成了十亿美元的损失”
Java中,引入了 @NonNull
和 @Nullable
注解辅助进行静态检查,有局限性。
Java8引入 Optional<>
来解决这个问题,写起来比较恶心,难以推广。
Kotlin希望在语言层面去解决这个问题->引入 Nullable
类型概念:
var nonnull: String = "must be inited with object" var nullable: String? = null
在语法层面做了诸多改进:
val l = s?.length // s != null, l = s.length else l = null, l: Int? val l = s!!.length // same as l = s.length in java, l: Int val l = s?.length ?: 0 // s != null, l = s.length else l = 0, l: Int return myValue ?: -1 // 链式使用: bob?.department?.head?.name // 任一为null不执行
推断的作用(智能转换):
// b: String? if (b != null && b.length > 0) { // b: String here print("length ${b.length}") } else { print("Empty string") } fun getStringLength(obj: Any): Int? { if (obj is String) { // automatically cast to `String` return obj.length } // `obj` is still of type `Any` outside of the type-checked branch return null }
如果被Java调用,由于Java无法保证非空(除非已经使用 @NonNull
注解注明),从Java接收的参数必须是可空的。
实际使用中,使得定义变量时必须要考虑是否可为空的问题,在一开始时如果不适应这种思维,可能会滥用可空类型,给调用带来麻烦。
举个例子:
class MyFragment : Fragment() { private var manager: MyAPIManager? = null @Override public void onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) manager = MyAPIManager(context) manager.authorize() } }
这里第二行中,由于Kotlin要求我们必须为属性赋予一个初值,但这里的初始化需要用到后面传入的 context
,按照Java的思维习惯,这个地方很容易就直接把类型改成可空的,然后给了个 null
的初值,但是这其实违背了Kotlin提供的特性:
manager
对象一旦被初始化之后就不会再为空,所以这应当是个非空类型 var
,实际上它并不应当被重新赋值,所以这应当是个 val
对象 Kotlin为我们提供了解决问题的方法:
当这个属性第一次被使用前再执地初始化代码,代码如下:
class MyFragment : Fragment() { private val manager: MyAPIManager by lazy { MyAPIManager(context) } @Override public void onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) manager.authorize() } }
在随后某个确定的时刻初始化,如果在使用时尚未被初始化,会抛出一个未初始化的运行时错误(与NPE略微不同),代码如下:
class MyFragment : Fragment() { lateinit var manager: MyAPIManager @Override public void onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) manager = MyAPIManager(context) manager.authorize() } }
但这时 manager
仍是一个 var
,美中不足。
使用这样的机制可以确保这个对象的可空性满足我们的预期,也就是说经过这样的处理的对象,在Kotlin中永远不会报 NPE
。而确实可为空的对象,我们利用 ?
表达式结合合适的默认值,是可以把 NPE
消灭的。
但没有 NPE
是一件好事吗?错误可能会因默认值变得隐含,虽然不会导致Crash,但给定位bug增加了一定难度。
Kotlin也提供了报 NPE
的办法:使用 !!
。
lateinit
只用于 var
,而 lazy
只用于 val
。如果是值可修改的变量(即在之后的使用中可能被重新赋值),使用 lateinit
模式 lateinit
模式。这种情况下, lazy
模式也可行但并不直接适用。 lazy
模式。从实现的角度来看, lateinit
模式仍然可用,但 lazy
模式更有利于封装初始化代码。 lateinit
模式是 lazy
模式的超集,你可以在任何使用 lazy
模式的地方用 lateinit
模式替代, 反之不然。 lateinit
模式在函数中暴露了太多的逻辑代码,使得代码更加混乱,所以推荐使用 lazy
,更好的封装了细节,更加安全。 https://kotlinlang.org/docs/reference/null-safety.html
https://dev.to/adammc331/understanding-nullability-in-kotlin
https://stackoverflow.com/questions/35723226/how-to-use-kotlins-with-expression-for-nullable-types
https://medium.com/@agrawalsuneet/safe-calls-vs-null-checks-in-kotlin-f7c56623ab30
fun dfs() { } val f = ::dfs f(graph)
函数参数终于可以有缺省值了(不用 Builder
了):
fun reformat(str: String, normalizeCase: Boolean = true, upperCaseFirstLetter: Boolean = true, divideByCamelHumps: Boolean = false, wordSeparator: Char = ' ') { // do something } reformat(str) reformat(str, wordSeparator = ' ') // 可以使用参数的名字给缺省参数赋值 // 可以通过@JvmOverloads注解生成Java的重载形式便于Java来调用
比如说,给别人的 View
加一个功能,给定一个资源 Id
,去取得它对应的资源:
// 写一个Util类,作为参数传进去 public class ViewUtils { public static int findColor(View view, int resId) { return view.getResources().getColor(resId); } } ViewUtils.findColor(view, resId);
通过扩展方法来解决:
fun View.findColor(id: Int) : Int { return this.resources.getColor(id) } view.findColor(resId)
一系列这种类型的Java工具类在Kotlin中被“改造”成了扩展方法例如:
Collection.sort(list)
在Kotlin中直接 list.sort()
就可以了。
可以完全取代以往的 Util
类。
语法简洁,逻辑连贯的最主要体现。
let/run
lambda
( run
则作为 this
) lambda
表达式的返回值 nullable
val length = s?.let { doSomething(it) it.length } ?: 0
// if...else...写法 private fun testIfElse(): Object? { return if (a !== null) { val b = handleA(a) if (b !== null) { handleB(b) } else { null } } else { null } } // ?.let写法 private fun testLet(): Object? { return a?.let { handleA(it) }?.let { handleB(it) } }
apply
this
传入 lambda
// old way of building an object val andre = Person() andre.name = "andre" andre.company = "Viacom" andre.hobby = "losing in ping pong" // after applying 'apply' (pun very much intended) val andre = Person().apply { name = "Andre" company = "Viacom" hobby = "losing in ping pong" }
return itemView.animation_like.apply { imageAssetsFolder = "images_feedcell/" loop(false) setAnimation("like_small.json") setOnClickListener(onClickListener) }
also
lambda
// transforming data from api with intermediary variable val rawData = api.getData() Log.debug(rawData) rawData.map { /** other stuff */ } // use 'also' to stay in the method chains api.getData() .also { Log.debug(it) } .map { /** other stuff */ }
takeIf/takeUnless
lambda
null
(根据 lambda
中语句的 true or false
) val outFile = File(outputDir.path).takeIf { it.exists() } ?: return false
混合使用举例:
// if...else...写法 private fun testIfElse(): Object? { return if (a !== null) { val b = handleA(a) if (b !== null) { handleB(b) } else { null } } else { null } } // ?.let写法 private fun testLet(): Object? { return a?.let { handleA(it) }?.let { handleB(it) } }
简洁,避免大量判空 if
的使用
File(url).takeIf { it.exists() } ?.let { JSONObject(NetworkUtils.postFile(20 * 1024, "http://i.snssdk.com/2/data/upload_image/", "image", url)) }?.takeIf { it.optString("message") == "success" } ?.let { post(content, contact, it.optJSONObject("data")?.optString("web_uri")) } ?: mHandler.post { view?.onFail() }
可以将逻辑划分清楚,直观,避免判空打断思路。
fun getMessages(context: Context, cursor: Int, direction: Int): ModelResult<MessageResponse> { return UrlBuilder(LOAD_NOTIFY) .apply { addParam("cursor", cursor) addParam("direction", direction) }.let { queryDataFromServer(it.build()) }?.let { val statusCode = it.optInt("status_code", -1) val statusMessage = it.optString("status_message") if (statusCode == 0) { MessageParser.parseMessageList(it.optString("data")) ?.let { ModelResult(true, statusMessage, it) } ?: ModelResult<MessageResponse>() } else { } } ?: ModelResult<MessageResponse>()) }
同样是划分逻辑,更加清晰?(需要适应)
附图一张:
https://medium.com/@elye.project/using-kotlin-takeif-or-takeunless-c9eeb7099c22
https://proandroiddev.com/the-tldr-on-kotlins-let-apply-also-with-and-run-functions-6253f06d152b
https://proandroiddev.com/the-difference-between-kotlins-functions-let-apply-with-run-and-else-ca51a4c696b8
本质上是一个匿名方法(单方法接口)
fun isGreaterThanZero(n: Int): Boolean { return n > 0 } collection.filter(::isGreaterThanZero) // 使用Lambda collection.filter{ i: Int -> i > 0 } collection.filter{ i -> i > 0 } collection.filter{ it > 0 }
单方法接口都可以传 Lambda
:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showToast(); } }); button.setOnClickListener{ showToast() }
内置常用的流处理 Lambda
:
names .filter{ it.startsWith("A") } .sortedBy{ it } .map{ it.toUpperCase() } .forEach{ print(it) }
配合 Rxjava
使用更佳(也有 Rxkotlin
)。
官方提供的扩展
全自动,无需声明,无需 findViewById
,直接使用 layout id
就行
text_view.text = "text"
一个注解自动实现 Parcelable
, 仍在实验阶段
// uiThread如果是Activity isFinish = true是不会调用的 doAsync { print("其他线程") uiThread { print("UI线程") } }
https://docs.gradle.org/current/dsl/index.html
String
的比较可以用 ==
了) Object Class
单例 Data Class
数据类型,自动实现 equals/hashCode/toString
Anko
扩展 https://www.jianshu.com/p/9f720b9ccdea
https://www.tuicool.com/articles/aEbeayN
https://github.com/android/android-ktx
https://github.com/adisonhuang/awesome-kotlin-android 其他开源库
结论:100%协同,Kotlin调Java没有问题,Java调Kotlin会有些绕,但不会出问题。
来源: https://blog.dreamtobe.cn/kotlin-performance/
varargs
参数展开,Kotlin比Java慢1倍,主要原因是在Kotlin在展开 varargs
前需要全量拷贝整个数组,这个是非常高的性能开销。 Delegated Properties
的应用,Kotlin相比Java慢10%。 Lambda
的使用,Kotlin相比Java快30%,而对用例中的 transaction
添加 inline
关键字配置内联后,发现其反而慢了一点点(约1.14%)。 companion object
的访问相比Java中的静态变量的访问,Kotlin与Java差不多快或更快一点。 Local Functions
)的访问相比Java中的局部函数的访问,Kotlin与Java差不多快或更快一点。 for
循环方式无论有没有使用 step
速度都差不多,但是如果对范围直接进行 .foreach
速度会比它们慢3倍,因此避免对范围直接使用 .foreach
。 lastIndex
会比使用 indices
快2%左右。 标准库大小 100k
左右。
新建标准工程(不带Kotlin支持),开启混淆,打release包。
将这个工程文件转为Kotlin实现,引入Kotlin支持,打release包,对比大小:
增加了约 109k
将某应用一个module转换为Kotlin实现(直接使用AS的工具转换),对比编译生成的所有 class
文件大小:
增加了约2%的体积。
https://discuss.kotlinlang.org/t/kotlin-generated-class-file-out-of-kt-is-bigger-than-java-file/1520/4
https://blog.dreamtobe.cn/2016/11/30/kotlin/