Kotlin | 9. 泛型

本章内容包括:

  • 声明泛型函数和类
  • 类型擦除和实化类型参数
  • 声明点变型和使用点变型

9.1 泛型类型参数


         // 如果要创建一个空的列表,必须显示的指定,有值的话可以被推导出来
        val readers: MutableList<String> = mutableListOf()
        val readers1 = mutableListOf<String>()

        val reader2 = listOf("jingbin", "jinbeen")

        /**-------------------- 9.1.1 泛型函数和属性 ----------------------*/
        // slice 泛型函数的类型形参为T
        fun <T> List<T>.slice(indices: IntRange): List<T>? {
            return null
        }
        /*
        * 第一个 <T> :类型形参声明
        * 第二个 <T> :接收者 类型使用了类型形参
        * 第三个 <T> :返回 类型使用了类型形参
        */

        // 代码清单9.1 调用泛型函数
        val letters = ('a'..'z').toList()
        // 显示地指定类型实参
        println(letters.slice<Char>(0..2))// [a,b,c]
        // 编译器推导出这里的T是Char
        println(letters.slice(10..13))// [k,l,m,n]

        // 代码清单9.2 调用泛化的高阶函数
        val authors = listOf("jingbin", "jinbeen")
        val readers3 = mutableListOf<String>()
        fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T>? {
            return null
        }
        readers3.filter { it !in authors }


        // 这个 泛型扩展函数 能任何种类元素的列表上调用
//        val <T> List<T>.penultimate: T
//        get() = this[size - 2]

        // 在这次调用中,类型参数T被推导成Int
        println(listOf(1, 2, 3, 4).penultimate)


        /**-------------------- 9.1.2 声明泛型类 ----------------------*/
        // List 接口定义了类型参数T
//        interface List2<T> {
        // 在接口或类的内部,T可以当作普通类型使用
//            operator fun get(index: Int): T
//        }

        // 这个类实现了List2,提供了具体类型实参: String
        class StringList : List2<String> {
            // 注意 T 如何被 String 代替
            override fun get(index: Int): String = ""
        }

        // 现在  ArrayList 的泛型类型形参 T 就是List的类型实参
        class ArrayList<T> : List2<T> {
            override fun get(index: Int): T {
                TODO("not implemented")
            }
        }

        // 一个类可以把它自己作为类型实参引用
//        interface Comparable<T>{
//            fun compareTo(other:T):Int
//        }
        class String3 : Comparable<String3> {
            override fun compareTo(other: String3): Int {
                return 1
            }
        }


        /**-------------------- 9.1.3 类型参数约束 ----------------------*/
        // Java
//        <T extends Number> T sum(List<T> list)

        // Kotlin
        // 通过在类型参数后指定上界来定义约束
//        fun <T : Number> List<T>.sum(): T

        println(listOf(1, 2, 3).sum())// 6

        // 指定 Number 为类型形参的上界
        fun <T : Number> onHalf(value: T): Double {
            // 调用Number类中的方法
            return value.toDouble() / 2.0
        }
        println(onHalf(3))// 1.5

        // 代码清单9.3 声明带类型参数约束的函数
        // 这个函数的实参必须是可比较的元素
        fun <T : kotlin.Comparable<T>> max(first: T, second: T): T {
            return if (first > second) first else second
        }
        // 字符串按字母表顺序比较
        println(max("kotlin", "java"))// kotlin

        // 代码清单9.4 为一个类型参数指定多个约束
        fun <T> ensureTrailingPeriod(seq: T)
                where T : CharSequence, T : Appendable {// 类型参数约束的列表
            if (!seq.endsWith('.')) {// 调用为 CharSequence 接口定义的扩展函数
                seq.append('.')// 调用为 Appendable 接口的方法
            }
        }

        val helloWord = StringBuilder("Hello World")
        ensureTrailingPeriod(helloWord)
        println(helloWord)// Hello World.


        /**-------------------- 9.1.4 让类型形参非空 ----------------------*/
        // 没有指定上界的类型形参将会使用 Any?
        class Processor<T> {
            fun process(value: T) {
                value?.hashCode()// value 是可空的,所以要用安全调用
            }
        }

        // 可空类型 String? 被用来替代T
        val processor = Processor<String?>()
        // 使用 null 作为value实参的代码可以编译
        processor.process(null)

        // 保证替代类型始终是非空类型
        class Processor2<T : Any> {
            fun process(value: T) {
                value.hashCode()
            }
        }

9.2 运行时的泛型:擦除和实化类型参数


         /**-------------------- 9.2.1 运行时的泛型:类型检查和转换 ----------------------*/
        // 和Java一样,Kotlin的泛型在运行时也被擦除了。
        val list1: List<String> = listOf("a", "b")
        val list2: List<Int> = listOf(1, 2, 3)

        /*
        * ["a","b"]  -- list1
        * [1,2,3]    -- list2
        * 在运行时,你不知道list1和list2是否声明成字符串或者整数列表。它们每个都只是List
        */
        // 一般而言,在 is 检查中不可能使用类型实参中的类型。下面这样的代码不会编译:
//        if(value is List<String>){...}

        // 可以使用特殊的 星号投影 语法来做这种检查:
//        if (value is List<*>){...}

        // 代码清单9.5 对泛型类型做类型转换
        fun printSum(c: Collection<*>) {
            // 这里会有警告。Unchecked cast:List<*> to List<Int>
            val intList = c as? List<Int> ?: throw IllegalArgumentException("List is expected")
            println(intList.sum())
        }
        // 一切都符合预期
        printSum(listOf(1, 2, 3))// 6
        // Set 不是列表,所以抛出了异常
        printSum(setOf(1, 2, 3))// IllegalArgumentException: List is expected
        // 类型转换成功,但后面抛出了另外的异常,as 通过了,在计算时抛出
        printSum(listOf("a", "b", "c"))// ClassCastException:String cannot be cast to Number

        // 代码清单9.6 对已知类型实参做类型转换
        fun printSum2(c: Collection<Int>) {
            // 这次的检查是合法的
            if (c is List<Int>) {
                println(c.sum())
            }
        }
        printSum2(listOf(1, 2, 3))// 6


        /**-------------------- 9.2.2 声明带实化类型参数的函数 ----------------------*/
        // 内联函数的类型形参能够被实化,意味着你可以在运行时引用实际的类型实参。

        // 代码清单9.7 声明带实化类型参数的函数
//        inline fun <reified T> isA(value: Any) = value is T
        println(isA<String>("abc"))// true
        println(isA<String>(123))// false

        // 代码清单9.8 使用标准库函数 filterInstance
        val items = listOf("one", 2, "three")
        println(items.filterIsInstance<String>())// [one,three]

        // 代码清单9.9 filterInstance 的简化实现
        // reified声明了类型参数不会在运行时被擦除
//        inline fun <reified T>
//                Iterable<*>.filterIsInstance(): List<T> {
//            val destination = mutableListOf<T>()
//            for (element in this) {
//                // 可以检查元素是不是指定为类型实参的类的实例
//                if (element is T) {
//                    destination.add(element)
//                }
//            }
//            return destination
//        }

        /*
        *  为什么实化只对内联函数有效?
        *  每次调用带实化类型参数的函数时,编译器都知道这次特定调用中用作类型实参的确切类型。
        *  因此,编译器可以生成引用作为类型实参的具体类的字节码。
        */

        /**-------------------- 9.2.3 使用实化类型参数替代类引用 ----------------------*/
        // 使用标准的 ServiceLoader Java API加载一个服务:
        val serviceImpl = ServiceLoader.load(Service::class.java)
        // ::class.java == Service.java 如何获取java.lang.Class对应的Kotlin类

        // 使用带实化类型参数的函数重写这个例子
        val serviceImpl2 = loadService<Service>()

        // 简化Android上的startActivity函数
//        inline fun <reified T : Activity> Context.startActivity() {
//            // 把 T:class 当成类型参数的类访问
//            val intent = Intent(this, T::class.java)
//            startActivity(intent)
//        }
        startActivity<Generic2Activity>()

        /**-------------------- 9.2.4 实化类型参数的限制 ----------------------*/
        /*
        * 具体来说,可以按下面的方式使用实化类型参数:
        *  - 用在类型检查和类型转换中 (is、!is、as、as?)
        *  - 使用Kotlin反射API,我们将在第10章讨论 (::class)
        *  - 获取相应的 java.lang.Class(::class.java)
        *  - 作为调用其他函数的类型实参
        *
        * 不能做下面的事情:
        *  - 创建指定为类型参数的类的实例
        *  - 调用类型参数类的伴生对象的方法
        *  - 调调用带实化类型参数函数的时候使用非实化类型形参作为类型实参
        *  - 把类、属性或者非内联函数的类型参数标记为reified
        */

9.3 变型:泛型很子类型化


         /**-------------------- 9.3.1 为什么存在变型:给函数传递实参 ----------------------*/
        // 打印出列表内容的函数
        fun printContents(list: List<Any>) {
            println(list.joinToString())
        }
        printContents(listOf("abc", "bac"))// abc,bac

        // 修改列表
        fun addAnswer(list: MutableList2<Any>) {
            list.add(42)
        }

        val strings = mutableListOf("abc", "bac")
        // 编译不了,添加或替换忽悠安全性问题
//        addAnswer(strings)

        /**-------------------- 9.3.2 类、类型和子类型 ----------------------*/
        // 每一个Kotlin类都可以用于构造至少两种类型
        var x: String
        var y: String?

        /*
        * 任何时候,如果需要的是 类型A 的值,你都能够使用 类型B 的值(当做A的值),类型B就称为类型A的子类型。
        * 如果A是B的子类型,那么B就是A的超类型。
        */

        // 代码清单9.10 检查一个类型是否是另一个的子类型
        fun test(i: Int) {
            // 编译通过,因为 Int 是Number的子类型
            val n: Number = i

            // 不能编译,因为Int不是String的子类型
//            fun f(s: String) {}
//            f(i)
        }
        /*
        * 简单的情况下,子类型和子类本质上意味着一样的事物。
        * 例如,Int类是Number的子类,因此Int类型是Number类型的子类型。
        *
        * 可空类型 的子类型和子类不是同一事物。
        */

        val s: String = "abc"
        // 这次的赋值是很合法的,因为 String 是 String? 的子类型
        val t: String? = s

        // MutableList<String> 不是 MutableList<Any> 的子类型,因为MutableList可空


        /**-------------------- 9.3.3 协变:保留子类型化关系 ----------------------*/
        // 如果 A 是 B 的子类型,那么 List<A>就是List<B>的子类型。这样的类或者接口被称为协变的。

        // 要声明类在某个类型参数上是可协变的,在该类型参数的名称前加上 out 关键字即可
//        interface Producer<out T>{
//            fun produce():T
//        }


        // 代码清单9.11 定义一个不变型的类似集合的类
        open class Animal {
            fun feed() {}
        }

        // 类型参数没有声明成协变的
        class Herd<T : Animal> {
            val size: Int get() = 1
            operator fun get(i: Int): T? {
                return null
            }
        }

        fun feedAll(animal: Herd<Animal>) {
            for (i in 0 until animal.size) {
                animal[i]?.feed()
            }
        }

        // 代码清单9.12 使用一个不变型的类似集合的类
        class Cat : Animal() {
            fun cleanLitter() {}
        }

        fun takeCarsOfCats(cats: Herd<Cat>) {
            for (i in 0 until cats.size) {
                cats[i]?.cleanLitter()
                // 错误:推导的类型是 Herd<Cat>,但期望的却是 Herd<Animal>。加上 out就好了: Herd<out T : Animal>
//                feedAll(cats)
            }
        }

        // 代码清单9.13 使用一个协变的类似集合的类
        class Herd2<out T : Animal> {

        }

        // 函数参数的类型叫做 in 位置,而函数返回类型叫作 out 位置
//        interface Transformer<T> {
//            // in 位置:[t:T]
//            // out 位置:[T]
//            fun transform(t: T): T
//        }

        /*
        * 类型参数 T 上的关键字 out 有两层含义:
        *  - 子类型化会被保留 (Producer<Car> 是  Producer<Animal>的子类型)
        *  - T 只能用在 out 位置
        */

        // List<Interface>的List是只读的,所以它只有一个返回类型为 T 的元素的方法 get,而没有定义任何类型为T的元素存储到列表中的方法,因为它是协变的。
//        interface List<out T>:Collection<T>{
//            operator fun get(index:Int):T
//        }

        // MutableList 不能在T上声明成协变的
//        interface MutableList<T> : List<T>, MutableCollection<T> {
//             因为T用在了 in 的位置
//            override fun add(element: T): Boolean
//        }

        /**-------------------- 9.3.4 逆变:反转子类型化关系 ----------------------*/

        // 在 in 位置使用T
//        interface Comparator<in T> {
//            fun compare(e1: T, e2: T): Int
//        }

        val anyComparator = Comparator<Any> { e1, e2 ->
            e1.hashCode() - e2.hashCode()
        }
        // 可以用任意对象的比较器比较具体对象,比如字符串
        val strings1: List<String> = listOf("11", "22")
        strings1.sortedWith(anyComparator)

        /*
        * Animal  <--  Cat
        * Producer<Animal>  <-- 协变 -- Producer<Cat>
        * Producer<Animal>   -- 逆变 --> Producer<Cat>
        * 对协变类型Producer<T>来说,子类型化保留了,但对逆变类型Consumer<T>来说,子类型化反转了
        *
        * 协变的、逆变的和不变型的类
        * 协变                             逆变                             不变型
        * -------------------------------------------------------------------------------------
        * Producer<out T>                  Consumer<in T>                  MutableList<T>
        * -------------------------------------------------------------------------------------
        * 类的子类型化保留了:Producer<Cat>  子类型化反转了:Consumer<Animal>   没有子类型变化
        * 是Producer<Animal>的子类型        是Consumer<Cat>的子类型
        * -------------------------------------------------------------------------------------
        * T 只能在 out 位置                 T 只能在 in 位置                   T 可以在任何位置
        */

        // 一个类可以在一个类型参数上协变,同时在另一个类型参数上逆变。Function接口
//        interface Function1<in P, out R> {
//            operator fun invoke(p: P): R
//        }


        fun enumerateCats(f: (Cat) -> Number) {}
        fun Animal.getIndex(): Int = 1
        // 在 Kotlin 中这段代码是合法的。Animal是Cat的超类型,而 Int 是Number的子类型
        enumerateCats(Animal::getIndex)

        /**-------------------- 9.3.5 使用点变型:在类型出现的地方指定变型 ----------------------*/
        /*
        * 声明点变型带来了更简洁的代码,因为只用指定一次变型修饰符,所有这个类的使用者都不用再考虑这些了。
        *
        * Kotlin的声明点变型 vs .Java 通配符
        * // java
        * public interface Stream<T> {
        *   <R> Stream<R> map(Function<? super T, ? extends R> mapper);
        * }
        */

        // 代码清单9.14 带不变型类型参数的数据拷贝函数
        fun <T> copyData(source: kotlin.collections.MutableList<T>, destination: kotlin.collections.MutableList<T>) {
            for (item in source) {
                destination.add(item)
            }
        }

        // 代码清单9.15 带不变类型参数的数据拷贝函数
        fun <T : R, R> copyData2(source: kotlin.collections.MutableList<T>, destination: kotlin.collections.MutableList<R>) {
            for (item in source) {
                destination.add(item)
            }
        }

        val ints = mutableListOf(1, 2, 3)
        val anyItems = mutableListOf<Any>()
        copyData2(ints, anyItems)
        println(anyItems)// [1,2,3]


        // 在函数定义中给特定用途的类型参数加上变型修饰符

        // 代码清单9.16 带 out 投影类型参数的数据拷贝函数
        // 可以给类型的用法加上 out 关键字:没有使用那些 T 用在 in 位置的方法
        fun <T> copyData3(source: kotlin.collections.MutableList<out T>, destination: kotlin.collections.MutableList<T>) {
            for (item in source) {
                destination.add(item)
            }
        }

        // 加上 out 禁止调用 add。不要为使用投影类型后不能调用某些方法而吃惊
        val list: kotlin.collections.MutableList<out Number> = mutableListOf(1, 2, 3)
//        list.add(42)

        // 代码清单9.17 带 in 投影类型参数的数据拷贝函数
        // 允许目标元素的类型是来源元素类型的超类型
        fun <T> copyData4(source: kotlin.collections.MutableList<T>, destination: kotlin.collections.MutableList<in T>) {
            for (item in source) {
                destination.add(item)
            }
        }

        /*
        * 注意:
        * Kotlin的使用点变型直接对应Java的限界通配符。
        * Kotlin中的 MutableList<out T> 和 Java中的 MutableList<? extends T> 是一个意思。
        * in 投影的 MutableList<in T> 对应到Java的 MutableList<? super T>
        */

        /**-------------------- 9.3.6 星号投影:使用 * 代替类型参数 ----------------------*/
        // 注意:MutableList<*> 和 MutableList<Any?> 不一样
        val list2: MutableList<Any?> = mutableListOf("a", 1, "qwe")
        val chars = mutableListOf('a', 'b', 'c')
        val unknownElements: MutableList<*> = if (java.util.Random().nextBoolean()) list2 else chars

//        unknownElements.add(42)// 编译器禁止调用这个方法
        println(unknownElements.first())// 读取元素是安全的:first() 返回一个类型为 Any? 的元素
        // MutableList<*> 投影成了 MutableList<out Any?>


        /*可以实现一个接收 List<*> 做参数的 printFirst 函数:*/

        // 每一种列表都是可能的实参
        fun printFirst(list: List<*>) {
            // isNotEmpty() 没有使用泛型类型参数
            if (list.isNotEmpty()) {
                // first() 现在返回的是 Any?,但是这里足够了
                println(list.first())
            }
        }
        printFirst(listOf("jingbin", "jinbeen"))// jingbin

        /*在使用点变型的情况下,你有一个替代方案---引入一个泛型类型参数:*/
        // 再一次,每一种列表都是可能的实参
        fun <T> printFirst(list: List<T>) {
            if (list.isNotEmpty()) {
                // first() 现在返回的是T的值
                println(list.first())
            }
        }

        /*
        * 星号投影的语法很简洁,但只能用在对泛型类型实参的确切值不感兴趣的地方:
        * 只是使用生产值的地方,而且不关心那些值的类型。
        */

        // 代码清单9.18 输入验证的接口
        // 接口定义成在 T 上逆变
//        interface FieldValidator<in T> {
        // T 只在 in 位置使用(这个方法只是消费T的值)
//            fun validate(input: T): Boolean
//        }

//        object DefaultIntValidator : FieldValidator<Int> {
//            override fun validate(input: Int): Boolean = input >= 0
//        }

//        object DefaultStringValidator : FieldValidator<String> {
//            override fun validate(input: String): Boolean = input.isNotEmpty()
//        }


        val validators = mutableMapOf<KClass<*>, FieldValidator<*>>()
        validators[String::class] = DefaultStringValidator
        validators[Int::class] = DefaultIntValidator
        /*
        * 如果这样做了,尝试使用验证器的时候就会遇到困难。不能用类型为 FieldValidator<*> 的验证器来验证字符串。
        * 这是不安全的,因为编译器不知道它是哪种验证器:
        */
        // 存储在map中的值的类型是 FieldValidator<*>
//        validators[String::class]!!.validate("")

        // 代码清单9.19 使用显式的转换获取验证器
        val stringValidator = validators[String::class] as FieldValidator<String>// 警告:未受检的转换
        println(stringValidator.validate(""))// false


        // 代码清单9.20 错误地获取验证器
        val fieldValidator = validators[Int::class] as FieldValidator<String>
        // 代码可以编译,直到使用验证器时才发现真正的错误。
//        println(fieldValidator.validate(""))

        // 代码清单9.21 封装对验证器集合的访问
//        object Validators {
        // 使用和之前一样的map,但现在无法在外部访问它
//            private val validators = mutableMapOf<KClass<*>, FieldValidator<*>>()
//            fun <T : Any> registerValidator(kClass: KClass<T>, fieldValidator: FieldValidator<T>) {
//                validators[kClass] = fieldValidator
//            }

//            @Suppress("UNCHECKED_CAST")// 禁止关于未受检的转换到 FieldValidator<T>的警告
//            operator fun <T : Any> get(kClass: KClass<T>): FieldValidator<T> =
//                    validators[kClass] as? FieldValidator<T>
//                            ?: throw IllegalArgumentException("No validator for ${kClass.simpleName}")
//        }

        //  现在 get 方法返回的是 FieldValidator<String>的实例。会提示报错
//        println(Validators[String::class].validate(42))

总结

  • Kotlin 的泛型和 Java 相当接近:它们使用同样的方式声明泛型函数和泛型类。
  • 和 Java 样,泛型类型的类型实参只在编译期存在。
  • 不能把带类型实参的类型和 is 运算符一起使用 ,因为类型实参在运行时将被擦除。
  • 内联函数的类型参数可以标记成实化的,允许你在运行时对它们使用 is 检查,以及获得 java.lang.Class 实例。
  • 变型是一种说明两种拥有相同基础类型和不同类型参数的泛型类型之间子类型化关系的方式,它说明了如果其中一个泛型类型的类型参数是另一个的类型参数的子类型 这个泛型类型就是另外一个泛型类型的子类型或者超类型。
  • 可以声明一个类在某个类型参数上是协变的,如果该参数只是用在 out 位置。
  • 逆变的情况正好相反:可以声明一个类在某个类型参数上是逆变的,如果该参数只是用在 in 位置。
  • 在Kotlin 中的 只读接口 List 声明成了协变的,这 意味着 List<String> 是 List<Any> 的子类型。
  • 在函数接口声明成了在第一个类型参数上逆变而在第二个类型参数上协变,使(Animal) -> Int 成为( Cat )一> Number 的子类型。
  • 在Kotlin 中既可以为整个泛型类指定变型(声明点变型),也可以为泛型类型特定的使用指定变型(使用点变型)。
  • 当确切的类型实参是未知的或者不重要的时候,可以使用星号投影语法。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容

  • 写在开头:本人打算开始写一个Kotlin系列的教程,一是使自己记忆和理解的更加深刻,二是可以分享给同样想学习Kot...
    胡奚冰阅读 1,416评论 1 3
  • Kotlin 泛型 声明泛型类 和Java一样,Kotlin通过在类名称后面加上一对尖括号,并把类型参数放在尖括号...
    leilifengxingmw阅读 2,413评论 0 0
  • 参考: Kotlin 实战 Java 泛型推荐阅读:https://www.zhihu.com/question/...
    zhaoyubetter阅读 27,482评论 1 10
  • Kotlin 知识梳理系列文章 Kotlin 知识梳理(1) - Kotlin 基础Kotlin 知识梳理(2) ...
    泽毛阅读 2,463评论 0 4
  • 泛型类型参数 泛型允许你定义带类型形参的类型。当这种类型的实例被创建出来的时候,类型形参被替换成称为类型实参的具体...
    张可_阅读 538评论 1 0