Kotlin学习笔记之基础知识
Kotlin学习笔记之基础知识
本内容是建立在有java的基础上去学习Kotlin的这门语言的,所以更多的是记录一些与java不同的之处,或者是Kotlin的特性等。
基本类型
在 Kotlin 中,所有东西都是对象,在这个意义上讲我们可以在任何变量上调用成员函数和属性。 一些类型可以有特殊的内部表示——例如,数字、字符和布尔值可以
在运行时表示为原生类型值,但是对于用户来说,它们看起来就像普通的类。 在本节中,我们会描述 Kotlin 中使用的基本类型:数字、字符、布尔值、数组与字符串。
数字
Kotlin 处理数字在某种程度上接近 Java,但是并不完全相同。例如,对于数字没有隐式拓宽转换(如 Java 中
int
可以隐式转换为
long
——译者注),另外有些情况的字面值略有不同。
字面常量
数值常量字面值有以下几种:
- 十进制:
123
- Long 类型用大写
L
标记:123L
- Long 类型用大写
- 十六进制:
0x0F
- 二进制:
0b00001011
Kotlin 同样支持浮点数的常规表示方法:
- 默认 double:
123.5
、123.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
这是完整的位运算列表(只用于
Int
和
Long
):
shl(bits)
– 有符号左移 (Java 的<<
)shr(bits)
– 有符号右移 (Java 的>>
)ushr(bits)
– 无符号右移 (Java 的>>>
)and(bits)
– 位与or(bits)
– 位或xor(bits)
– 位异或inv()
– 位非
浮点数比较
相等和大小的 比较与java相同,Kotlin增加了一种区间实例与区间检测,如下:
- 区间实例以及区间检测:
a..b
、x in a..b
、x !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
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
val asc = Array(5, { i -> (i * i).toString() })
如上所述,[] 运算符代表调用成员函数 get() 和 set()。
注意: 与 Java 不同的是,Kotlin 中数组是不型变的(invariant)。这意味着 Kotlin 不让我们把 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)
”。