Kotlin类型系统笔记

作者: dengyin2000 | 来源:发表于2018-02-23 14:26 被阅读767次

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

    数值常量字面值有以下几种
    • 十进制:123
    • Long类型:123L
    • 十六进制:0x0F
    • 二进制:0b000011
    • Double类型:123.123123.123e10
    • Float类型:123.123f123.123F
    • 我们也可以使用下划线来分隔数字:1_000_0001234_5678_9012_3456L0xFF_EC_DE_5E0b11010010_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表示。他们不能直接用来当作数字。

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

    • 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
    编译器告诉我们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
    }
    

    相关文章

      网友评论

        本文标题:Kotlin类型系统笔记

        本文链接:https://www.haomeiwen.com/subject/ojftxftx.html