Kotlin | 6.Kotlin 的类型系统

2021-03-02 15:55:57 浏览数 (1)

本章内容包括:

  • 处理 null 的可空类型和语法
  • 基本数据类型和它们对应的Java类型
  • Kotlin 的集合,以及它们和Java的关系

6.1 可空性

代码语言:javascript复制
        /**-------------------- 6.1.1 可空类型 ? ----------------------*/
        // Kotlin和Java最重要的区别:对可空类型的显式的支持。

        /*java*/
//        int strLen(String s){
//            return s.length()
//        }

        fun strLen(s: String) = s.length
        // 在编译器会标记成错误(这个函数中的参数被声明成String类型,在Kotlin中这表示它必须包含一个String实例)
//        strLen(null)

        // 如果允许调用这个方法的时候传给它所有的可能的实参,包括null,需要显示地在类型名称后面加上问号来标记它:
        fun strLenSafe(s: String?) = {}
        // Type? = Type or null

        val e: String? = null
//        val f: String = null // 错误
//        strLen(e)// 错误

        // 代码清单6.1 使用if检查处理null
        fun strLenSafe2(s: String?): Int = if (s != null) s.length else 0
//        fun strLenSafe2(s: String?): Int = s?.length ?: 0
        strLenSafe2(e)// 0
        strLenSafe2("sss")// 3

        /**-------------------- 6.1.2 类型的含义 ----------------------*/
        /*
        * 类型就是数据的分类......决定了该类型可能的值,以及该类型的值上可以完成的操作
        * 可空和非可空的对象在运行时没有什么区别;
        * 可空类型并不是非空类型的包装。
        * 所有的检查都发生在编译期。这意味着使用Kotlin的可空类型并不会在运行时带来额外的开销
        */

        /**-------------------- 6.1.3 安全调用运算符: ?. ----------------------*/
        // ?. 它允许你把一次null检查和一次方法调用合并成一个操作
        val ok: String? = null
        println(ok?.toUpperCase())// null
        println(
                if (ok != null) {
                    ok.toUpperCase()
                } else null
        )
        // 这次调用的结果类型也是可空的。
        fun printAllCaps(s: String?) {
            // allCaps可能也是null
            val allCaps: String? = s?.toLowerCase()
            println(allCaps)
        }

        // 安全调用不光可以调用方法,也能访问属性。
        // 代码清单6.2 使用安全调用处理可空属性
        class Employee(val name: String, val manager: Employee?)

        fun managerName(employee: Employee): String? = employee.manager?.name
        val ceo = Employee("Da Boss", null)
        val developer = Employee("jingbin", ceo)
        managerName(ceo)// null
        managerName(developer)// Da Boss

        // 代码清单6.3 链接多个安全调用
        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 "unKnow"
//            return country ?: "unKnow"
        }

        val person = Person("beijing", null)
        println(person.countryName())// unKnow

        /**-------------------- 6.1.4 Elvis运算符 ?: ----------------------*/
        // kotlin中有方便的运算符来提供null的默认值。
        fun foo(s: String?) {
            // 如果 s 为null,结果是一个空的字符串
            val t: String = s ?: ""
        }

        // 代码清单6.4 使用Elvis运算符处理null值
        fun strLenSafe3(s: String?): Int = s?.length ?: 0
        println(strLenSafe3("aaa"))// 3
        println(strLenSafe3(null))// 0

        // 代码清单6.5 同时使用throw和Elvis运算符
//        class Address(val streetAddress: String, val zipCode: Int, val city: String, val country: String)
        fun printShipping(person: Person) {
            // 缺少address就抛出异常
            val address = person.company?.address ?: throw IllegalArgumentException("No address")
            // 使用with函数避免在这一行中重复使用四次address
            with(address) {
                println(streetAddress)
                println("$zipCode $city $country")
            }
        }

        val address = Address("高新区", 1000, "武汉", "中国")
        val company = Company("keji", address)
        val person1 = Person("jingbin", company)
        printShipping(person1)
        printShipping(Person("jingbin", null))// java.lang.IllegalArgumentException:No address


        /**-------------------- 6.1.5 安全转换 as? ----------------------*/
        // as? 运算符尝试把值转换成指定的类型,如果值不是合适的类型就返回null
//        foo as? Type
//        foo is Type --- foo as Type
//        foo !is Type --- null

        // 代码清单6.6 使用安全转换实现equals
        class Person2(val firstName: String, val lastName: String) {
            override fun equals(o: Any?): Boolean {
                // 检查类型,如果不匹配就返回false
                val otherPerson = o as? Person2 ?: return false
                // 在安全转换后,变量otherPerson被智能转换为Person类型
                return otherPerson.firstName == o.firstName && otherPerson.lastName == o.lastName
            }

            override fun hashCode(): Int = firstName.hashCode() * 37   lastName.hashCode()
        }

        val p1 = Person2("jing", "bin")
        val p2 = Person2("jing", "bin")
        // == 运算符会调用equals方法
        println(p1 == p2)// true
        println(p1.equals(12))// false


        /**-------------------- 6.1.6 非空断言 !! ----------------------*/
        // 有时候你并不需要Kotlin的这些支持来处理null值,你只需要直接告诉编译器这个值实际上并不是null。
        // 非空断言是Kotlin提供最简单直率的处理可空类型值的工具。
//        foo!!
//        foo != null  ---  foo
//        foo == null  ---  NullPointerException

        // 代码清单6.7 使用非空断言
        fun ignoreNulls(s: String?) {
            // 异常在这一行:告诉编译器 我知道这个值不为null,如果我错了我准备好了接收这个异常
            val sNotNull: String = s!!
            println(sNotNull.length)
        }
//        ignoreNulls(null)

        // 代码清单6.8 在Swing action中使用非空断言
//        class CopyRowAction(val list: JList<String>) : AbstractAction {
//            override fun isEnabled(): Boolean = list.selectedValue != null
        // 只会在isEnabled返回 true 时调用
//            override fun actionPerformed(e: ActionEvent) {
//                val selectedValue = list.selectedValue!!
//            }
//        }

        // 不要写这样的代码,因为不知道 跟踪信息只表明异常发生在哪一行代码
        person1.company!!.address!!.country


        /**-------------------- 6.1.7 let 函数 ----------------------*/
        /*
        * let函数让处理可空表达式变得更容易,和安全调用运算符一起,它允许你对表达式求值,检查求值结果是否为null,并把结果保存为一个变量。
        * 所有这些变动都在同一个简洁的表达式中。
        * let 函数做的所有事情就是把一个调用它的对象编程lambda表达式的参数。
        */
//        foo?.let {
//            ...it...
//        }
//        foo !=null // 在lambda内部it是非空的
//        foo ==null // 什么都不会发生

        // 代码清单6.9 使用let调用一个接受非空参数的函数
        fun sendEmailTo(email: String) {
            println("Sending email to $email")
        }

        val email: String? = "jingbin@qq.com"
        // let函数只在email的值非空时才会被调用
        email?.let { sendEmailTo(it) }

        val email2: String? = null
        // 不会被调用
        email2?.let { sendEmailTo(it) }

//        val person3 : Person? = getTheBestInWord()
//        if (person3!=null) sendEmailTo(person3.email)
        // 等同于
//        getTheBestInWord()?.let{sendEmailTo(it.email)}


        /**-------------------- 6.1.8 延迟初始化的属性 ----------------------*/

        //代码清单6.10 使用非空断言访问可空属性
//        open class MyService {
////            fun performAction(): String = "foo"
////        }
////        class MyTest {
////
////            // 声明一个可空类型的属性并初始化为null
////            private var myService: TypeSystem1Activity.MyService? = null
////
////            @Before
////            fun setUp() {
////                // 在setUp方法中提供真正的初始化器
////                myService = TypeSystem1Activity.MyService()
////            }
////
////            @Test
////            fun testAction() {
////                // 必须注意可空性:要么用!!,要么用?.
////                Assert.assertEquals("foo", myService!!.performAction())
////            }
////        }

        // 代码清单6.11 使用延迟初始化属性 lateinit
//        class MyTest2 {
//
//            // 声明一个不需要初始化器的非空类型的属性
//            private lateinit var myService: TypeSystem1Activity.MyService
//
//            @Before
//            fun setUp() {
//                // 像之前的例子一样在setUp方法中初始化属性
//                myService = TypeSystem1Activity.MyService()
//            }
//
//            @Test
//            fun testAction() {
//                // 不需要null检查直接访问属性
//                Assert.assertEquals("foo", myService.performAction())
//            }
//        }

        // 延迟初始化的属性都是var的。


        /**-------------------- 6.1.9 可空类性的扩展 ----------------------*/
        // isEmpty 是否是"" isBlank是否是Null或""

        //代码清单6.12 用可空接收者调用扩展函数
        fun verifyUserInput(input: String?) {
            // 这里不需要安全调用
            if (input.isNullOrBlank()) {
                println("isNullOrBlank....")
            }
        }
        // 接收者调用isNullOrBlank并不会导致任何异常
        verifyUserInput(null)

        /*
        * input 可空类型的值
        * isNullOrBlank() 可空类型的拓展
        * . 不需要安全调用
        */
        // 可空字符串的扩展
        fun String?.isNullOrBlank(): Boolean = this == null || this.isBlank()
        // 当你为一个可空类型(以?结尾)定义扩展函数时,这意味着你可以对可空的值调用这个函数;
        // 并且函数体中的this可能为null,所以你必须显示的检查。在可空类型的扩展函数中,this可能为null

        val person5: String? = null
        // 没有安全调用,所以 it 是可空类型
//        person5.let { sendEmailTo(it) }
        person5?.let { sendEmailTo(it) }

        /**-------------------- 6.1.10 类型参数的可空性 ----------------------*/
        // Kotlin中所有泛型类和泛型函数的类型参数默认都是可空的。

        // 代码清单6.13 处理可空的类型参数
        fun <T> printHashCode(t: T) {
            // 因为 t 可能为null,所以必须使用安全调用
            println(t?.hashCode())
        }
        // T 被推导成 Any?
        printHashCode(null)

        // 要是类型参数非空,必须要为它指定一个非空的上界,那样泛型会拒绝可空类型作为实参。
        // 代码清单6.14 为类型参数声明非空上界
        fun <T : Any> printHashCode2(t: T) {
            // T 不是可空的
            println(t.hashCode())
        }
//        printHashCode2(null)

        /**-------------------- 6.1.11 可空性和Java ----------------------*/

        // Java中的 @Nullable String 被Kotlin当做 String?,而@NotNull String就是String
        // @Nullable   Type = Type?
        // @NotNull   Type = Type

        /*
        * 平台类型
        * 平台类型本质上就是Kotlin不知道可空性信息的类型。
        * Type(Java) = Type? or Type  (Kotlin)
        */

        //代码清单6.15 没有可空性注解的 Java 类
        /*
         * public  class Person {

            private final String name;

            public Person(String name) {
                this.name = name;
            }

            public String getName() {
                return name;
            }
        }
         */
        // getName()能不能返回null? Kotlin编译器不知道可空性,需要自己处理它

        //代码清单6.16 不使用null检查访问Java类
        fun yellAt(person: JavaCode.PersonJava) {
            println(person.name.toUpperCase()   "!!!")
        }
//        yellAt(JavaCode.PersonJava(null)) // 会有异常

        // 代码清单6.17 使用null 检查来访问Java类
        fun yellAtSafe(person: JavaCode.PersonJava) {
            println((person.name ?: "Anyone").toUpperCase()   "!!!")
        }
        yellAtSafe(JavaCode.PersonJava(null))// Anyone!!!

        // 这两种都是可以的
        val person6 = JavaCode.PersonJava("222")
        val s: String? = person6.name
        val s1: String = person6.name


        /**继承*/
        // 代码清单6.18 使用String参数的Java接口
        /*Java*/
//        public  interface StringProcess {
//            void process(String value);
//        }

        // 代码清单6.19 实现Java接口时使用不同的参数可空性
        class StringPrint : JavaCode.StringProcess {
            override fun process(value: String) {

            }
        }

        class StringPrint2 : JavaCode.StringProcess {
            override fun process(value: String?) {
                if (value != null) {
                    println(value)
                }
            }
        }

6.2 基本数据类型和其他基本类型

代码语言:javascript复制
        /**-------------------- 6.2.1 基本数据类型:Int、Boolean及其他 ----------------------*/

        // Kotlin并不区分基本数据类型和包装类型,使用的永远是同一类型:(如:Int)
        val i: Int = 1
        val listOf: List<Int> = listOf(1, 2, 3)

        fun showProgress(progress: Int) {
            val coerceIn = progress.coerceIn(0, 100)
            println("we are ${coerceIn}% done!")
        }
        // 只在泛型类的时候会被编译成Integer,如集合类,其他是int
        // 对应到Java基本数据类型的类型完整列表如下:
        /*
        * 整数类型:Byte、Short、Int、Long
        * 浮点型类型:Float、Double
        * 字符类型:Char
        * 布尔类型:Boolean
        */

        /**-------------------- 6.2.2 可空的基本数据类型:Int?、Boolean? 及其他 ----------------------*/
        data class Person(val name: String, val age: Int? = null) {
            fun isOlderThan(other: Person): Boolean? {
                if (age == null || other.age == null) {
                    return null
                }
                return age > other.age
            }
        }

        /**-------------------- 6.2.3 数字转换 ----------------------*/
        // Kotlin和Java之间一条重要的区别就是处理数字转换的方式。

        val i2 = 1
        // 必须显示的转换
        val l: Long = i2.toLong()

        println(i2.toLong() in listOf(1L, 2L, 3L))

        /*
        * 基本数据类型字面值
        * 使用后缀 L 表示Long  字面值:123L
        * 标准浮点数表示 Double  字面值:0.12 2.0 1.2e10
        * 后缀 F 表示Float类型 字面值:123.4f、.456f
        * 使用前缀 0x或0X 表示十六进制字面值:0xCAFEBABE 或者 0xbdcL
        * 使用前缀 0b或0B 表示二进制字面值:0b000000101
        *
        * */

        // 初始化一个类型已知和变量时,或者把字面值作为实参传给函数时,必要的转换会自动的发生。
        fun foo(l: Long) = println(l)
        foo(42)

        val b: Byte = 1
        val l2 = b   1L


        /**-------------------- 6.2.4 Any 和 Any? 根类型 ----------------------*/
        /*
        * 和 Object作为Java类层级结构的根差不多,Any类型是Kotlin所有非空类型的超类型(非空类型的根)。
        */
        // Any是引用类型,所以值42会被装箱
        val answer: Any = 42


        /**-------------------- 6.2.5 Unit类型 Kotlin的 void ----------------------*/

        //  Kotlin中的Unit类型完成了Java中的void一样的功能。当函数没什么有意义的结果返回时,他可以用作函数的返回类型
        fun f(): Unit {}

        // 显式的Unit声明被省略了
        fun f2() {}


        class NoResultProcess : Processor2<Unit> {
            // 返回Unit,但可以省略类型说明
            override fun process(): Unit {
                // 这里不需要显示的return
//                return Unit
            }
        }

        /**-------------------- 6.2.6 Nothing类型: 这个函数永不返回 ----------------------*/
        // 对某些 Kotlin 函数来说,"返回类型”的概念没有任何意义,因为它们从来不会成功地结束。
        fun fail(message: String): Nothing {
            throw IllegalStateException(message)
        }
        // 注意 返回Nothing的函数可以放在Elvis运算符的右边来做先决条件检查:
//        val address: String? = null
        val address: String? = "wuhan"
        val s = address ?: fail("No address")

6.3 数组与集合

代码语言:javascript复制
        /**-------------------- 6.3.1 可空性和集合 ----------------------*/
        // 代码清单6.21 创建一个包含可空值的集合
        // 从一个文件中读取文本行的列表,并尝试把每一行文本解析成一个数字
        fun readNumbers(reader: BufferedReader): List<Int?> {
            // 创建包含可空Int值的列表
            val arrayList = ArrayList<Int?>()
            for (line in reader.readLine()) {
                try {
                    val toInt = line.toInt()
                    // 向列表添加整数(非空值)
                    arrayList.add(toInt)
                } catch (e: NumberFormatException) {
                    // 向列表添加null
                    arrayList.add(null)
                }
            }
            return arrayList
        }

        // 代码清单6.22 使用可空值的集合
        fun addValidNumbers(numbers: List<Int?>) {
            var sumOfValidNumbers = 0
            var invalidNumbers = 0
            for (number in numbers) {
                if (number != null) {
                    sumOfValidNumbers  = number
                } else {
                    invalidNumbers  
                }
            }
            println("sumOfValidNumbers:  $sumOfValidNumbers")
            println("invalidNumbers:  $invalidNumbers")
        }

        val bufferedReader = BufferedReader(StringReader("1nabcn42"))
        // [1,null,42]
        val readNumbers = readNumbers(bufferedReader)
        addValidNumbers(readNumbers)
        // sumOfValidNumbers:  43
        // invalidNumbers:  1

        // 代码清单6.23 对包含可空值的集合使用 filterNotNull
        fun addValidNumbers2(numbers: List<Int?>) {
            // 类型为List<Int>,因为过滤保证了不会出现null
            val filterNotNull: List<Int> = numbers.filterNotNull()
            println("sum of valid numbers::  ${filterNotNull.sum()}")
            println("Invalid numbers:  ${numbers.size - filterNotNull.size}")
        }

        /**-------------------- 6.3.2 只读集合和可变集合 ----------------------*/
        /*
        * Kotlin的集合设计和Java不同的另一项重要特质是,它把访问集合数据的接口和修改集合数据的接口分开了。
        * 一般的规则是在代码的任何地方都应该使用只读接口,只在代码需要修改集合的地方使用可变接口的变体。
        *
        * Collection
        *  - size
        *  - iterator()
        *  - contains()
        * MutableCollection 继承Collection
        *  - add()
        *  - remove()
        *  - clear()
        */

        // 代码清单6.24 使用只读集合接口与可变集合接口
        fun <T> copyElements(source: Collection<T>, target: MutableCollection<T>) {
            // 在source集合中的所有元素中循环
            for (item in source) {
                // 想可变的target集合中添加元素
                target.add(item)
            }
        }

        val source: Collection<Int> = arrayListOf(3, 5, 7)
        val target: MutableCollection<Int> = arrayListOf(1)
        copyElements(source, target)
        println(target)// [1,3,5,7]

        // 只读集合不一定是不可变的,只读集合并不总是线程安全的。


        /**-------------------- 6.3.3 Kotlin集合和Java ----------------------*/
        /*
        * 集合创建函数
        * 集合类型    只读                可变
        * List      listOf          mutableListOf、arrayListOf
        * Set       setOf           mutableSetOf、hashSet、linkedSetOf、sortedSetOf
        * Map       mapOf           mutableMapOf、hashMapOf、linkedMapOf、sortMapOf
        *
        * Java并不会区分只读集合与可变集合,即使Kotlin中把集合声明成只读的,Java代码页能够修改这个集合。
        */

        /*Java*/
//        public static class CollectionUtils {
//            public static List<String> uppercaseAll(List<String> items) {
//                for (int i = 0; i < items.size(); i  ) {
//                    items.set(i, items.get(i).toLowerCase());
//                }
//                return items;
//            }
//        }

        // 声明只读的参数
        fun printInUppercase(list: List<String>) {
            // 调用可以修改集合的Java函数
            println(JavaCode.CollectionUtils.uppercaseAll(list))
            // 打印被修改过的函数
            println(list.first())
        }

        val listOf = listOf("a", "b", "c")
        printInUppercase(listOf)// [A,B,C] A


        /**-------------------- 6.3.4 作为平台类型的集合 ----------------------*/
        /*
        * 集合是否可空?
        * 集合中的元素是否可空?
        * 你的方法会不会修改集合?
        */

        // 代码清单6.25 使用集合参数的Java接口r
//        public interface FileContentProcessor {
//            void processContents(File path, byte[] binaryContents, List<String> textContents);
//        }

        // 代码清单6.26 FileContentProcessor的kotlin实现
        class FileIndexer : JavaCode.FileContentProcessor {
            override fun processContents(path: File, binaryContents: ByteArray?, textContents: MutableList<String>?) {

            }
        }

        // 代码清单6.27 另一个使用集合参数的Java接口
//        public interface DataParser<T> {
//            void parseData(String input, List<T> output, List<String> errors);
//        }

        // 代码清单6.28 DataParser的kotlin实现
        class PersonParser : JavaCode.DataParser<Object4Activity.Person> {
            override fun parseData(input: String, output: MutableList<Object4Activity.Person>, errors: MutableList<String?>?) {
                // 默认是都会为空、集合元素不为空、方法会修改集合。
                // 需要根据具体场景配置设置
            }
        }


        /**-------------------- 6.3.5 对象和基本数据类型的数组 ----------------------*/
        fun main(args: Array<String>) {
            // 使扩展属性 array.indices 在下标的范围内迭代
            for (i in args.indices) {
                // 通过下标使用array[index]访问元素
                println("Argument $i is: ${args[i]}")
            }
        }

        // 代码清单6.30 创建字符数组
        val array = Array<String>(26) { i -> ('a'   i).toString() }
        println(array.joinToString(""))// abcd...

        // 代码清单6.31 向vararg方法传递集合
        val listOf1 = listOf("a", "b", "c")
        // 期望vararg参数时使用展开运算符 (*) 传递数组
        println("%s%s%s".format(*listOf1.toTypedArray()))

        // 创建存储了5个0的整型数组的两种选择:
        val fiveZeros = IntArray(5)
        val intArrayOf = intArrayOf(0, 0, 0, 0, 0)
        // 接收lambda的构造方法的例子
        val intArray = IntArray(5) { i -> (i   1) * (i   1) }
        println(intArray.joinToString())// 1,4,9,16,25

        // 代码清单6.32 对数组使用 forEachIndexed
        fun main2(args: Array<String>) {
            args.forEachIndexed { index, element -> println("Argument $index is: $element !!!!") }
        }

总结

  • Kotlin 对可空类型的支持,可以帮助我们在编译期,检测出潜在的NullPointerException错误。
  • Kotlin 提供了像安全调用(?.)、 Elvis 运算符(?:)、非 言( !!)及let 函数这样的工具来简洁地处理可空类型。
  • as ?运算符提供了 种简单的方式来把值转换成 个类型,以及处理当它拥有不同类型时的情况。
  • Java 中的类型在 Kotlin 中被解释成平台类型,允许开发者把它们当作可空或非空来对待。
  • 表示基本数字的类型(如 Int )看起来用起来都像普通的类,但通常会被编译成 Java 基本数据类型。
  • 可空的基本数据类型(如 Int ?)对应着 Java 中的装箱基本数据类型(如java.lang.Integer )。
  • Any 类型是所有其他类型的超类型,类 Java Object 。而 Unit 类比于void
  • 不会正常终止的函数使用 Nothing 类型作为返回类型。
  • Kotlin 使用标准 Java 集合类,并通过区分只读和可变集合来增强它们。
  • 当你在 Kotlin 中继承 Java 类或者实现 Java 接口时,你需要仔细考虑参数的可空性和可变性。
  • Kotlin的Array 类就像普通的泛型类 但它会被编译成 Java 数组。
  • 基本数据类型的数组使用像 IntArray 这样的特殊类来表示。

0 人点赞