Kotlin笔记--类型系统

      这一节主要学习Kotlin的类型系统知识点,类型系统是Kolin最重要的一部分,Kotlin 引入了 一些新特性,提升代码可读性的基本要素,比如:对可空的类型和只读集合的支持。把数组作为头等公民来支持;Kotlin不区分基本数据类型和引用类型,它使用的永远是一个类型(例如Int),此外,你还能对一个数字类型的值调用方法。在运行时,数字类型会尽可能地使用最高效的方式来表示,大多数情况下,对于变量、属性、参数和返回类型,Kotlin的Int类型会被编译成Java基本数据类型int。唯一不可行的例外是泛型类,例如集合,用作泛型类型参数的基本数据类型会被编译成对象的Java包类型。

     对应到Java基本数据类型的类型完整列表如下:整数类型:Byte、Short、Int、Long;浮点数类型:Float、Double;字符类型:Char;布尔类型:Boolean;像Int这样的Kotlin类型在底层可以轻易地编译成对应的Java基本数据类型。而在Kotlin中使用Java声明时,Java基本数据类型会变成非空类型,因为它们不能持有null值。

1.1 可空类型
     Kotin 和 Java 的类型系统之间第一条也可能是最重要的一条区别是, Kotiin 对可空类型的显式的支持。意思是指出你的程序中哪些变量和属性允许为 null 的方式。如果你允许调用这个方法的时候传给它所有可能的实参,包括那些可以为null 的实参,需要显式地在类型名称后面加上问号来标记它:

fun strLenSafe(s: String?) = ...

问号可以加在任何类型的后面来表示这个类型的变量可以存储 null 引用 :String ?、 Int ?、 MyCustomType ?,Type? =  Type or null ;提示一下 没有问号的类型表示这种类型 的变量不能存储 null 引用 ;说明所有常见类型默认都是非空的,除非显式地把它标记为可空。

1.2 安全调用运算符: "?."

      安全调用运算符: 1 ,它允许你把一次 nu ll 检查和一次方法调用合并成一个操作。例如,表达式 s? .toUpperCase()等同于下面这种烦琐的写法: if (s!=null) s.toUpperCase() else null 。 它只会调用非空值得方法;安全调用不光可以调用方法, 也能用来访问属性。

class Employee(val name: String, val manager: Employee?)

fun managerName(employee: Employee): String? = employee.manager?.name

链接多个安全调用:

class Address(val streetAddress: String, val zipCode: Int,
              val city: String, val country: String)

class Company(val name: String, val address: Address?)

class Person(val name: String, val company: Company?)

fun Person.countryName(): String {
   val country = this.company?.address?.country
   return if (country != null) country else "Unknown"
}

fun main(args: Array<String>) {
    val person = Person("Dmitry", null)
    println(person.countryName())
}

输出:Unknown

1.3 Elvis运算符 : "?:" ,Kotiin 有方便的运算符来提供代替 null 的默认值。它被称作 Elvis 运算符

fun foo(s:String?) {
   val t: String = s ?: ""
  }

      Elvis 运算符接收两个运算数,如果第一个运算数不为 null ,运算结果就是第一个运算数;如果第一个运算数为 null ,运算结果就是第二个运算数。

fun strLenSafe(s: String?): Int = s?.length ?: o
>> println(strLenSafe (”abc”))
3
>> println (strLenSafe (null) )
。

1.4 安全转换 as?, as ?运算符尝试把值转换成指定的类型, 如果值不是合适的类型就返回 nul

foo as? Type   true  foo as type    false null 

一种常见的模式是把安全转换和 Elvis 运算符结合使用。例如,实现 equals方法的时候这样的用法非常方便。

class Person(val firstName: String, val lastName: String)

override fun equals(o: Any?): Boolean {
 otherPerson = o as? Person?: return false
 return otherPerson.firstName = firstName &&  otherPerson. lastName== lastName
 }

override fun hashCode(): Int =
firstName.hashCode( ) * 37 + lastName.hashCode()}
 
>> val pl = Person ("Drnitry”,”Jernerov”)
>> val p2 = Person ("Drnitry”,”Jernerov")
>> println (pl == p2)   // ==运算符会调用 “equals”方法 
true
>> println (pl.equals(42) )
false

说明:安全调用、安全转换和 Elvis 运算符都非常有用,它们出现在 Kotlin 代码中的频率非常高。这一块需要多练掌握;

1.5 非空断言: "!!",非空断言是 Kotlin 提供给你的最简单直率的处理可空类型值的工具。它使用双感叹号表示,可以把任何值转换成非空类型。如果对 null 值做非空断言,则会抛出异常。

 fun ignoreNulls(s: String?) {
    val sNotNull: String = s!!
    println(sNotNull.length)
  }

  fun main(args: Array<String>) {
    ignoreNulls(null)
  }

当你使用 !! 并且它的结果是异常时 , 异常调用的跟踪信息只表明异常发生在哪一行代码,而不会表明异常发生在哪一个表达
式 。 为了让跟踪信息更清晰精确地表示哪个值为 nu ll , 最好避免在同一行中使用多个!!断言

person. company! ! .address! ! country  //不要写这样的代码!

因为 如果上面这 一 行代码中发生了异常,你不能分辨出到底 company 的值为null ,还是 address 的值为 null 。
       如果你要将一个可空值作为实参传递一个只接收非空值的函数时 ,应该怎么办?编译器不允许在没有检查的情况下这样做,因为这样不安全。 Kotlin 语言并没有对这种使用场景的特殊支持,但是标准库函数可以帮到你 : 这个函数叫作 let 。

      1.6 "let"函数,let 函数让处理可空表达式变得更容易 。 和安全调用运算符一起,它允许你对表达式求值,检查求值结果是否为 null ,并把结果保存为一个变量。 所有这些动作都在同一个简洁的表达式中。是被传递给一个接收非空参数的函数 。 比如说下面这个 sendEmailTo 函数,它接收一个 String 类型 的参数井向这个地址发送一封邮件。这个函数在 Kotlin 中是这样写的,它需要一个非空的参数:

fun sendEmailTo(email: Str工ng) { /* ... *I }

不能把可空类型的值传给这个函数:

>> val email: String? =
>> sendEmailTo(email)
ERROR: Type mismatch: inferred type is String? but String was expected
必须显式地检查这个值不为 null:
if (email != null) sendEmailTo (email)

       还有一种处理方式:使用let 函数,并通过安全调用来调用它;let函数做的所有事情就是把一个调用它的对象变成 lambda 表达式的参数 。let 函数只在 email 的值非空时才被调用,所以你就能在 lambda 中把 email当作非空的实参使用。

email?.let { email -> sendEmai l To(ema工 1) }

注意,如果有一些很长的表达式结果不为 null ,而你又要使用这些结果时,let 表示法特别方便 。 这种情况下你不必创建一个单独的变量。对比一下显式的 if检查

val person : Person? = getTheBestPersoninTheWorld()
if (person ! = null) sendEmailTo(person.email)

功能相同但没有额外变量的代码;

1.7 使用延迟化属性

 import org.junit.Before
import org.junit.Test
import org.junit.Assert

class MyService {
    fun performAction(): String = "foo"
}
class MyTest {
    private var myService: MyService? = null

    @Before fun setUp() {
        myService = MyService()
    }

    @Test fun testAction() {
        Assert.assertEquals("foo",
            myService!!.performAction())
    }
}

     注意:延迟化的属性都是var,因为需要在构造方法外修改它的值 , 而val 属性会被编译 成必 须在构造方法中初始化的 final 字段 ;lateinit 属性常见的一种用法是依赖注入 。 在这种情况下,lateinit 属性的值是被依赖注入框架从外部设直的 。 为了保证和各种Java (依赖注入)框架的兼容性, Kotlin 会自动生成一个和 lateinit 属性具有相同可见性的字段。 如果属性的可见性是 public ,生成字段的可见性也是 public 。如果想要使用 let 来检查非空的实参,你就必须使用安全调用运算符?.,就像之前看到的代码一样: person?.let { sendEmailTo(it) } 。

1.8 类型参数的可空性

       Kotiin 中所有泛型类和泛型函数的类型参数默认都是可空的。任何类型包括可空类型在内,都可以替换类型参数。这种情况下,使用类型参数作为类型的声明都允许为接口,尽管类型参数 T 并没有用问号结尾。当你为一个可空类型(以?结尾)定义扩展函数时,这意味着你可以对可空的值调用这个函数;并且函数体中的 this 可能为 null ,所以你必须显式地检查 。在 Java 中,this 永远是非空的,因为它引用的是当前你所在这个类的实例。而在Kotlin 中,这并不永远成立:在可空类型的扩展函数中, this 可以为 null 。注意我们之前讨论的 let 函数也能被可空的接收者调用,但它并不检查值是否为 null 。如果你在一个可空类型直接上调用 let 函数,而没有使用安全调用运算符,lambda 的实参将会是可空的:因此,如果想要使用 let 来检查非空的实参,你就必须使用安全调用运算符?.,就像之前看到的代码一样: person?.let { sendEmailTo(it) } 。

猜你喜欢

转载自blog.csdn.net/ljt2724960661/article/details/108330918
今日推荐