Kotlin开发Android开发Android开发经验谈

Kotlin类型系统笔记

2018-02-23  本文已影响767人  dengyin2000

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的源码如下:

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的基本类型。

数值常量字面值有以下几种
显示转换

范围较小的类型需要显示转换成较大的类型。


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表示。他们不能直接用来当作数字。

image.png
特殊字符可以用反斜杠转义,当然也支持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的数组,在创建的时候我们必须指定其中元素的类型,要不然会报错。

error
另外: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中也有无装箱开销的用来表示原生类型的数组。这些原生数组类型如下:

/**
 * 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
编译器告诉我们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的父类是AnyUnit?的父类是Any?

Unit

5. kotlin.Nothing类型

Kotlin中没有类似Java中的返回值为void的标记,在Java中,返回void方法,其返回值是无法被访问到的。void不是变量的类型,但是在Java的包装类中Voidvoid的包装类,如果你想让一个方法返回类型永远是null的话,可以写成如下:

public Void voidDemo() {
    System.out.println("Hello,Void");
    return null;
}

这个Void就是Kotlin中的Nothing?。它的唯一可被访问到的值也是null。
注意:UnitNothing的区别: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
}
上一篇下一篇

猜你喜欢

热点阅读