本文中的所有示例仅为了演示, 没有实际意义, 更不代表生产环境的代码应该这样写.
基本数据类型
在 Kotlin
中, 一切都是对象 (Everything is an object
), 包括 Java
中的基本数据类型 (primitive types
), 这样我们就可以直接调用它们的方法或者访问它们的属性. 因此在 Java
中属于基本数据类型的字面量在 Kotlin
中也能够直接调用其方法:
// kotlin
println(1.toString()) // 1
println(1.equals(2)) // false
数值型 (Number
)
Kotlin
数值数据类型包括以下 6 种: Double
, Float
, Long
, Int
, Short
, Byte
, 它们的长度分别为 64, 32, 64, 32, 16, 8.
Char
类型不属于数值类型注意到在
Kotlin
中Char
类型不属于数值类型. 因此像这样的Java
代码在Kotlin
中将不会编译通过:
// java
char a = 65;
System.out.println(a); // a
// kotlin
val a: Char = 65 // 编译失败, 因为赋值符号两边类型不一致
但 Kotlin
为所有数值类型都提供了 toChar
方法, 加上 “原始数据类型可以直接调用方法” 我们同样可以方便地达到最初的目的.
// kotlin
val a = 65.toChar()
println(65.toChar()) // A
println(66L.toChar()) // B
println(66.4.toChar()) // B
println(66.6.toChar()) // B
没有隐式转换
除了
Char
类型不属于数值类型之外,kotlin
还不会将低精度的数值类型自动提升成高精度的数值类型. 像这样常见的java
代码在kotlin
中同样会编译失败.
// java
int i = 1;
// ...
long l = i;
// kotlin
var i: Int = 1
// ...
var l: Long = i // 失败, 因为 Int 类型不会被自动提升为 Long 类型
我们需要显式地告诉编译器需要把 i
作为 Long
类型. 为了方便进行数据类型转换, Kotlin
中所有数值类型 (Number
类的子类) 都实现了 Number
类的转换方法:
public abstract class Number {
public abstract fun toDouble(): Double
public abstract fun toFloat(): Float
public abstract fun toLong(): Long
public abstract fun toInt(): Int
public abstract fun toChar(): Char
public abstract fun toShort(): Short
public abstract fun toByte(): Byte
}
即使 Char
类型不是 Number
类型的子类, 它也提供了和以上方法签名一样的系列转换函数.
字面数值表示方法
在
Java
中, 表示数值字面量的方式有 3 种, 即 十六进制, 十进制, 和 八进制, 直到Java7
, 才可以使用 二进制 表示一个数值字面量.int eight = 010; // 八进制, 以 0 为前缀 System.out.println(eight); // 8 int sixteen = 0x10; // 十六进制, 以 0x 为前缀 System.out.println(sixteen); // 16 int ten = 10; // 十进制, nothing special System.out.println(ten); // 10 int two = 0b10; // 二进制, 以 0b 为前缀. (since 1.7) System.out.println(two); // 2
而
Kotlin
则去掉了八进制表示方法, 只留下其他三种表示方法, 其表示方法与Java
一致, 不再赘述.
字符型 (Character
)
字符型不能直接当做整型进行运算和操作
布尔型 (Boolean
)
Nothing special
字符串 (String
)
Kotlin
中的字符串与 Java
中的字符串一样都是不可变的, 不过由于 Kotlin
的其他特性, Kotlin
中的字符串的操作比 Java
中对字符串的操作要方便和丰富很多.
下标操作
由于
Kotlin
敞开了对操作符重载的大门, 现在可以通过str[n]
获取字符串str
的第n
个字符了, 但因为字符串确实是不可变的, 所以无论如何也无法使用str[n] = 'a'
改变字符串str
的第n
个字符:val str = "abc" println(str[1]) str[1] = 'd' // 编译失败, 不能改变字符串
与字符串关系密切的还有
StringBuilder
和StringBuffer
, 但它们是可变的, 因此使用下标改变它们是可行的:val sb = StringBuilder("abcd") sb[0] = 'd' println(sb) // dbcd
字符串字面量 (
literals
)Kotlin
中字符串的字面量表示方法除了从Java
继承来最基本的表示方法之外, 还学 (chao) 习了python
的表示方法. 像val helloWorld = "Hello \n world"
这样的表示方法与Java
别无二致, 但想在Java
中构建像下面这样的JSON
字符串用于测试JSON
解析工具或想在字符串中存放代码时, 可能就有点蛋疼了.private String getFansLoginInfo() { String returnBody = "{\n" + "\"snsUid\":\"fansj\",\n" + "\"snsPlatform\":\"tw\",\n" + "\"nickname\":\"Groupy\",\n" + "\"avatar\":\"aa\",\n" + "\"sign\":\"aa\",\n" + "\"introduce\":\"\"\n" + "}"; return returnBody; } public void printMain() { System.out.println("\t\tpublic static void main (String[]args){\n" + "\t\t\tPersonUtil.persisted(new Person());\n" + "\t\t}"); }
Kotlin
可以使用原始字符串 (raw string
) 来表示复杂的字符串字面量, 使用 3 个双引号包围:fun mainMethodInString(): String { return """ public static void main(String[] args) { PersonUtil.persisted(new Person()); } """ }
需要注意的是在
raw string
中, 没有转义字符的概念, 因此诸如\n,\t
等都会原样输出.字符串模板 (
String Template
)Kotlin
字符串模板语法与Swift
非常类似, 区别是后者使用\()
在字符串中添加表达式, 而Kotlin
使用${}
, 当大括号中的表达式为简单变量名时, 大括号可以省略:fun hello(name: String) : String { return "Hello $name" }
字符串模板也可以在
raw string
中使用, 但raw string
不支持\
转义, 因此如果想输出$
本身, 请使用${'$'}
.
数组 (Array
)
kotlin
使用Array
表示数组, 要声明一个Int
数组a
, 可以使用var a: Array<Int>
也可以使用
var a: IntArray
避免自动装箱拆箱的代价. 要快速声明和初始化一个
Int
数组, 可以使用便捷方法arrayOf()
, 同样为了避免装箱拆箱的代价,kotlin
还提供了原始数据类型的便捷操作,var array = intArrayOf(1, 2, 3)
. 对于 6 种数据类型,kotlin
都提供了相应的方法xxxArrayOf
.
控制流
if
表达式
在
kotlin
中if
语句是一个表达式, 也就是说if
语句拥有返回值. 因为if
语句已经提供了三目运算符的功能,kotlin
没有三目运算符号:// java int absA = a >= 0 ? a : -a;
// kotlin val absA = if (a >= 0) a else -a
if
分支还可以是代码块, 如果不嫌麻烦的话上面的代码也可以写成:// kotlin var absA: Int if (a >= 0) { absA = a } else { absA = -a }
如果
if
分支是一个代码块, 那么其最后一个表达式就是该分支的返回值.// kotlin var absA = if (a >= 0) { println("a is positive") a } else { println("a is negative") -a }
Note : 当把
if
作为表达式 (即在if
中返回一个值并赋值给某个变量) 时, 必须提供一个else
分支.
when
表达式
kotlin
中使用 when
语句代替 java
中的 switch
语句, 与 if
类似, when
也是一个表达式. kotlin
中的 when
表达式比 java
(甚至其他任何语言) 的 switch
语句功能都要强大. when
表达式的语法表示如下:
when (used by atomicExpression)
: "when" ("(" expression ")")? "{"
whenEntry*
"}"
;
whenEntry (used by when)
: whenCondition{","} "->" controlStructureBody SEMI
: "else" "->" controlStructureBody SEMI
;
whenCondition (used by whenEntry)
: expression
: ("in" | "!in") expression
: ("is" | "!is") type
;
不像
java
的switch
语句要求所有case
都必须是常量表达式,when
表达式中的case
项可以是任何与when
参数类型一致的常量, 变量, 表达式, 函数调用或类型判断 (is,!is
) 和包含关系判断 (in,!in
):// kotlin open class Person(open val age: Int = 0) class Employee(override val age: Int = 0, val leader: Person?) : Person(age) { fun work() { println("working ") } } val person = Person(22) val lisi = Employee(21, person) val elites = arrayOf(lisi) val superElites = arrayOf(lisi) val zhangsan = Employee(18, lisi) val description = when (lisi) { is Employee -> { lisi.work() "一个普通的员工" } in elites, in superElites -> "一个精英" zhangsan.leader -> "张三的领导" else -> "默默无闻" } println(description) // 一个普通的员工
尽管没有显式的
break
语句, 但是只要when
表达式中按顺序从上而下有满足条件的分支成立,when
表达式就会返回.当
when
作为语句时, 其分支代码块中的返回值相当于被忽略 (与if
类似):// kotlin when { person === lisi -> println("此人正是李四") person === zhangsan.leader -> println("此人是张三的领导") zhangsan in elites -> println("张三是个人才啊") }
kotlin
中==
相当于调用equals
方法,===
相当于java
中的==
, 即比较引用
for
循环
for
循环能够迭代任何提供了 iterator()
函数的对象. 其语法结构定义如下:
for (used by loop)
: "for" "(" annotations (multipleVariableDeclarations | variableDeclarationEntry) "in" expression ")" controlStructureBody
;
其中迭代变量可以使用注解, 也可以有多个迭代变量(构成元组), 这在遍历提供了
componentn
函数的对象集合上可以很方便的destructure
对象, 典型的示例是在遍历Map
和带下标遍历数组时:// kotlin for (@NotNull elite in elites) { println(elite) } val map = mapOf(1 to "one", 2 to "two", 3 to "three") for ((key, value) in map){ println("$key 对应英文是 $value") } for ((index, value) in array.withIndex()) { println("the element at $index is $value") }
while
循环
Nothing special
函数
kotlin
中声明函数使用 fun
关键字, 大有一语双关之意. 声明函数的语法规则如下:
function (used by memberDeclaration, declaration, topLevelObject)
: modifiers "fun"
typeParameters?
(type ".")?
SimpleName
typeParameters? valueParameters (":" type)?
typeConstraints
functionBody?
;
functionBody (used by getter, setter, function)
: block
: "=" expression
;
首先是
modifiers
修饰符, 与java
一样,kotlin
函数也有public
,protected
,private
等访问权限修饰符和abstract
,final
等修饰符接着是关键字
fun
然后是泛型参数, 这在声明泛型方法时可以用上:
fun <T : Comparable<T>> max(n1: T, n2: T): T { return if (n1 > n2) n1 else n2 }
接着是方法名, 方法名前面是可选的
type.
, 在使用kotlin
给已有的类添加方法时, 需要指定该方法是添加在哪个类上的, 比如我们想给String
类添加一个本地化的方法localized
, 我们需要指定type
说明该方法是添加在String
上的:fun String.localized(): String { return if (this == "hello") "你好" else "不懂" }
接着是函数参数,
kotlin
的参数声明使用pascal
的写法, 即name: type
, 使用逗号分隔多个参数.接着是类型约束, 在声明泛型函数时有额外约束可以使用
where
语句指定最终是函数体, 函数体可以是代码块, 当函数体只有一行时, 直接用
=
:fun <T:Comparable<T>> max(a: T, b: T ): T = if (a > b) a else b
中缀函数 (infix
)
顾名思义, 中缀函数是指在调用函数时, 可以使用形如 a plus b
的中缀表达式形式调用的函数. 由于 kotlin
中缀函数的存在, 使得很多看似平常的函数调用看起来像魔法一样. 要能够使用中缀形式调用的函数需要满足以下几个条件:
- 必须是成员函数或者
extension
函数 - 有且只有一个参数
- 使用
infix
修饰符修饰
从kotlin
类库中找几个中缀函数作为参考:
public infix fun shl(bitCount: Int): Int // 左移位运算, 1 shl 2 = 4
public infix fun shr(bitCount: Int): Int // 右移位运算, 4 shr 2 = 1
public infix fun ushr(bitCount: Int): Int // 无符号右移, -4 ushr 2 = 1073741823
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
最后一个函数是快速创建一个二元组的中缀函数, 其调用形式为 1 to "one"
, 由于这种方便的创建方式, kotlin
提供的快速创建一个 map
的函数也非常方便和简洁:
class ModelAndView(viewName: String, model: Map<String, Any?>)
fun index(): ModelAndView {
// ...
return ModelAndView("index", mapOf("name" to "kid", "age" to 23))
}
参数默认值/命名参数
kotlin
函数中参数可以有默认值, 在参数声明后使用 = <default>
为该参数指定默认值为 <default>
:
fun sayHello(name: String = "world", times: Int = 1) {
repeat(times) {
println("Hello $name")
}
}
sayHello() // Hello world
在调用函数时, 对于已经指定了默认值的参数, 可以不传递参数. 不一定要按照函数参数的声明顺序进行传递参数, 但此时必须指定要传递的参数名称(参数的命名就是该函数的形参名), 有了该特性可以减少很多的重载函数, 如上函数 sayHello
可以按照如下调用:
sayHello(times = 2, name = "kid")
sayHello(times = 2)
sayHello(name = "kid")
我们将该特性用在 JavaBean
的构造函数中:
open class Person(var id: Int? = null, var name: String? = null, var age: Int? = null)
val p1 = Person(id = 1)
val p2 = Person(name = "kid", age = 23)
val p3 = Person(id = 2, age = 23)
若使用 java
实现相同的构造函数, 对于一个有三个字段的类, 要提供 (3 + 6 + 6)=15
个构造函数(如果参数类型不冲突的话).
Unit
返回值
kotlin
中, 当声明方法的返回值为 Unit
时, 表明该方法返回 Unit
(不返回任何东西). Unit
类似于 java
中的 void
. 对于返回 Unit
的函数, 其返回类型可以不指定, 其函数体也不需要显式的返回 Unit
, 因此以下几个函数是等价的:
// kotlin
// 显式指定
fun a(): Unit {
return Unit
}
// 不指定返回类型
fun b() {
return Unit
}
// 不显式返回
fun c(): Unit {
}
高阶函数和 lambda
高阶函数是指接受函数作为参数或者返回函数作为返回值的函数.
官网上提供了一个比较复杂的例子详细说明了高阶函数 戳这里, 这里借用学习 swift
时经常被举例的另一个简单的例子介绍 kotlin
的高阶函数, 以供对比.
要实现一个函数
adder
接收一个数字参数a
, 然后返回一个新的函数, 这个新的函数接收一个数字参数, 返回该参数加上a
之后的值. 比如说:
val addTen = adder(10) // addTen 是一个函数
println(addTen(10)) // 20
val addTwo = adder(2)
println(addTwo(10)) // 12
函数作为返回值
kotlin
中, 将函数作为参数或者返回值时, 其类型的写法与在声明函数时写法有以下几点不同:
- 不需要
fun
关键字和函数名 - 参数列表和返回值之间的
:
使用->
代替
比如直接声明一个 addTwo
函数时是这样的:
fun addTwo(n: Int): Int { return n + 2 }
如果要将与这个函数签名一样的函数作为参数或者返回值, 则使用:
((n: Int) -> Int)
所以以上构建加法器的函数可以这样实现:
// kotlin
fun adder(n: Int): ((a: Int) -> Int) { // 返回值是这种函数类型: ((a: Int) -> Int)
return fun (a: Int): Int { // 将函数返回, 这里的函数写法与普通写法一样
return a + n
}
}
函数作为参数
如果上面的例子中, 我们传入的参数 a
也希望是通过传入一个函数计算出来的, 那么原来的参数 a
就需要使用一个函数作为参数:
// kotlin
fun adder(aFun: () -> Int): ((a: Int) -> Int) {
return fun (a: Int): Int {
return a + aFun()
}
}
在调用的时候就需要传入一个函数作为参数:
// kotlin
val tenFun = fun(): Int { return 10 }
val addTen = adder(tenFun)
println(addTen(10))
lambda
以上调用含有函数作为参数的函数时略显麻烦, 因为要先创建一个函数, 赋值给 tenFun
, 然后再把这个变量传递给函数 adder
, 即使要传递的参数函数仅简单地返回 10
. kotlin
中, 当在传递函数作为参数时, 可以传递一个 lambda
表达式, lambda
表达式有以下特点:
- 总是使用
{}
包围 - 如果
lambda
有参数, 则参数写在->
前面 lambda
的内容写在->
后面lambda
的参数类型可以省略 (可以被推断出来)
将上面的例子中改用 lambda
进行传递函数:
// kotlin
val addTen = adder({ -> 10 })
因为该 lambda
没有参数, 因此 ->
可以被省略:
如果
lambda
表达式只有一个参数, 那么该参数也可以被省略, 在lambda
内部可以使用it
引用到该参数.
// kotlin
val addTen = adder({ 10 })
当该 lambda
表达式是函数的最后一个参数时, 该 lambda
表达式可以被提到括号外面:
// kotlin
val addTen = adder() { 10 }
如果该 lambda
表达式还是函数的唯一一个参数, 函数调用的 ()
也可以省略掉:
// kotlin
val addTen = adder { 10 }
kotlin
的类库中有大量利用lambda
表达式这种简洁表示方法的函数, 提供了很多类似语言本身提供的功能, 但实际是语法糖的 “特效”
类
kotlin
与 java
一样使用关键字 class
声明一个类. 但 kotlin
中的类的结构与 java
的不太一样, kotlin
声明一个类的语法规则如下:
class (used by memberDeclaration, declaration, topLevelObject)
: modifiers ("class" | "interface") SimpleName
typeParameters?
primaryConstructor?
(":" annotations delegationSpecifier{","})?
typeConstraints
(classBody? | enumClassBody)
;
primaryConstructor (used by class, object)
: (modifiers "constructor")? ("(" functionParameter{","} ")")
;
classBody (used by objectLiteral, enumEntry, class, companionObject, object)
: ("{" members "}")?
;
其中 class | interface 类名<泛型类型>
与 java
类似, 后面有可选的 primaryConstructor
, 注解 annotations
和类型约束 typeConstraints
. 这些在 kotlin
中都称为类头部 class header
, 紧接着才是大括号和类内容. 如果类中没有内容, 大括号也可以被省略.
// kotlin
class Test
构造函数 -
primary constructor
上面说过, 在
class header
中可以有primaryConstructor
, 但一个类最多只能有一个primaryConstructor
,primaryConstructor
中可以接收参数, 用于构造类对象时的需要, 由于primaryConstructor
是class header
的一部分, 因此primaryConstructor
没有包含代码的能力, 如果需要在构造对象时执行初始化代码, 使用初始化代码块, 与java
中的初始化代码块不一样,kotlin
的初始化代码块多了init
关键字修饰, 更加直白.primaryConstructor
同样可以有访问修饰符和注解:// kotlin class UserBean public @Inject constructor(name: String, age: Int) { var name: String? = null var age: Int? = null init { this.name = name this.age = age } }
- 其中
public
表示该构造函数的访问权限修饰符, 由于primaryConstructor
的默认修饰符是public
, 因此可以省略; - 当
primaryConstructor
前面没有修饰符和注解 (即@Inject
) 的时候, 关键字constructor
也可以省略; - 虽然
primaryConstructor
不能包含代码块, 但在其内部声明的参数却可以在class body
中声明属性时作为初始化值使用;
// kotlin // 简化后的代码 class UserBean (name: String?, age: Int?) { var name: String? = name var age: Int? = age }
为了在
primaryConstructor
中声明并且初始化属性,kotlin
还提供了更简便的方法:// kotlin class UserBean (var name: String?, var age: Int?) { // ... }
- 其中
构造函数 -
secondary constructor
由于
primary constructor
最多只能有一个, 当需要声明多个构造函数时, 就需要使用secondary constructor
. 为UserBean
添加重载的构造函数, 当age
未知时赋为null
:// kotlin class UserBean (var name: String?, var age: Int?) { constructor(name: String?) : this(name, null) }
- 所有
secondary constructor
都必须直接或间接的调用primary constructor
, 调用方法是在方法体前使用: this()
; - 当构造函数的函数体为空的时候,
{}
也可以省略;
- 所有
NOTE: 前面说过函数的参数可以有默认值, 考虑到构造函数也是函数, 因此构造函数也可以有默认值, 并且也可以通过命名参数进行调用, 以上代码可以进一步简化:
// kotlin
class UserBean (var name: String? = null, var age: Int? = null)
// 使用:
val u1 = UserBean() // name 和 age 都是 null
val u2 = UserBean("kid", 23)
val u3 = UserBean(age = 23) // name = null, age = 23
val u4 = UserBean(name = "kid") // name = "kid", age = null
kotlin
中创建对象时不需要new
关键字
继承
正如所有的
java
类都继承自Object
类, 所有的kotlin
类都继承自Any
类. 当声明一个类的时候, 默认是final
修饰的, 如果要允许该类被继承, 需要使用open
修饰:// kotlin open class UserBean
Kotlin
中使用:
代替java
中的extends
关键字表示继承如果子类拥有
primary constructor
, 那么父类必须在子类primary constructor
后进行初始化 (调用其构造函数):// kotlin class StudentBean: UserBean()
如果子类没有
primary constructor
, 那么其每一个secondary constructor
都必须使用super
关键字初始化父类:class StudentBean : UserBean { constructor(name: String?) : super(name) }
子类的
secondary constructor
默认会尝试调用父类的无参构造函数, 如果父类有无参构造函数, 那么子类的secondary constructor
可以不用显式调用super()
// kotlin class StudentBean : UserBean { constructor() }
抽象类
使用
abstract
关键字修饰的类即抽象类, 抽象类默认就是open
的, 因此不需要open
关键字修饰.伴随对象 (
companion objects
)不像
java
,kotlin
中没有静态方法和静态字段,kotlin
推荐在大多数情况下都可以使用包级别的函数替代静态方法:package cn.groupy.share.kotlin.utils fun toCamel(string: String): String { var result = string // ... return result }
这样就能直接调用
toCamel("abc_def")
而不用类名.如果需要访问类的内部信息, 或该方法与一个类确实相关联, 需要使用类名来调用一个静态方法 (比如工厂方法) , 此时可以将该方法作为该类的伴随对象的成员:
// kotlin class Session class SessionFactory { companion object { fun openSession(): Session { return Session() } } } // 使用 val session = SessionFactory.openSession()
更确切的说, 在伴随对象内部的所有字段, 方法都可以被外部类像在
java
中调用静态字段和静态方法一样调用.
语法糖
函数/属性扩展 - Extensions
kotlin
提供了不用继承一个类就能够给它添加方法 (和属性) 的能力, 这种能力叫做Extension
.
扩展函数
为了把一个函数添加到一个已有的类上, 需要在声明函数的时候在函数名前面加上接收对象的类型.
假如我们想给字符串
String
类添加一个将首字母变大写的函数capitalize
, 在声明capitalize
的时候需要在前面加上该函数的接收对象 (即要扩展的类), 然后在函数内部就能够使用this
引用到调用该函数时的接收对象了:// kotlin fun String.capitalize(): String { // 此时可以使用 this 引用到该方法被调用时的字符串 if (this.isEmpty()) return this if (this[0].isLowerCase()) return substring(0, 1).toUpperCase() + substring(1) return this }
以上只是一个示例,
kotlin
标准类库中已经提供了该方法了扩展属性
为一个类添加扩展属性与为其添加扩展函数类似, 都是在属性前加接收类型.
假如我们想为字符串添加一个属性
upperCaseCount
表示该字符串中大写字母的个数, 在声明完属性时同时为该属性指定一个getter
函数:// kotlin val String.size: Int get() = this.filter { it.isUpperCase() }.count()
扩展函数的坑
扩展函数实际并不会改变类, 仅仅是使得该方法能够被该类的实例使用 .
来调用而已. 扩展方法是 静态解析 的, 也就是说静态方法无法享受到面向对象语言的 多态 特性了, 考虑以下代码的输出:
// kotlin
open class Animal
open class Cat: Animal()
class Tiger : Cat ()
fun Animal.whoAmI() {
println("Animal")
}
fun Cat.whoAmI() {
println("Cat")
}
fun Tiger.whoAmI() {
println("Tiger")
}
fun test() {
var animal: Animal = Animal()
animal.whoAmI() // (1)
animal = Cat()
animal.whoAmI() // (2)
animal = Tiger()
animal.whoAmI() // (3)
val animals: Array<Animal> = arrayOf(Animal(), Cat(), Tiger())
animals.forEach { it.whoAmI() } // (4)
}
以上代码将会输出:
Animal
Cat
Tiger
Animal
Animal
Animal
对于第 1 到 3 的输出, 看起来像是扩展函数还具有多态的特性, 但是第 4 到 6 个为什么就不行了呢? 原因是编译器在调用代码 (1) ~ (3) 的时候很智能地检测了一下 animal
的类型, 发现 animal
有更具体的类型, 并且该类型都有 whoAmI
方法, 因此调用了更加具体类型的 whoAmI
方法, 而在执行代码 (4) 时, 由于泛型擦除, 编译器首先要检测 animals
中取出来的对象是否是 Animal
类型的, 就不会再检测一遍该对象是否有更具体的类型了.
通过查看编译后的字节码可以看到在调用代码 (2) , (3) 中的 whoAmI
之前确实有对 animal
变量做更详细类型的检测:
Code:
0: new #9 // class cn/groupy/share/kotlin/Animal
3: dup
4: invokespecial #12 // Method cn/groupy/share/kotlin/Animal."<init>":()V
7: astore_1
8: aload_1
9: invokestatic #18 // Method cn/groupy/share/kotlin/ExtensionKt.whoAmI:(Lcn/groupy/share/kotlin/Animal;)V
12: new #20 // class cn/groupy/share/kotlin/Cat
15: dup
16: invokespecial #21 // Method cn/groupy/share/kotlin/Cat."<init>":()V
19: checkcast #9 // class cn/groupy/share/kotlin/Animal
22: astore_1
23: aload_1
24: checkcast #20 // class cn/groupy/share/kotlin/Cat <调用 (2)前>
27: invokestatic #24 // Method cn/groupy/share/kotlin/ExtensionKt.whoAmI:(Lcn/groupy/share/kotlin/Cat;)V
30: new #26 // class cn/groupy/share/kotlin/Tiger
33: dup
34: invokespecial #27 // Method cn/groupy/share/kotlin/Tiger."<init>":()V
37: checkcast #9 // class cn/groupy/share/kotlin/Animal
40: astore_1
41: aload_1
42: checkcast #26 // class cn/groupy/share/kotlin/Tiger <调用 (3) 前>
45: invokestatic #30 // Method cn/groupy/share/kotlin/ExtensionKt.whoAmI:(Lcn/groupy/share/kotlin/Tiger;)V
48: iconst_4
49: anewarray #9 // class cn/groupy/share/kotlin/Animal
52: dup
53: iconst_0
54: new #9 // class cn/groupy/share/kotlin/Animal
57: dup
58: invokespecial #12 // Method cn/groupy/share/kotlin/Animal."<init>":()V
61: aastore
62: dup
63: iconst_1
64: new #20 // class cn/groupy/share/kotlin/Cat
67: dup
68: invokespecial #21 // Method cn/groupy/share/kotlin/Cat."<init>":()V
71: checkcast #9 // class cn/groupy/share/kotlin/Animal
74: aastore
75: dup
76: iconst_2
77: new #26 // class cn/groupy/share/kotlin/Tiger
80: dup
81: invokespecial #27 // Method cn/groupy/share/kotlin/Tiger."<init>":()V
84: checkcast #9 // class cn/groupy/share/kotlin/Animal
87: aastore
88: dup
89: iconst_3
90: ldc #32 // String a
92: checkcast #9 // class cn/groupy/share/kotlin/Animal
95: aastore
96: checkcast #34 // class "[Ljava/lang/Object;"
99: astore_3
100: aload_3
101: checkcast #36 // class "[Lcn/groupy/share/kotlin/Animal;"
104: astore_2
105: aload_2
106: checkcast #34 // class "[Ljava/lang/Object;"
109: astore_3
110: iconst_0
111: istore 4
113: iload 4
115: aload_3
116: arraylength
117: if_icmpge 144
120: aload_3
121: iload 4
123: aaload
124: astore 5
126: aload 5
128: checkcast #9 // class cn/groupy/share/kotlin/Animal
131: astore 6
133: aload 6
135: invokestatic #18 // Method cn/groupy/share/kotlin/ExtensionKt.whoAmI:(Lcn/groupy/share/kotlin/Animal;)V
138: iinc 4, 1
141: goto 113
144: return
如果在调用代码 (3) 之前, 我们在把 Tiger()
赋给 animal
的时候把其类型显式转为 Animal
, 在调用 whoAmI
的时候就不会在做详细类型的检测了:
// kotlin
fun test() {
var animal: Animal = Animal()
animal.whoAmI() // (1)
animal = Cat() as Animal // <-------
animal.whoAmI() // (2)
animal = Tiger() as Animal // <------------
animal.whoAmI() // (3)
val animals: Array<Animal> = arrayOf(Animal(), Cat(), Tiger())
animals.forEach { it.whoAmI() } // (4)
}
此时将会输出:
Animal
Animal
Animal
Animal
Animal
Animal
查看字节码也发现更详细的类型检测没有了.
kotlin
这种 “自作聪明” 的检测有时候会给初学者带来困惑
在 java
中, 我们经常使用像 xxxUtils
这样的类来给 xxx
类提供一些静态工具函数, 虽然 java
类库中使用 XXXs
类名而不是 xxxUtils
, 但也是使用静态函数提供一系列工具方法, java.util.Collections
, java.util.Arrays
和 java 1.7
的 java.lang.Objects
都是这样的例子. kotlin
中扩展函数的作用就是取代这些工具类, 给已有的类提供更加方便的工具函数, 使得在调用这些工具函数的时候代码更佳简洁. 之所以称扩展函数是 kotlin
的语法糖, 就是因为 kotlin
实际上就是把扩展函数编译成我们以前在 java
中的静态函数.
集合框架
kotlin
提供了很多便利的方法能够快速创建各种集合, 前面讲到的arrayOf
就是其中一个. 对于array
,list
,map
,kotlint
都提供了快速创建它们的不可变长度对象 (其存放的元素个数不可变) 的函数:arrayOf
,listOf
,mapOf
和可变长度对象 (除了array
, 因为其本身就是不可变长的) 的函数:mutableListOf
,mutableMapOf
.所有集合类型, 得益于
Extension
, 以前在java
(java 8
) 中要使用stream()
函数才能够进行的流式操作都能直接在集合类上进行了, 并且功能更多; 得益于lambda
, 流操作写出来的代码将更加简洁.// kotlin // 遍历一个列表 val products = arrayOf("newsjet", "groupy", "newsline") products.forEach { println(it) } // 求一个区间的所有偶数的和 (1..100).filter { it % 2 == 0 }.sum() val names = listOf("tracy", "roy", "kid", "ken", "benny", "paddy", "neo", "blues") println(names.sortedBy { it[1] }) // 根据第二个字母排序 [paddy, ken, benny, neo, kid, blues, roy, tracy] println(names.groupBy { it[0] }) // 根据首字母进行分组 {t=[tracy], r=[roy], k=[kid, ken], b=[benny, blues], p=[paddy], n=[neo]} println(names.groupBy { it[0] }.map { Pair(it.key, it.value.count()) }) // 统计各个首字母的名字数 [(t, 1), (r, 1), (k, 2), (b, 2), (p, 1), (n, 1)] println(names.groupBy { it[0] }.map { Pair(it.key, it.value.count()) }.sortedByDescending { it.second }) // 统计完再根据次数降序 class Person(val age: Int = 0, val name: String = "", val weight: Int = 50) val people = arrayOf(Person(24, "kid"), Person(age = 50, name = "paddy"), Person(name = "benny", weight = 200), Person(name = "tracy", weight = 53)) // 求平均年龄 people.sumBy { it.age } / people.size // 求最重的 people.maxBy { it.weight }
try-with-resources
java 6
开始, 可以使用下面的代码使用一个 autoclosable
的资源, 不需要手动关闭该资源:
try (FileReader reader = new FileReader("/tmp/a")) {
} catch (Exception e) {
}
在 kotlin
中可以使用 use
函数:
FileReader("/tmp/a").use {
// 使用 it 引用
}
use
确实是一个函数, 该函数的唯一参数是一个 lambda
表达式, 因此函数调用的括号 ()
被省略, 并且在 lambda
内部可以使用 it
引用到 lambda
的参数. 说 use
函数是一个语法糖就是因为该函数内部只是替我们把本应该手动写的代码写好了而已:
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
var closed = false
try {
return block(this)
} catch (e: Exception) {
closed = true
try {
this?.close()
} catch (closeException: Exception) {
}
throw e
} finally {
if (!closed) {
this?.close()
}
}
}
更多精彩的语法糖, 请 戳这里 参考官方文档