Kotlin类型系统笔记
Kotlin语言基础笔记
Kotlin流程控制语句笔记
Kotlin操作符重载与中缀表示法笔记
Kotlin扩展函数和扩展属性笔记
Kotlin空指针安全(null-safety)笔记
Kotlin类型系统笔记
Kotlin面向对象编程笔记
Kotlin委托(Delegation)笔记
Kotlin泛型型笔记
Kotlin函数式编程笔记
Kotlin与Java互操作笔记
Kotlin协程笔记
1. 根类型Any
Kotlin中所有的类都有一个共同的基类Any,如果类没有申明继承其他类的话,默认继承的就是Any。我们测试一段代码:
fun main(args: Array<String>) {
val any = Any()
println(any) //打印java.lang.Object@49476842
println(any::class) //打印class kotlin.Any
println(any::class.java) //打印class java.lang.Object
}
上面输出可以看到其实Kotlin中的Any就是对应Java中的java.lang.Object类型。在Java 中Object类是所有引用类型的父类,但是不包括那些基本类型,int,long,float等。而在Kotlin中所有的类型都是引用类型,统一继承父类Any。
注意,如果你发现
println(any::class)
打印出这么一段Kotlin reflection is not available
,请在你的gradle.gradle文件中加入下面这段依赖compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
。
Any的源码如下:
它只有三个方法,equals,hashCode和toString。这三个方法跟Java中的意思是一样的,唯一不同的是,Any的equals在Kotlin中进行了操作符重载,所以大家可以使用
==
来进行值相等的比较。
2. 基本类型
在Java中有byte、int、short、long、float、double、char、boolean这些基本类型,另外,void也可以算是一种基本类型,它也有一个装箱类Void
(Koltin中也有类似的概念,Unit、Nothing),Void不能new出来。
在Kotlin中是真正的一切皆是对象。所有的类型都是引用类型。Kotlin中的基本类型的类图如下:
2.1 数字(Number)类型
Kotlin 提供了如下的内置类型来表示数字(与 Java 很相近):
类型 | 宽度(Bit) |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
从上面的类结构图,我们可以看到这些内置的数字类型,都继承了Number和Comparable类。Kotlin的数字类型跟Java的基本相同,但是要注意的是Kotlin中数字没有隐私转化,也就是说Kotlin的Int不能自动转换成Long。另外:Kotlin中字符Char
不是数字。Kotlin的这些内部数字类型在运行时会自动转换成Java的基本类型。
数值常量字面值有以下几种
- 十进制:
123
- Long类型:
123L
- 十六进制:
0x0F
- 二进制:
0b000011
- Double类型:
123.123
、123.123e10
- Float类型:
123.123f
、123.123F
- 我们也可以使用下划线来分隔数字:
1_000_000
、1234_5678_9012_3456L
、0xFF_EC_DE_5E
、0b11010010_01101001_10010100_10010010
显示转换
范围较小的类型需要显示转换成较大的类型。
image.png
image.png
你需要显示转换类型,如下代码:
fun main(args: Array<String>) {
val a: Int? = 1
val g: Long? = a?.toLong()
val b: Byte = 1
val i: Int = b.toInt()
}
每个数字类型都继承Number类,而Number中定义了一些方法用来方便的做显示转换。
Number
操作符重载
有时候缺少隐式转换也没关系,例如Long类对+
进行了重载。
public operator fun plus(other: Byte): Long
public operator fun plus(other: Short): Long
public operator fun plus(other: Int): Long
public operator fun plus(other: Long): Long
public operator fun plus(other: Float): Float
public operator fun plus(other: Double): Double
2.2 Char字符类型
字符用Char
表示。他们不能直接用来当作数字。
特殊字符可以用反斜杠转义,当然也支持Unicode转义序列语法,例如:
\uFF00
。
2.3 String 字符串类型
索引运算符
fun main(args: Array<String>) {
val s: String = "abc"
println(s[2]) //打印c
}
查看源码可以知道,s[i]
会被翻译成java.lang.String.charAt()
。
循环迭代字符串
fun main(args: Array<String>) {
val s: String = "abc"
for (c in s) {
println(c)
}
}
操作符+
重载
字符串String类型重载了+
操作符,作用对象是可以任意对象,包括空引用:
>>> "abc".plus(true)
abctrue
>>> "abc"+false
abcfalse
>>> "abc"+1
abc1
>>> "abc"+1.20
abc1.2
>>> "abc"+100L
abc100
>>> "abc"+"cdef"
abccdef
>>> "abc"+null
abcnull
>>> "abc"+'z'
abcz
>>> "abc"+arrayOf(1,2,3,4,5)
abc[Ljava.lang.Integer;@3d6f0054
字符串的值
使用两个双引号定义,其中可以包含一些转义字符。
val s: String = "Hello world, \n\n\n"
还可以使用三个双引号("""
)括起来,里面可以包含任意字符。
val s: String = """
for (c in "abc")
print(c)
"""
另外:在kotlin.text包下面的Indent.kt中定义了String类的扩展函数。我们可以使用trimMargin()
和trimIndent()
来去除空格。
trimMargin()默认使用"|"来作为边界字符。
fun main(args: Array<String>) {
val text = """
|理论是你知道是这样,但它却不好用。
|实践是它很好用,但你不知道是为什么。
|程序员将理论和实践结合到一起:
|既不好用,也不知道是为什么。
"""
println(text.trimMargin())
}
输出
理论是你知道是这样,但它却不好用。
实践是它很好用,但你不知道是为什么。
程序员将理论和实践结合到一起:
既不好用,也不知道是为什么。
trimIndent()则是把字符串行的左边空白对其切割:
fun main(args: Array<String>) {
val text = """
hello
world!
"""
println(text.trimIndent())
}
输出
hello
world!
字符串模版
字符串中可以包含一些表达式,如下:
fun main(args: Array<String>) {
val name = "Denny"
val price = 100.5
println("My name is $name") //打印My name is Denny
println("$name.length is ${name.length}") //打印Denny.length is 5
println("""the price is $$price""") //打印the price is $100.5
}
2.4 Array 数组类型
class Array<T> private constructor() {
val size: Int
operator fun get(index: Int): T
operator fun set(index: Int, value: T): Unit
operator fun iterator(): Iterator<T>
// ……
}
我们可以看到Array重载了[]
操作符。
我们可以使用arrayOf()
来创建一个数组,并给定数组中的初始值。
fun main(args: Array<String>) {
val a1 = arrayOf(1, 2, 3)
println(a1::class) //打印class kotlin.Array
println(a1::class.java) //打印class [Ljava.lang.Integer;
val a2 = arrayOf(1, "s", null)
println(a2::class) //打印class kotlin.Array
println(a2::class.java) //打印class [Ljava.lang.Object;
}
我们可以使用arrayOfNulls()
来创建一个指定大小,元素都为null的数组,在创建的时候我们必须指定其中元素的类型,要不然会报错。
另外:Array类还有一个构造函数。
public inline constructor(size: Int, init: (Int) -> T)
第一个参数是数组的大小,第二个参数是初始化函数类型的参数。比如:
fun main(args: Array<String>) {
val a = Array(8, { i -> i + i })
println(a.joinToString(prefix = "[", postfix = "]")) //打印[0, 2, 4, 6, 8, 10, 12, 14]
}
Kotlin中也有无装箱开销的用来表示原生类型的数组。这些原生数组类型如下:
- BooleanArray
- ByteArray
- CharArray
- ShortArray
- IntArray
- LongArray
- FloatArray
- DoubleArray
- BooleanArray
这些类和Array没有继承关系,但是他们有同样的函数和属性,他们也有相应的工厂方法:
/**
* Returns an array containing the specified [Double] numbers.
*/
public fun doubleArrayOf(vararg elements: Double): DoubleArray
/**
* Returns an array containing the specified [Float] numbers.
*/
public fun floatArrayOf(vararg elements: Float): FloatArray
/**
* Returns an array containing the specified [Long] numbers.
*/
public fun longArrayOf(vararg elements: Long): LongArray
/**
* Returns an array containing the specified [Int] numbers.
*/
public fun intArrayOf(vararg elements: Int): IntArray
/**
* Returns an array containing the specified characters.
*/
public fun charArrayOf(vararg elements: Char): CharArray
/**
* Returns an array containing the specified [Short] numbers.
*/
public fun shortArrayOf(vararg elements: Short): ShortArray
/**
* Returns an array containing the specified [Byte] numbers.
*/
public fun byteArrayOf(vararg elements: Byte): ByteArray
/**
* Returns an array containing the specified boolean values.
*/
public fun booleanArrayOf(vararg elements: Boolean): BooleanArray
3. 可空类型(Nullable Types)
Kotlin把可空性作为类型系统的一部分,这样编译器可以在编译过程中发现一些可能的错误,减少运行过程中抛出异常的可能性。
Kotlin的类型系统和Java相比,主要的区别就是Kotlin对可空类型的显示支持。
3.1 Kotlin中的null
fun main(args: Array<String>) {
println(null == null) //打印true
println(null != null) //打印false
println(null is Any) //打印false
println(null is Any?) //打印true
}
与Java不同,Kotlin中null与null是相等的,null不是Any类型,但是是Any?类型。下面我们来看看Null到底是什么类型。
编译器告诉我们null的类型是
Nothing?
。
3.2 可空性的实现原理
假如我们有这样一个StringUtil.kt类。
package com.dengyin2000.kotlintest1
object StringUtil{
fun testNullable1(x: String, y: String?): Int {
return x.length
}
fun testNullable2(x: String, y: String?): Int? {
return y?.length
}
fun testNullable3(x: String, y: String?): Int? {
return y!!.length
}
}
然后我们来看看上面的代码对应生成的Java代码是怎样的。在Intellij IDEA中点击"Tools" -> "Kotlin" -> "Show Kotlin Bytecode"。
bytecode
再在出来的界面上点击"Decompile"。
decompile
最后就能看到最终生成的Java代码,代码如下:
finalcode
我们可以看到生成的Java代码,不可为空变量都注解了@NotNull
,而可为空变量注解了@Nullable
。
在函数调用前都用kotlin.jvm.internal.Intrinsics. checkParameterIsNotNull
方法检查了不为空变量是否为空。
public static void checkParameterIsNotNull(Object value, String paramName) {
if (value == null) {
throwParameterIsNullException(paramName);
}
}
而可空变量的安全调用符y?.length
转成Java的代码如下:
y != null?Integer.valueOf(y.length()):null
可空变量的断言调用y!!.length
转成的Java代码如下:
if (y == null) {
Intrinsics.throwNpe();
}
return y.length();
3.3 可空类型层次体系
Any
是非空类型的根,Any?
是可空类型的根,由于Any?
是Any的根,所以Any?
是Kotlin的类型层次结构的最顶端。
fun main(args: Array<String>) {
println(1 is Any) //打印true
println(1 is Any?) //打印true
println(null is Any) //打印false
println(null is Any?) //打印true
println(Any() is Any?) //打印true
}
4. kotlin.Unit类型
Kotlin中的Unit
类实现了跟Java中void
一样的功能,大多数情况下,我们并不需要显示的返回Unit
,或者申明一个函数的返回类型为Unit
。编译器会推断它。
fun sayHello1() {
}
fun sayHello2() {
return Unit
}
fun sayHello3(): Unit {
}
fun main(args: Array<String>) {
println(sayHello1()) //打印kotlin.Unit
println(sayHello2()) //打印kotlin.Unit
println(sayHello3()) //打印kotlin.Unit
}
这三个函数其实是一样的。总之这个Unit
并没有什么特别之处,看看它的源码:
package kotlin
/**
* The type with only one value: the Unit object. This type corresponds to the `void` type in Java.
*/
public object Unit {
override fun toString() = "kotlin.Unit"
}
跟其他的类型一样, Unit
的父类是Any
,Unit?
的父类是Any?
。
5. kotlin.Nothing类型
Kotlin中没有类似Java中的返回值为void
的标记,在Java中,返回void
方法,其返回值是无法被访问到的。void
不是变量的类型,但是在Java的包装类中Void
是void
的包装类,如果你想让一个方法返回类型永远是null的话,可以写成如下:
public Void voidDemo() {
System.out.println("Hello,Void");
return null;
}
这个Void
就是Kotlin中的Nothing?
。它的唯一可被访问到的值也是null。
注意:Unit
和Nothing
的区别:Unit类型表达式计算结果返回的是Unit类型,Nothing类型表示永远不会返回结果(跟Java的void相同)。
throw关键字中断表达式的计算,并抛出堆栈的功能。所以,一个throw Exception
的代码就是返回Nothing
的表达式。
fun formatCell(value: Double): String =
if (value.isNaN())
throw IllegalArgumentException("$value is not a number") // Nothing
else
value.toString()
再比如,Kotlin的标准库的exitProcess
函数:
@file:kotlin.jvm.JvmName("ProcessKt")
@file:kotlin.jvm.JvmVersion
package kotlin.system
/**
* Terminates the currently running Java Virtual Machine. The
* argument serves as a status code; by convention, a nonzero status
* code indicates abnormal termination.
*
* This method never returns normally.
*/
@kotlin.internal.InlineOnly
public inline fun exitProcess(status: Int): Nothing {
System.exit(status)
throw RuntimeException("System.exit returned normally, while it was supposed to halt JVM.")
}
Nothing?可以只包含一个值:null。
Nothing
6. 类型检测与类型转换
6.1 is运算符
Kotlin的is运算符跟Java中的instanceof是一样的,用来检测某个对象是否某个类型或者父类的实例。其实我们之前已经有用到is运算符了
。
当你使用is运算符时,类型会自动转换,例如:
open class Animal{
open fun eat(){
println("animal eat")
}
}
open class Dog : Animal() {
fun run() {
println("run")
}
}
class Bird : Animal() {
fun fly() {
println("fly")
}
}
class Fish : Animal() {
fun swin() {
println("swin")
}
}
fun playAnimal(animal: Animal) {
when (animal) {
is Dog -> animal.run() //自动类型转换
is Bird -> animal.fly() //自动类型转换
is Fish -> animal.swin() //自动类型转换
}
}
如果是在Java中,我们需要用instanceof判断后然后再显示类型转换(Cast)。
6.2 as运算符
as
运算符用于进行显示类型转换,如果转换的类型与指定的类型兼容,转换就能成功,如果类型不兼容,使用as?
就会返回null。
package com.dengyin2000.kotlintest1
open class Animal{
open fun eat(){
println("animal eat")
}
}
open class Dog : Animal() {
fun run() {
println("run")
}
}
class Bird : Animal() {
fun fly() {
println("fly")
}
}
class Fish : Animal() {
fun swin() {
println("swin")
}
}
fun main(args: Array<String>) {
val animal = Animal()
val dog1 = animal as? Dog
println(dog1) //打印null
val dog = animal as Dog //Exception in thread "main" java.lang.ClassCastException: com.dengyin2000.kotlintest1.Animal cannot be cast to com.dengyin2000.kotlintest1.Dog
}