目录

Kotlin学习笔记之基础知识

Kotlin学习笔记之基础知识

本内容是建立在有java的基础上去学习Kotlin的这门语言的,所以更多的是记录一些与java不同的之处,或者是Kotlin的特性等。

基本类型

在 Kotlin 中,所有东西都是对象,在这个意义上讲我们可以在任何变量上调用成员函数和属性。 一些类型可以有特殊的内部表示——例如,数字、字符和布尔值可以

在运行时表示为原生类型值,但是对于用户来说,它们看起来就像普通的类。 在本节中,我们会描述 Kotlin 中使用的基本类型:数字、字符、布尔值、数组与字符串。

数字

Kotlin 处理数字在某种程度上接近 Java,但是并不完全相同。例如,对于数字没有隐式拓宽转换(如 Java 中 int

可以隐式转换为 long

——译者注),另外有些情况的字面值略有不同。

字面常量

数值常量字面值有以下几种:

  • 十进制: 123
    • Long 类型用大写 L 标记: 123L
  • 十六进制: 0x0F
  • 二进制: 0b00001011

Kotlin 同样支持浮点数的常规表示方法:

  • 默认 double: 123.5123.5e10
  • Float 用 f 或者 F 标记: 123.5f

注意: 在 Kotlin 中字符不是数字,且 不支持八进制,且字符不是数值

表示方式

在 Java 平台数字是物理存储为 JVM 的原生类型,除非我们需要一个可空的引用(如 Int? )或泛型。 后者情况下会把数字装箱

注意数字装箱不必保留同一性:

val a : Int= 10000

print ( a===a )

// 输出“true”

val boxedA : Int?=a

val anotherBoxedA : Int?=a

print ( boxedA===anotherBoxedA )

// !!!输出“false”!!!

另一方面,它保留了相等性:

val a : Int= 10000

print ( a==a )

// 输出“true”

val boxedA : Int?=a

val anotherBoxedA : Int?=a

print ( boxedA==anotherBoxedA )

// 输出“true”

类型转换

Kotlin 不能像java那样类型那样强制转换,int类型数据不能默认和long类型的数值进行比较大小。

比如在java中:

int a;

long b = 100;

a = b;(在kotlin中这样是不允许的,必须写成:a = b.toInt())

每个数字类型支持如下的转换:

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double
  • toChar(): Char

但是在做计算的过程中,隐式转换又存在,比如:

val l= 1L + 3

// Long + Int => Long

运算

Kotlin支持数字运算的标准集,运算被定义为相应的类成员(但编译器会将函数调用优化为相应的指令)。 参见 。

对于位运算,没有特殊字符来表示,而只可用中缀方式调用命名函数,例如:

val x = (1 shl 2) and 0x000FF000

这是完整的位运算列表(只用于 IntLong ):

  • shl(bits) – 有符号左移 (Java 的 << )
  • shr(bits) – 有符号右移 (Java 的 >> )
  • ushr(bits) – 无符号右移 (Java 的 >>> )
  • and(bits) – 位与
  • or(bits) – 位或
  • xor(bits) – 位异或
  • inv() – 位非

浮点数比较

相等和大小的 比较与java相同,Kotlin增加了一种区间实例与区间检测,如下:

  • 区间实例以及区间检测: a..bx in a..bx !in a..b

字符

之前有提到过,字符不是数字

字符用 Char 类型表示。它们不能直接当作数字

fun check(c: Char) {

if (c == 1) { // 错误:类型不兼容

// ……

}

}

字符字面值用单引号括起来: '1'

。 特殊字符可以用反斜杠转义。 支持这几个转义序列: \t

\b

\n

\r

\'

\"

\\

\$

。 编码其他字符要用 Unicode 转义序列语法: '\uFF00'

数组

数组在 Kotlin 中使用 Array 类来表示,它定义了 get 和 set 函数(按照运算符重载约定这会转变为 [])和 size 属性,以及一些其他有用的成员函数:

class Array private constructor() {

val size: Int

operator fun get(index: Int): T

operator fun set(index: Int, value: T): Unit

operator fun iterator(): Iterator

// ……

}

我们可以使用库函数 arrayOf() 来创建一个数组并传递元素值给它,这样 arrayOf(1, 2, 3) 创建了 array [1, 2, 3]。 或者,库函数 arrayOfNulls() 可以用于创建一个指定大小的、所有元素都为空的数组。

另一个选项是用接受数组大小和一个函数参数的工厂函数,用作参数的函数能够返回给定索引的每个元素初始值:

// 创建一个 Array 初始化为 [“0”, “1”, “4”, “9”, “16”]

val asc = Array(5, { i -> (i * i).toString() })

如上所述,[] 运算符代表调用成员函数 get() 和 set()。

注意: 与 Java 不同的是,Kotlin 中数组是不型变的(invariant)。这意味着 Kotlin 不让我们把 Array 赋值给 Array,以防止可能的运行时失败(但是你可以使用 Array, 参见类型投影)。

Kotlin 也有无装箱开销的专门的类来表示原生类型数组: ByteArray、 ShortArray、IntArray 等等。这些类和 Array 并没有继承关系,但是它们有同样的方法属性集。它们也都有相应的工厂方法:

val x: IntArray = intArrayOf(1, 2, 3)

x[0] = x[1] + x[2]

字符串

字符串除了具有java的特性意外,字符串的元素——字符可以使用索引运算符访问: s[i]

。 可以用

for

循环迭代字符串:

for

( c in str )

{

println ( c )

}

原生字符串

使用三个引号( """

)分界符括起来,内部没有转义并且可以包含换行和任何其他字符:

val text= """

for (c in “foo”)

print(c)

"""

你可以通过 trimMargin() 函数去除前导空格:

val text = """

|Tell me and I forget.

|Teach me and I remember.

|Involve me and I learn.

|(Benjamin Franklin)

“”".trimMargin()

默认 | 用作边界前缀,但你可以选择其他字符并作为参数传入,比如 trimMargin(">")。

备注: 前导空格,我的理解就是去除“|”前面的空格

(“ABC\n123\n456”===(“““ABC

|123

|456"”".trimMargin()))  // 结果为false

(“ABC\n123\n456”==(“““ABC

|123

|456"”".trimMargin())) // 结果为true

字符串模板

字符串可以包含

模板表达式

,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符( $

)开头,由一个简单的名字构成:

val i= 10 val s= “i = $i”

// 求值结果为 “i = 10”

或者用花括号括起来的任意表达式:

val s= “abc”

val str= “$s.length is ${s.length}”

// 求值结果为 “abc.length is 3”

原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $

字符(它不支持反斜杠转义),你可以用下列语法:

val price= "””

${’$’}9.99

"””

包与导入

如果出现名字冲突,可以使用

as

关键字在本地重命名冲突项来消歧义:

import foo . Bar // Bar 可访问

import bar . Bar as bBar // bBar 代表“bar.Bar”

与 Java 不同的是Kotlin 没有单独的

语法; 所有这些声明都用 import

关键字导入。

控制流:if、when、for、while

在 Kotlin 中,

if

是一个表达式,即它会返回一个值。 因此就不需要三元运算符(条件 ? 然后 : 否则),因为普通的

if

就能胜任这个角色。

例如:

传统if:

var max : Int

if

( a>b )

{

max=a

} else

{

max

=

b

}

可以写成:

val max= if

( a>b ) a else b

if

的分支可以是代码块,最后的表达式作为该块的值:

val max= if

( a>b )

{

print (

“Choose a”

)

a

} else

{

print (

“Choose b”

)

b

}

备注:

如果你使用

if

作为表达式而不是语句(例如:返回它的值或者

把它赋给变量),该表达式需要有 else

分支。

When 表达式

when 取代了类 C 语言的 switch 操作符。其最简单的形式如下:

when

( x )

{

1 ->print (

“x == 1”

)

2 ->print (

“x == 2”

)

else -> {

// 注意这个块

print (

“x is neither 1 nor 2”

)

}

}

when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。 when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式, 符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。(像 if 一样,每一个分支可以是一个代码块,它的值是块中最后的表达式的值。)

如果其他分支都不满足条件将会求值 else 分支。 如果 when 作为一个表达式使用,则必须有 else 分支, 除非编译器能够检测出所有的可能情况 都已经覆盖了。

如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:

when

( x )

{

0

,

1 ->print (

“x == 0 or x == 1”

)

else ->print (

“otherwise”

)

}

我们可以用任意表达式(而不只是常量)作为分支条件:

when

( x )

{

parseInt ( s ) ->print (

“s encodes x”

)

else ->print (

“s does not encode x”

)

}

我们也可以检测一个值在(

in

)或者不在(

!in

)一个

或者集合中:

when

( x )

{

in

.

10 ->print (

“x is in the range”

)

in validNumbers->print (

“x is valid”

)

! in

.

20 ->print (

“x is outside the range”

)

else ->print (

“none of the above”

)

}

另一种可能性是检测一个值是(

is

)或者不是(

!is

)一个特定类型的值。注意: 由于

,你可以访问该类型的方法和属性而无需

任何额外的检测。

fun hasPrefix ( x : Any )

when

( x )

{

is String->x . startsWith (

“prefix”

)

else -> false

}

when

也可以用来取代

if

else

if

链。 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:

when

{

x . isOdd (

) ->print (

“x is odd”

)

x . isEven (

) ->print (

“x is even”

)

else ->print (

“x is funny”

)

}

For 循环

for

循环可以对任何提供迭代器(iterator)的对象进行遍历,这相当

于像 C# 这样的语言中的 foreach

循环。语法如下:

for

( item in collection ) print ( item )

或者代码块

for

( item : Int in ints )

{

//

……

}

for

可以循环遍历任何提供了迭代器的对象。即:

有一个成员函数或者扩展函数 iterator()

,它的返回类型

有一个成员函数或者扩展函数 next()

,并且

有一个成员函数或者扩展函数 hasNext()

返回 Boolean

如果你想要通过索引遍历一个数组或者一个 list,你可以这么做:

for

( i in array . indices )

{

print ( array [ i ]

)

}

注意:

这种“在区间上遍历”会编译成优化的实现而不会创建额外对象。

或者你可以用库函数 withIndex

for

(

( index , value ) in array . withIndex (

)

)

{

println (

“the element at $index is $value”

)

}

While 循环

while

do

..

while

照常使用

备注:

在循环中 Kotlin 支持传统的

break

continue

操作符

Break 与 Continue 标签

在 Kotlin 中任何表达式都可以用标签(

label

)来标记。 标签的格式为标识符后跟 @

符号,例如: abc@

fooBar@

都是有效的标签

。 要为一个表达式加标签,我们只要在其前加标签即可。

loop@ for

( i in

.

100

)

{

// ……

}

现在,我们可以用标签限制

break

或者

continue

loop@ for

( i in

.

100

)

{

for

( j in

.

100

)

{

if

( …… ) break @loop

}

}

标签限制的 break 跳转到刚好位于该标签指定的循环后面的执行点。

continue

继续标签指定的循环的下一次迭代。

标签处返回

Kotlin 有函数字面量、局部函数和对象表达式。因此 Kotlin 的函数可以被嵌套。 标签限制的

return

允许我们从外层函数返回。 最重要的一个用途就是从 lambda 表达式中返回。

fun foo (

)

{

ints . forEach {

if

( it== 0

) return

// nonlocal return from inside lambda directly to the caller of foo()

print ( it )

}

}

这个

return

表达式从最直接包围它的函数即 foo

中返回。 (注意,这种非局部的返回只支持传给

的 lambda 表达式。) 如果我们需要从 lambda 表达式中返回,我们必须给它加标签并用以限制

return

fun foo (

)

{

ints . forEachlit@ {

if

( it== 0

)

return @lit

print ( it )

}

}

或者简写成 fun foo (

)

{

ints . forEach {

if

( it== 0

) return @forEach

print ( it )

}

}

或者,我们用一个

替代 lambda 表达式。 匿名函数内部的

return

语句将从该匿名函数自身返回

fun foo (

)

{ ints . forEach ( fun

( value : Int )

{

if

( value== 0

) return

// local return to the caller of the anonymous fun,

i.e. the forEach loop

print ( value )

}

)

}

当要返一个回值的时候,解析器优先选用标签限制的 return,即

return @a 1

意为“从标签 @a

返回 1”,而不是“返回一个标签标注的表达式 (@a 1)

”。