前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kotlin | 6.Kotlin 的类型系统

Kotlin | 6.Kotlin 的类型系统

作者头像
Jingbin
发布2021-03-02 15:55:57
2.3K0
发布2021-03-02 15:55:57
举报

本章内容包括:

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

6.1 可空性

        /**-------------------- 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 基本数据类型和其他基本类型

        /**-------------------- 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 数组与集合

        /**-------------------- 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("1\nabc\n42"))
        // [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 这样的特殊类来表示。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 6.1 可空性
  • 6.2 基本数据类型和其他基本类型
  • 6.3 数组与集合
  • 总结
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档