变量和常量
常见变量类型及变量的取值范围
变量用 var 关键字声明,常见的数据类型如下:
Byte、Short、Int、Long、Float、Double、String、Boolean
数据类型 | 取值范围 | 备注 |
---|
Byte | -128~127 | 8bit |
Short | -32768~32767 | 16bit |
Int | -2147483648~2147483647 | 32bit |
Long | -9223372036854775808~9223372036854775807 | 64bit |
Float | 1.4E-45~3.4028235E38 | |
Double | 4.9E-324~1.7976931348623157E308 | |
String | | |
Boolean | | |
1
2
3
4
5
6
7
8
9
| fun main(args: Array<String>) {
var aByte: Byte = Byte.MAX_VALUE; // 127
var aByte1: Byte = Byte.MIN_VALUE; // -128
println(aByte)
println(aByte1)
var aInt:Int = 0b0011 // 二进制赋值
println(aInt) // 3
}
|
取最大值最小值通过如 Byte.MAX_VALUE
或 Byte.MIN_VALUE
来获取;
用二进制来赋值,前面加上 0b
表示二进制赋值
变量类型推断(Type inference)
由编译器推断变量的类型;变量不能只声明而没有具体的值
1
2
3
4
5
6
7
8
9
10
| fun main(args: Array<String>) {
var name = "hacket很厉害";
println(name)
name = "张三"
println(name)
// name = 8 // 报错,
// println(name)
}
|
变量显示类型声明
显示的指明变量的类型;可以只声明变量而不用初始化
1
2
3
4
5
6
7
8
9
10
11
| fun main(args: Array<String>) {
var a // 报错,This variable must either have a type annotation or be initialized
var b:Int // 不报错
var i:Int = 9
var j:Long = 999999999999999999;
var name:String = "hacket"
}
|
常量 val
常量用 val 关键字声明,声明后不能修改值
小结
- var 声明变量
- val 声明常量
- 不同的数据类型用不同的容器保存
- kotlin 会通过类型推断自动推断数据类型
- 用: 可以显示的指定数据类型
控制语句
条件控制
if-else
同 java 中的 if else 用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| fun gradeStude2(scroe: Int) {
if (scroe == 10) {
println("满分,棒棒哒")
} else if (scroe == 9) {
println("干的不错")
} else if (scroe == 8) {
println("还可以")
} else if (scroe == 7) {
println("还需努力")
} else if (scroe == 6) {
println("刚好及格")
} else {
println("需要加油哦")
}
}
|
kotlin 中的 if else 可以用成 Java 中的三目运算符功能,kotlin 中没有三目运算符;允许 if else 返回值
1
| String s = if (i>0) "大于0" else "不大于0"
|
when
- 和 Java 语言中的 switch 类似,Kotlin 中没有 switch/case,但是比 switch 强大
2. when 也允许有返回值
3. Java 中的 switch/case 只能跟常量,case 不能跟变量,Kotlin 去掉了这个限制,允许引入变量;也可以引入具体的运算表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
| fun gradeStude(scroe: Int) {
when (scroe) {
10 -> println("满分,棒棒哒")
9 -> println("干的不错")
8 -> println("还可以")
7 -> println("还需努力")
6 -> println("刚好及格")
else -> println("需要加油哦")
}
}
fun main(args: Array<String>) {
gradeStude(9)
}
|
- Java 的 case 仅仅对应一个常量值,when 机制可以 5 个常量并排写一起用逗号隔开即可;如果几个常量是连续的数字,用 in,或者!in 不在区间内范围。
1
2
3
4
5
6
7
8
9
10
| fun testWhen() {
var count = 20
var s = when(count){
1,3,5,7,9 -> "凉风有信的谜底"
in 13..19 -> "秋月无边的谜底"
!in 6..10 -> "当里的当,少侠你来猜猜"
else -> "好诗,这真是一首好诗!"
}
println(s)
}
|
- 还可以跟 is XXX 类型判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| fun testIs() {
var str = ""
if (str is String) {
}
var count = 6 % 3
var countType = when (count) {
0 -> count.toLong()
1 -> count.toDouble()
else -> count.toFloat()
}
var result = when (countType) {
is Long -> "Long了啊"
is Double -> "Double了啊"
else -> "Float了啊"
}
println(result)
}
|
- when 跟表达式判断
1
2
3
4
5
6
| when {
processName == app.applicationInfo.packageName -> return MainAppRuntime()
processName.endsWith(":core") -> return CoreRuntime()
processName.endsWith(":push") -> return PushRuntime()
else -> throw IllegalArgumentException("不认识的进程,进程名:$processName")
}
|
7.when 跟 return
1
2
3
4
5
6
7
8
| fun createRuntime(app: Application, processName: String): AbsRuntime {
return when {
processName == app.applicationInfo.packageName -> MainAppRuntime()
processName.endsWith(":core") -> CoreRuntime()
processName.endsWith(":push") -> PushRuntime()
else -> throw IllegalArgumentException("不认识的进程,进程名:$processName")
}
}
|
循环控制
for
标准 for
1
2
3
| for(item量 in list step 跳过值){
循环体
}
|
下标 indices
1
2
3
4
5
6
7
8
9
10
11
12
| fun testFor() {
var list = listOf(1, 2, 3, 4, 5, 6)
for (i in list.indices) {
var get = list[i]
var get1 = list.get(i)
}
var array = arrayOf(1, 2, 3, 4, 1)
for (i in array.indices) {
var item = array[i]
}
}
|
进阶 for
- until 升序,默认一步;左闭右开区间
- step 升序,跨越几步
- downTo 降序,默认一步
1
2
3
4
5
6
7
8
| // 左闭右开区间,合法值包括11,不包括66
for(i in 11 until 66) {}
// 每次递增4,包括23也包括89
for(i in 23..89 step 4) {}
// 50递减到7,包括50包括7
for(i in 50 downTo 7){}
|
for 配合 range
- 用
..
来表示区间 - 定义首尾闭区间:1..100(包括首尾即 1 和 100)
- 定义首闭尾开区间:1 until 100(包括 1 不包括 100)
获取 range 总数: count() 方法
反转 range: reversed() 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| fun main(args: Array<String>) {
// rangeDemo(100);
var nums = 1..100; // [1,100]
// printRange(nums)
println("nums总数:"+nums.count());
var nums1 = 1 until 100;
// printRange(nums1) // [1,100)
var num2 = 1..16
printRange(num2, 2)
var num3 = num2.reversed();
}
fun printRange(nums: IntRange) {
for (num in nums) {
print(" " + num)
}
}
fun printRange(nums: IntRange, step: Int = 1) {
for (num in nums step step) {
print(" " + num)
}
}
|
while、do while
和 Java 一样
kotlin 之 loop 和 range
Kotlin 在 for、forEach、forEachIndexed 中如何 continue 和 break
Java 中的 forEach
- Java8 中使用 return,会跳出当前循环,继续下一次循环,作用类似 continue;
- Java8 中的 lambda 表达式 foreach() 不支持 continue 和 break 关键字
for 中的 continue 和 break 和 Java 一样
Break 与 Continue 标签
在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、fooBar@都是有效的标签(参见语法)。 要为一个表达式加标签,我们只要在其前加标签即可。
1
2
3
| loop@ for (i in 1..100) {
// ……
}
|
continue
- 不带 label
1
2
3
4
5
6
7
8
9
10
11
| fun testForContinue() {
println("test for begin")
for (index in 0 until 4) {
if (index == 1) {
println("test for continue index=$index")
continue
}
println("test for index=$index")
}
println("test for end")
}
|
结果:
1
2
3
4
5
6
| test for begin
test for index=0
test for continue index=1
test for index=2
test for index=3
test for end
|
- 带 label
1
2
3
4
5
6
7
8
9
10
11
| fun testForContinue() {
println("test for begin")
loop@ for (index in 0 until 4) {
if (index == 1) {
println("test for continue index=$index")
continue@loop
}
println("test for index=$index")
}
println("test for end")
}
|
输出
1
2
3
4
5
6
| test for begin
test for index=0
test for continue index=1
test for index=2
test for index=3
test for end
|
break
单层循环 break
1
2
3
4
5
6
| for (i in 1..10 step 1) {
if (i == 4) {
break
}
println(i)
}
|
跳出多层循环,用 xxx@
做标记,跳出时用 @xxx
1
2
3
4
5
6
7
8
| out@ for (i in 1..10 step 1) {
for (j in 1..4) {
if (j == 3) {
break@out
}
println("$i -- $j")
}
}
|
forEach、forEachIndexed 中的 continue 和 break
forEach return (forEach 默认 return 跳出函数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // 直接return到testForeachReturn
fun testForeachReturn() {
println("test start")
val array = arrayOf(1, 2, 3, 4)
array.forEach {
println("forEach start $it")
if (it == 2) {
println("forEach return $it")
return
}
println("forEach end $it")
}
println("test end")
}
fun main(args: Array<String>) {
println("main start")
testForeachReturn()
println("main end")
}
|
forEach
中 return,是直接 return 到该函数了。
结果:
1
2
3
4
5
6
7
| main start
test start
forEach start 1
forEach end 1
forEach start 2
forEach return 2
main end
|
编译成 class,可以看到 return,是直接 return 到 testForeachReturn()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| public static final void testForeachReturn() {
String var0 = "test start";
System.out.println(var0);
Integer[] array = new Integer[]{1, 2, 3, 4};
Object[] $receiver$iv = array;
int var2 = array.length;
for(int var3 = 0; var3 < var2; ++var3) {
Object element$iv = $receiver$iv[var3];
int it = ((Number)element$iv).intValue();
String var6 = "forEach start " + it;
System.out.println(var6);
if (it == 2) {
var6 = "forEach return " + it;
System.out.println(var6);
return;
}
var6 = "forEach end " + it;
System.out.println(var6);
}
String var10 = "test end";
System.out.println(var10);
}
|
forEach continue (return@forEach 跳出当前一轮循环,相当于 continue)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // continue
fun testForeachContinue() {
println("test start")
val array = arrayOf(1, 2, 3)
array.forEach continuing@{
println("forEach start $it")
if (it == 2) {
println("forEach return $it")
return@continuing
}
println("forEach end $it")
}
println("test end")
}
fun main(args: Array<String>) {
println("main start")
testForeachContinue()
println("main end")
}
|
结果:
1
2
3
4
5
6
7
8
9
10
| main start
test start
forEach start 1
forEach end 1
forEach start 2
forEach return 2
forEach start 3
forEach end 3
test end
main end
|
可以看到 return@continuing
或者 return@forEach
,相当于 continue,只是结束当前循环。
编译成 class,return@continuing
后面的代码是直接作为 else 块分支
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| public static final void testForeachContinue() {
String var0 = "test start";
System.out.println(var0);
Integer[] array = new Integer[]{1, 2, 3};
Object[] $receiver$iv = array;
int var2 = array.length;
for(int var3 = 0; var3 < var2; ++var3) {
Object element$iv = $receiver$iv[var3];
int it = ((Number)element$iv).intValue();
String var6 = "forEach start " + it;
System.out.println(var6);
if (it == 2) {
var6 = "forEach return " + it;
System.out.println(var6);
} else {
var6 = "forEach end " + it;
System.out.println(var6);
}
}
String var10 = "test end";
System.out.println(var10);
}
|
forEach break (forEach 跳出循环,相当于 break)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // break
fun testForeachBreak() {
println("test start")
run breaking@{
val array = arrayOf(1, 2, 3)
array.forEach continuing@{
println("forEach start $it")
if (it == 2) {
println("forEach return $it")
return@breaking
}
println("forEach end $it")
}
}
println("test end")
}
fun main(args: Array<String>) {
println("main start")
testForeachBreak()
println("main end")
}
|
结果:
1
2
3
4
5
6
7
8
| main start
test start
forEach start 1
forEach end 1
forEach start 2
forEach return 2
test end
main end
|
在 forEach 前面加上 run breaking@{}
,然后 return 到 return@breaking
,相当于 break。
编译成 class,符合了 return@breaking
条件的代码直接走 else break 了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| public static final void testForeachBreak() {
String var0 = "test start";
System.out.println(var0);
Integer[] array = new Integer[]{1, 2, 3};
Object[] $receiver$iv = array;
int var2 = array.length;
int var3 = 0;
while(var3 < var2) {
Object element$iv = $receiver$iv[var3];
int it = ((Number)element$iv).intValue();
String var6 = "forEach start " + it;
System.out.println(var6);
if (it != 2) {
var6 = "forEach end " + it;
System.out.println(var6);
++var3;
} else {
var6 = "forEach return " + it;
System.out.println(var6);
break;
}
}
var0 = "test end";
System.out.println(var0);
}
|
自定义 forEach,未加 inline
kotlin 自带的 forEach:
1
2
3
4
| @kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
|
自带的 forEach 是 inline 修饰的,编译会插入到代码运行处,所以 return 会直接 return 调用函数。
自己编写不带 inline 的:
1
2
3
| fun <T> Array<out T>.forEach2(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
|
不带 label 的 return 编译不过
编译成 class
1
2
3
4
5
6
7
8
9
10
| public static final void forEach2(@NotNull Object[] $receiver, @NotNull Function1 action) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
Intrinsics.checkParameterIsNotNull(action, "action");
int var4 = $receiver.length;
for(int var3 = 0; var3 < var4; ++var3) {
Object element = $receiver[var3];
action.invoke(element);
}
}
|
运算符
位运算符 ^
、&
、|
、~
- 与运算符
&
两个二进制数值如果在同一位上都是 1,则结果中该位为 1,否则为 0,可以认为两个都是 true(1),结果也为 true(1),比如 1011 & 0110 = 0010。 - 或运算符
|
运算规则:两个二进制数值如果在同一位上至少有一个 1,则结果中该位为 1,否则为 0,比如 1011 & 0010 = 1011。 - 非运算符
~
如果位为 0,结果是 1,如果位为 1,结果是 0 - 异或运算符
^
运算规则:两个二进制数值如果在同一位上相同,则结果中该位为 0,否则为 1,比如 1011 & 0010 = 1001。
1
2
3
4
5
6
7
8
9
10
11
12
13
| and(bits) 位与 同Java &
or(bits) 位或 同Java |
inv(bits) 位非 同Java ~
xor(bits) 位异或 同Java ^
shl(bits) 左移 同Java <<
shr(bits) 右移 同Java >>
ushr(bits) 无符号右移 同Java >>>
|
Kotlin 中的 位运算符 只对 Int 和 Long 两种 数据类型 起作用
双冒号 (::
)
支持函数作为参数,是 Kotlin 自带的特性,::
的作用跟这个特性无关,不过它能够使函数调用更为简单。
::
只能将函数作为参数来使用,不能直接用来调用函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| fun main() {
val body = { -> bar1() }
foo1(body)
// 等同于
foo1(::bar1)
}
fun foo1(body: () -> Unit) {
body()
}
fun bar1() {
println("bar1")
}
|
- 不过需要注意的是,作为参数的函数,该函数的参数类型和返回值类型一定要和规定的一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| fun main() {
foo2("xxx", ::bar2) // right
foo2("xxx", ::bar1) // 编译不过,Type mismatch
}
fun bar1() {
print("bar1")
}
fun foo2(content: String, body: (String) -> Unit) {
body(content)
}
fun bar2(string: String) {
print(string)
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| abstract class BaseViewBindingFragment<VB : ViewBinding> : BaseFragment() {
abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
}
class OvoFragment :BaseViewBindingFragment<FragmentOvoBinding>() {
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentOvoBinding
get() = FragmentOvoBinding::inflate
// 等同于
override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentOvoBinding
get() = { inflater, parent, root ->
FragmentOvoBinding.inflate(inflater, parent, root)
}
}
|
Kotlin 等式判断
== 值判断
=== 引用判断
1
2
3
4
5
6
| val ii = java.lang.String("123")
val jj = java.lang.String("123")
println("ii==jj ${ii == jj}") // true
println("ii!=jj ${ii != jj}") // false
println("ii===jj ${ii === jj}") // false
println("ii!==jj ${ii !== jj}") // true
|
结果:
1
2
3
4
| ii==jj true
ii!=jj false
ii===jj false
ii!==jj true
|
- kotlin 中的 === 等同于 java 中 == ,比较的是引用;!== 类似
- kotlin 中的 == 等同于 java 的 ` equals `,比较的是值;!= 类似
is
判断类型
Java 中用 instanceOf,被 kotlin 中的 is 替代了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| fun testIs() {
var str = ""
if (str is String) {
}
var count = 6 % 3
var countType = when (count) {
0 -> count.toLong()
1 -> count.toDouble()
else -> count.toFloat()
}
var result = when (countType) {
is Long -> "Long了啊"
is Double -> "Double了啊"
else -> "Float了啊"
}
println(result)
}
|
in
判断值是否在数据和容器中
运算符重载
重载算术运算符
Java 中的算术运算符只能用于基本数据类型;kotlin 中可以重载运算符
重载二元算术运算
使用 operator
关键字来声明 plus 函数,重载 plus 函数后,就可以用 + 号来求和了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| fun test701() {
var point = Point(10, 44)
var plus = point.plus(Point(1, 2))
println(plus)
}
// 1、声明为成员函数
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
}
// 2、声明为一个扩展函数(给第三方库的类定义约定扩展函数的常用模式)
operator fun Point.plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
|
kotlin 不能定义自己的运算符,它限定了你能重载的运算符,以及你需要在你的类中定义的对应名字的函数,列举了你能定义的二元运算符:
表达式 | 函数名 |
---|
a * b | times |
a / b | div |
a % b | mod |
a + b | plus |
a - b | minus |
自定义的运算符的优先级和标准的优先级一样,*、/、%高级+和-
定义算术运算符,不要求两个运算数是相同的类型;返回类型也可以不同于任一运算数的类型
运算符函数可以被重载,可以定义多个同名的参数不同的方法
kotlin 中没有位运算符
位运算函数 | 描述 |
---|
shl | 带符号左移 |
shr | 带符号右移 |
ushr | 无符号右移 |
and | 按位与 |
or | 按位或 |
xor | 按位异或 |
inv | 按位取反 |
+
(plus)
Point 类使用 operator 关键词声明了 plus() 函数,并在其中定义了相加算法,这使得 Point 对象之间可以使用 + 来做加法运算,即原本的 p1.plus(p2) 可以简写成 p1+p2。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| fun main() {
val p1 = Point(1, 0)
val p2 = Point(2, 1)
println(p1 + p2) // Point(x=3, y=1)
println(p1.plus(p2)) // Point(x=3, y=1)
}
data class Point(
val x: Int,
val y: Int
) {
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| fun main() {
val p1 = Point(1, 0)
val p2 = Point(2, 1)
p1 + p2
println(p1)
println(p2)
p1.plus(p2)
println(p1)
println(p2)
// Point(x=3, y=1)
// Point(x=2, y=1)
// Point(x=5, y=2)
// Point(x=2, y=1)
}
data class Point(
var x: Int,
var y: Int
) {
operator fun plus(other: Point) {
x += other.x
y += other.y
}
}
|
*
(times)
1
2
3
4
5
6
7
| operator fun String.times(n: Int): String {
var temp = ""
for (index in 0 until n) {
temp += " $this"
}
return temp
}
|
使用:
1
2
| val result = "hacketzeng" * 3
println(result)
|
结果:
1
| hacketzeng hacketzeng hacketzeng
|
in
(contains)
in 关键字其实是 contains
操作符的简写,它不是一个接口,也不是一个类型,仅仅是一个操作符,也就是说任意一个类只要重写了 contains 操作符,都可以使用 in 关键字,如果我们想要在自定义类型中检查一个值是否在列表中,只需要重写 contains() 方法即可,Collections 集合也重写了 contains 操作符。
1
2
3
4
5
6
7
8
9
10
| // 使用扩展函数重写 contains 操作符
operator fun Regex.contains(text: CharSequence) : Boolean {
return this.containsMatchIn(text)
}
// 结合着 in 和 when 一起使用
when (input) {
in Regex("[0–9]") -> println("contains a number")
in Regex("[a-zA-Z]") -> println("contains a letter")
}
|
重载复合赋值运算符
复合运算符:+=
,-=
,*=
等
表达式 | 函数名 |
---|
a *= b | timesAssign |
a /= b | divAssign |
a %= b | modAssign |
a += b | plusAssign |
a -= b | minusAssign |
标准库为可变集合定义了 plusAssign 函数,
1
2
3
| operator fun <T> MutableCollection<T>.plusAssign(element: T) {
this.add(element)
}
|
plus 和 plusAssign 同时定义,解决:
- val 替代 var,这样 plusAssign 不会适用
- 不要同时给出 plus 和 plusAssign
- 类可变的,一般只需要提供 plusAssign 即可
重载一元运算符
可重载的一元运算符:
表达式 | 函数名 |
---|
+a | unaryPlus |
-a | unaryMinus |
!a | not |
a,a | inc |
–a,a– | dec |
1
2
3
4
5
6
7
| operator fun Point.unaryMinus(): Point {
return Point(-x, -y)
}
fun test702() {
var point = Point(7, 34)
println(-point)
}
|
重载比较运算符
- 比较运算符: == 、!= 、> 、< 等
- 等号运算符:equals Kotiln 中使用 == ,它将直接转换为 equals 方法的调用; != 也会调用 equals 方法,只是对结果取反
1
2
3
| a==b
等同于:转换为equals调用及null的校验
a?.equals(b) ?: (b==null)
|
- === 恒等运算符,来检查参数与调用 equals 的对象是否相同,与 Java 中的 == 运算符完全相同; === 不能被重载
equals 被标记为 override,它是在 Any 中被定义了;equals 函数不能实现为扩展函数,因为继承自 Any 类的实现始终优先于扩展函数
排序运算符:compareTo
compareTo 返回类型必须是 Int;比较运算符(<,>, <= , >= )的使用转换为 compareTo 调用
利用 kotlin 提供的 compareValuesBy()
函数来简洁地实现 compareTo 方法,按顺序调用,相同,调用下组,没有更多就返回 0;
java 中实现了 Comparable 接口的类,都可以直接使用简洁的运算符语法,不需要增加扩展函数。
- 重写 compareTo 方法,大于返回正数,=相等,小于返回负数
- 实现 Comparable 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| data class GiftComboInfo(
val giftId: Long,
val giftName: String,
val giftSendAmount: Int, // 单次送礼数量
var giftTotalCount: Int, // 总数量数量,3秒内连击
var msgOrderId: Long,// 用户送礼请求响应序列id,有序
val comboSeq: String // 用户送礼连击标识
) : Comparable<GiftComboInfo?> {
override fun compareTo(other: GiftComboInfo?): Int {
if (other == null) return 1
if (comboSeq != other.comboSeq) return 1 // comboSeq不等于都算大于
return msgOrderId.compareTo(other.msgOrderId)
}
}
|
测试
1
2
3
4
5
6
7
8
9
10
11
12
| fun main() {
val c1: GiftComboInfo? = null
val c1_0: GiftComboInfo = GiftComboInfo(1, "rocket", 777, 777, 10, "431kj")
val c1_1: GiftComboInfo = GiftComboInfo(1, "rocket", 1, 778, 11, "431kj")
val c2: GiftComboInfo = GiftComboInfo(2, "gift", 7, 7, 29, "45fsda")
val c3: GiftComboInfo = GiftComboInfo(2, "gift", 7, 7, 19, "45fsda")
println("c1_0>c1_1:${c1_0 > c1_1}") // false
println("c1_0>c1:${c1_0 > c1}") // true
println("c1_1>c1:${c1_1 > c1}") // true
println("c1_0>c2:${c1_0 > c2}") // true
println("c2>=c3:${c2 >= c3}") // true
}
|
集合与区间的约定
1)通过下标来访问元素:get、set(下标运算符 []
)
kotlin 中访问 map 可以通过 java 中访问数组一样来访问 map;同样也可以改变一个可变的 map 元素
1
2
| val value = map[key]
mutableMap[key] = newValue
|
使用下标运算符 ([]
),会被转换为 get 运算符方法的调用,写入元素将调用 set,Map 和 MutableMap 接口已经定义好了这些方法
定义一个名 get 函数,标记为 operator,get 参数可以是任意类型(当你对 map 使用下标运算符,参数类型是 key 的类型,它可以是任意类型),还可以定义具有多个参数的 get 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
| fun test703() {
val p = Point(10, 20)
println(p[0])
println(p[1])
println(p[2])
}
operator fun Point.get(index: Int): Int {
return when (index) {
0 -> x
1 -> y
else -> throw IndexOutOfBoundsException("Invalid index: $index")
}
}
|
2)in 约定
集合支持的另一运算符 in,用于检查某个对象是否属于集合,相对应的函数叫做 contains
;in 右边的对象将会调用 contains 函数,in 左边的对象将会作为参数入参
1
2
3
4
5
6
7
8
9
10
| fun test704() {
val rect = Rectangle(Point(10, 20), Point(50, 50))
println(Point(20, 30) in rect) // true
println(Point(5, 5) in rect) // false
}
data class Rectangle(val upperLeft: Point, val lowerRight: Point)
operator fun Rectangle.contains(p: Point): Boolean {
return p.x in upperLeft.x until lowerRight.x // until来构建一个开区间
&& p.y in upperLeft.y until lowerRight.y
}
|
3)rangeTo 的约定
创建区间用 ..
,..
运算符是调用 rangeTo 函数的一个简洁方法;rangeTo 函数返回一个区间;如果该类实现了 Comparable 接口,那么不需要定义了,可以调用 kotlin 标准库创建一个任意可比较元素的区间,这个库定义了可以用于任何可比较元素的 rangeTo 函数
rangeTo 运算符优先级低于算术运算符
4)for 循环使用 iterator 约定
for 也可以用 in;
for(x in list) { }
将被转换为 list.iterator() 的调用;在 kotlin 中这是一种约定,iterator() 方法可以被定义为扩展函数
kotlin 中的数据类型
基本数据类型
kotlin 不区分基本类型和引用类型;大多数情况下,基本类型会被编译成 Java 中的基本类型(如 Int 编译成 Java 中的 int); 而泛型例外,用作泛型类型参数的基本数据类型会被编译成对应的 java 包装类型
- kotlin 对应到 Java 基本数据类型
整数类型:Byte、Short、Int、Long
浮点数类型:Float、Double
字符类型:Char
布尔类型:Boolean - 可空的基本数据类型
kotlin 中的可空类型不能用 java 的基本数据类型表示,null 只能被存储在 java 的引用类型的变量中(只要用了基本数据类型的可空版本,它就会编译成对应的包装类型) - 数字转换
kotlin 不会自动把数字从一种类型转换成另外一种,即便是转换成更大的类型
1
2
3
| val i= 1
val l:Long = i // 编译不过,需要显式地转换
var l:Long = i.toLong()
|
每一种基本数据类型(Boolean 除外)都定义有转换函数,支持双向转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| fun test() {
var i: Int = 20
var toFloat = i.toFloat()
var toString = i.toString()
var toLong = i.toLong()
var toDouble = i.toDouble()
var toChar = i.toChar()
println("toFloat:$toFloat toString:$toString toLong:$toLong toDouble:$toDouble toChar:$toChar")
var s:String = "65"
var toInt = s.toInt()
var toLong1 = s.toLong()
var toFloat1 = s.toFloat()
var toDouble1 = s.toDouble()
var toCharArray = s.toCharArray()
println("toFloat:$toFloat1 toInt:$toInt toLong:$toLong1 toDouble:$toDouble1 toCharArray:${toCharArray.forEach { println(it.toString()) }}")
}
|
其他类型
Any
和 Any?
Any 相当于 Java 中的 Object 类,Java 中 Object 是所有引用类型的超类,而基本数据类型并不是;而 kotlin 中 Any 是所有类型的超类型,包括 Int 这样的基本数据类型
把基本数据类型赋值给 Any 类型的变量会自动装箱- Unit 类型 (
void
)
kotlin 中 Unit 类型完成了 java 中的 void 一样的功能,当函数没有什么可以返回的,可以用 Unit 来作为返回值。
Java 中的 void 和 Unit 的区别:
Unit 是一个完备的类型,可以作为类型参数,而 void 却不行;只存在一个值是 Unit 类型,这个值也叫做 Unit,并且在函数中会被隐式地返回
1
2
3
4
5
6
7
8
| interface Processor<T> {
fun process(): T
}
class NoResultProcessor : Processor<Unit> {
override fun process() { // 返回Unit,可以省略类型说明
// do stuff //这里不需要显式地return
}
}
|
Nothing
类型:这个函数永不返回
当做函数返回值使用,或者当做泛型函数返回值的类型参数使用才会有意义
集合
集合变量自己类型的可空性
集合中的变量类型的可空性
- 只读集合与可变集合
kotlin 中的把访问集合的数据接口和修改集合数据的接口分开了
访问集合接口:Collection
修改集合接口:MutableCollection
只读集合并不是一定是不可变的,只读集合并不总是线程安全的
如:同一个集合对象,有两个不同的引用,一个只读,另一个可变,可变的集合引用可以修改
- Kotlin 集合和 Java
Java 中的集合接口在 kotlin 中都有两种版本,只读的,可变的
平台类型
kotlin 和 java 混编时,要做可空性判断
数组
kotlin 中的一个数组是一个带有类型参数的类,其元素类型被指定为相应的类型参数,创建数组
- arrayOf 函数创建一个数组,它包含的元素是指定为该函数的实参
- arrayOfNulls 创建一个给定大小的数组,包含的是 null 元素(它只能用来创建包含元素类型可空的数组)
- Array 构造方法接收数组的大小和一个 lambda 表达式,调用 lambda 表达式来创建每一个数组元素
1
2
| var array = Array<String>(26, { i -> ('a' + i).toString() })
println(array.joinToString("-"))
|
1
2
3
| var strings = listOf("a", "b", "c")
var toTypedArray = strings.toTypedArray()
println("%s/%s/%s".format(*toTypedArray)) // 期望vararg参数时,使用展开运算符*传递数组
|
数组类型的类型参数始终会变成对象类型,你声明的 Array<Int>
,将会是一个包含了包装类的数组,对应的 java 类型是 Integer[]
。
创建基本数据类型数组,用 IntArray
,ByteArray
,CharArray
,BooleanArray
等,有三种方式创建基本数据类型数组:
- 类型构造方法接受 size 作为默认大小的数组大小
- 工厂函数
- 构造方法,接收 lambda 表达式
1
2
3
4
5
6
7
8
9
10
| var squares = IntArray(5) { i -> (i + 1) * (i + 1) }
// 数组遍历方式1:下标
for (i in squares.indices) { // 下标遍历
var value = squares[i]
println("Argument $i is :$value")
}
// 数组遍历方式2:forEachIndexed
squares.forEachIndexed { index, element ->
println("Argument forEachIndexed $index is :$element")
}
|
Kotlin 中的属性和字段
普通属性
普通属性声明
在 Kotlin 中,声明一个属性涉及到 2 个关键字,var 和 val 。
- var 声明一个可变属性
- val 声明一个只读属性
getters 与 setters 读写访问器
一个属性的完整声明:
1
2
3
4
| var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
// = 和 {}怎么用看一行是否能返回值
|
其 初始器(initializer)
、getter
和 setter
都是可选的。属性类型如果可以从初始器 (或者从其 getter 返回值,如下文所示)中推断出来,也可以省略
1
2
3
4
5
6
7
8
9
10
11
12
13
| class User {
var initialized = 1 // 类型 Int、默认 getter 和 setter
// 自定义getter和setter
var name: String? = null
get() {
return if (field.isNullOrBlank()) "百姓头条用户" else field
}
set(value) {
field = value?.toUpperCase()
}
val isEmpty: Boolean
get() = this.name?.isNullOrBlank() ?: true
}
|
只读属性
改变一个访问器的可见性,但是不需要改变默认的实现, 你可以定义访问器而不定义其实现。
setter 前添加 private
, 那么这个 setter 就是私有的:
1
2
3
4
| class PPPPP {
var setterVisibility: String = "abc"
private set // 此 setter 是私有的并且有默认实现
}
|
属性加注解
对一个访问器注解,但是不需要改变默认的实现, 你可以定义访问器而不定义其实现。
1
2
3
4
5
6
| class PPP {
var setterWithAnnotation: Any? = null
@Inject set // 用 Inject 注解此 setter
annotation class Inject
}
|
backing field (幕后字段)
什么是 backing field
在 Kotlin 类中不能直接声明字段。然而,当一个属性需要一个幕后字段时,Kotlin 会自动提供。这个幕后字段可以使用 field
标识符在访问器中引用
1
2
3
4
| var counter = 0 // 注意:这个初始器直接为幕后字段赋值
set(value) {
if (value >= 0) field = value
}
|
**field**
标识符只能用在属性的读或写访问器内,即 getter 或 setter。
Kotlin 的属性需要满足下面条件之一才有幕后字段:
- 使用默认 getter / setter 的属性,一定有幕后字段。对于 var 属性来说,只要
getter / setter 中有一个使用默认实现
,就会生成幕后字段; - 在自定义 ` getter/setter中使用了field的属性 `
举一个没有幕后字段的例子:
1
2
3
4
5
6
7
8
9
| class NoField {
var size = 0
// isEmpty没有幕后字段
var isEmpty
get() = size == 0
set(value) {
size *= 2
}
}
|
生成的 Java 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| public final class NoField {
private int size;
public final int getSize() {
return this.size;
}
public final void setSize(int var1) {
this.size = var1;
}
public final boolean isEmpty() {
return this.size == 0;
}
public final void setEmpty(boolean value) {
this.size *= 2;
}
}
|
isEmpty 是没有幕后字段的,重写了 setter 和 getter,没有在其中使用 field;翻译成 Java 代码,只有一个 size 变量,isEmpty 翻译成了 isEmpty() 和 setEmpty() 两个方法。返回值取决于 size 的值。
有幕后字段的属性转换成 Java 代码一定有一个对应的 Java 变量
有幕后属性生成的 Java 代码:
1
2
3
4
5
6
| class Person {
var name: String = "Paul"
set(value) {
println("执行了写访问器,参数为:$value")
}
}
|
Person 生成的 Java 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public final class Person {
@NotNull
private String name = "Paul";
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String value) {
Intrinsics.checkNotNullParameter(value, "value");
String var2 = "执行了写访问器,参数为:" + value;
System.out.println(var2);
}
}
|
扩展属性初始化(没有 backing field)
扩展属性是没有 backing field 的,它其实就是扩展函数的特殊形式。但是总有人想要给某个类扩展一个真正的 field,就像下面那样:
1
2
3
4
5
6
7
8
| class Some {}
// unresolved reference: field
var Some.rua : String
get() = field
set(value) {
field = value
}
|
解决:找个地方将数据存起来,这里选择了一个全局的 Map 来解决存储问题
1
2
3
4
5
6
| private var refMap: WeakHashMap<Some, String> = WeakHashMap()
var Some.rua: String
get() = refMap[this] ?: ""
set(value) {
refMap[this] = value
}
|
需要注意的是,这里对 Map 的类型有要求:因为不能干扰 jvm 的垃圾回收机制,所以需要是 WeakReferenceMap
;因为每个实例都需要保存一份自己的数据,所以需要是 IdentityMap
;如果在多线程环境跑这代码,需要是 ConcurrentMap。综上你需要一个 ConcurrentWeakIdentityMap
来解决这个问题。
backing property(幕后属性)
默认 getter 和 setter 访问私有属性会被优化,所以不会引入函数调用开销。
有时候有这种需求,我们希望一个属性:对外表现为只读,对内表现为可读可写,我们将这个属性成为幕后属性。 如:
1
2
3
4
5
6
7
8
| private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // 类型参数已推断出
}
return _table ?: throw AssertionError("Set to null by another thread")
}
|
将 _table
属性声明为 private,因此外部是不能访问的,内部可以访问,外部访问通过 table 属性,而 table 属性的值取决于 _table
,这里 _table
就是幕后属性。
幕后属性这中设计在 Kotlin 的的集合 Collection 中用得非常多,Collection 中有个 size 字段,size 对外是只读的,size 的值的改变根据集合的元素的变换而改变,这是在集合内部进行的,这用幕后属性来实现非常方便。
如 Kotlin AbstractList 中 SubList 源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // AbstractList
private class SubList<out E>(private val list: AbstractList<E>, private val fromIndex: Int, toIndex: Int) : AbstractList<E>(), RandomAccess {
private var _size: Int = 0
init {
checkRangeIndexes(fromIndex, toIndex, list.size)
this._size = toIndex - fromIndex
}
override fun get(index: Int): E {
checkElementIndex(index, _size)
return list[fromIndex + index]
}
override val size: Int get() = _size
}
|
AbstractMap 源码中的 keys 和 values 也用到了幕后属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| public abstract class AbstractMap<K, out V> protected constructor() : Map<K, V> {
override val keys: Set<K>
get() {
if (_keys == null) {
_keys = object : AbstractSet<K>() {
override operator fun contains(element: K): Boolean = containsKey(element)
override operator fun iterator(): Iterator<K> {
val entryIterator = entries.iterator()
return object : Iterator<K> {
override fun hasNext(): Boolean = entryIterator.hasNext()
override fun next(): K = entryIterator.next().key
}
}
override val size: Int get() = this@AbstractMap.size
}
}
return _keys!!
}
@kotlin.jvm.Volatile
private var _keys: Set<K>? = null
override val values: Collection<V>
get() {
if (_values == null) {
_values = object : AbstractCollection<V>() {
override operator fun contains(element: @UnsafeVariance V): Boolean = containsValue(element)
override operator fun iterator(): Iterator<V> {
val entryIterator = entries.iterator()
return object : Iterator<V> {
override fun hasNext(): Boolean = entryIterator.hasNext()
override fun next(): V = entryIterator.next().value
}
}
override val size: Int get() = this@AbstractMap.size
}
}
return _values!!
}
@kotlin.jvm.Volatile
private var _values: Collection<V>? = null
}
|
编译期常量
已知值的属性可以使用 const
修饰符标记为编译期常量。 这些属性需要满足以下要求:
- 位于顶层或者是 object 的一个成员
- 用 String 或原生类型 值初始化
- 没有自定义 getter
这些属性可以用在注解中:
1
2
| const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { …… }
|
延迟初始化属性与变量
一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性可以通过依赖注入来初始化, 或者在单元测试的 setup 方法中初始化。 这种情况下,你不能在构造函数内提供一个非空初始器。 但你仍然想在类体中引用该属性时避免空检查。
为处理这种情况,你可以用 lateinit
修饰符标记该属性:
1
2
3
4
5
6
7
8
9
10
11
| public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接解引用
}
}
|
该修饰符只能用于在类体中的属性(不是在主构造函数中声明的 var 属性,并且仅当该属性没有自定义 getter 或 setter 时),而自 Kotlin 1.2 起,也用于顶层属性与局部变量。该属性或变量必须为非空类型,并且不能是原生类型。
在初始化前访问一个 lateinit 属性会抛出一个特定异常,该异常明确标识该属性被访问及它没有初始化的事实。
检测一个 lateinit var 是否已初始化(自 1.2 起)
1
2
3
| if (foo::bar.isInitialized) {
println(foo.bar)
}
|
此检测仅对可词法级访问的属性可用,即声明位于同一个类型内、位于其中一个外围类型中或者位于相同文件的顶层的属性。
覆盖属性
委托属性
Kotlin null 值安全
null 引用 ?
- 非 null 引用,kotlin 默认
- null 引用,类型后面加上?即可
1
2
| var a:String? = null
var b:String = "abc"
|
安全调用符 ?
用 ?.
表示
1
2
3
4
5
| fun testNull1() {
var b: String? = "abc"
b = null
println(b?.length)
}
|
可以结合 let
对非 null 值执行某个操作
Elvis 操作符 ?
用 ?:
表示,如果 ?:
左侧的表达式值不为 null,Elvis 操作符就会返回它的值,否则返回右侧表达式的值。(只有在左侧表达式值为 null 时,才会计算右侧表达式)
1
2
3
4
5
6
| fun testNullElvis() {
var b: String? = null
val len: Int = if (b != null) b.length else 0
println(len)
println(b?.length ?: 0)
}
|
- throw 和 return 结合
Kotlin 中,throw
和 return
都是表达式,也可以出现在 Elvis 操作符的右侧。
1
2
| val name = b ?: throw IllegalArgumentException("b不能为null")
val name2 = b ?: return null
|
1
2
3
4
5
6
| fun isNotchScreen(fragment: String?): Boolean {
return fragment?.let {
println("test $fragment")
true
} ?: false
}
|
1
2
3
4
5
| fun isVivoRom(): Boolean {
val a = getSystemProperty("ro.vivo.os.name")
return !a.isNullOrBlank()
&& a?.contains("funtouch", true)!!
}
|
另外一种:
1
| get?.invoke(null) as Boolean
|
!!操作符
用 !!
表示,左侧为空时,直接抛出 NPE 异常。
1
2
| var b:String? = null
b!!.length
|
抛出的 NPE
1
| Exception in thread "main" kotlin.KotlinNullPointerException
|
安全的类型转换 as?
用 as? 具体类型
,如果左侧为 null 或者不是右边的类型,那么返回 null
1
2
3
4
5
6
7
8
| fun testAs(a: Any?) {
val value: Int? = a as? Int
println(value)
}
// 调用
testAs("11") // null
testAs(null) // null
testAs(1) // 1
|
可为 null 的类型构成的集合
用 filterNotNull()
函数类过滤集合中的 null 元素
1
2
3
4
5
6
| fun testListNUll() {
var nullableList = listOf(1, 2, null, 4)
println(nullableList) // [1, 2, null, 4]
var filterNotNull = nullableList.filterNotNull()
println(filterNotNull) // [1, 2, 4]
}
|
Kotlin 是如何实现空安全的
1
2
3
4
5
6
7
| class Test {
fun test_1(str: String) = str.length // 1
fun test_2(str: String?) = str?.length // 2
fun test_3(str: String?) = str!!.length // 3
fun test_4(str: Any?) = str as String // 4
fun test_5(str: Any?) = str as? Int // 5
}
|
最终编译成 java 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| public final class Test {
public final int test_1(@NotNull String str) {
Intrinsics.checkParameterIsNotNull(str, "str");
return str.length();
}
@Nullable
public final Integer test_2(@Nullable String str) {
return str != null ? str.length() : null;
}
public final int test_3(@Nullable String str) {
if (str == null) {
Intrinsics.throwNpe();
}
return str.length();
}
@NotNull
public final String test_4(@Nullable Object str) {
if (str == null) {
throw new TypeCastException("null cannot be cast to non-null type kotlin.String");
} else {
return (String)str;
}
}
@Nullable
public final Integer test_5(@Nullable Object str) {
Object var10000 = str;
if (!(str instanceof Integer)) {
var10000 = null;
}
return (Integer)var10000;
}
}
|
- test_1 函数
如果 str 为 null,就会触发 Intrinsics.checkParameterIsNotNull(str, "str");
,抛出异常 IllegalArgumentException
参数为空的异常 - test_2 函数
比较简单,判断是否为空,不为空的话,调用对应的方法,否则返回一个 null。 - test_3
直接判断是否为空,空的话直接抛出 Npe 异常 - test_4
判空,如果空抛出类型转换异常,否则执行后续代码;如果类型不对,转换会报 ClassCastException
异常的 - test_5
创建一个新对象把参数的值传给他,然后进行类型判断,如果不是特定类型,对象赋值 null,然后把执行类型强转后的对象赋值给一个新的对象。(类型不对的话,转换返回 null)
Kotlin 中对空安全处理
- 非空类型的属性编译器添加
@NotNull
注解,可空类型添加 @Nullable
注解; - 非空类型直接对参数进行判空,如果为空直接抛出异常;
- 可空类型,如果是
?.
判空,不空才执行后续代码,否则返回 null;如果是 !!
,空的话直接抛出 NPE 异常。 as
操作符会判空,空的话直接抛出异常,不为空才执行后续操作,没做类型判断!运行时可能会报错 ClassCastException
!as?
则是新建一个变量,参数赋值给它,然后判断是否为特定类型,如果不是要转换的类型那么赋值为 null,如果是的话直接强制转换(as?处理后的参数可能为空)。
小结
- ?类型后?表示该变量可以为 null 类型
- ?. 左侧变量为空时直接返回 null
- ?: 左侧变量为空时返回右边的值
- !! 左侧变量为空时,抛异常
- filterNotNull() 过滤集合中的 null 元素
- null 不等于 false,也不等于 true
1
2
| println(null == false) // false
println(null == true) // false
|
Kotlin 类型检查和类型转换
is 和!is 操作符
is
操作符,在运行时检查一个对象与一个给定的类型是否一致;!is
相反。
- is 表达式满足条件,Kotlin 编译器会自动转换 is 前面的对象到后面的数据类型。
- 对象和 is 后面的类型要兼容,如果不兼容,无法编译通过。
1
2
3
4
5
6
7
8
9
10
| fun testType1() {
var obj:Any = 456
if (obj is String) {
println("""字符串:$obj""")
}
if (obj is Int) {
println("""Int:$obj""")
var toString = obj.toString() // 自动转换为Int
}
}
|
智能类型转换
很多情况下,Kotlin 不必使用显式的类型转换操作,编译器会对不可变的值追踪 is
检查,然后在需要的时候自动插入安全的类型转换。
1
2
3
4
5
| fun demo(x: Any) {
if (x is String) {
println(x.length) // x自动转换为String类型
}
}
|
- is 相反的类型检查导致了 return,编译器能够判断出转换处理是安全的
1
2
3
4
5
| fun demo2(x:Any) {
if (x !is String)
return
print(x.length) // x自动转换为String类型
}
|
- 在
&&
和 ||
操作符的右侧也是一样
在 !is
的 ||
右侧;在 is
和 &&
的右侧
1
2
3
4
5
6
7
8
| fun demo3(x:Any) {
if (x !is String || x.length ==0) { // ||右侧x被自动转换为String
return
}
if (x is String && x.length > 0) { // &&右侧x被自动转换为String
println(x.length)
}
}
|
这种类型转换(smart cast)对于 when
表达式,while
循环同样有效。
强制类型转换(as 和 as?)
as(转换失败抛出异常)
在 Kotlin 中,不安全的类型转换使用中缀操作符 as
。
null
不能被转换为 String,因为 String 表示不可为 null 的。需要在后面加上 String?
。如果转换是不可能的,转换操作符会抛出一个异常。因此,我们称之为不安全的。
1
2
| val y:Any? = null
val x:Int? = y as Int?
|
as?(转换失败不抛出异常,返回 null)
为了避免抛出异常,我们使用安全的类型转换操作符 as?
,在类型转换失败时,返回 null。
1
2
3
| val y:Any? = "abcd"
val x:Int? = y as? Int? // 转换错误,不抛出异常,x为null
print(x) // null
|