多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] ## 泛型的分类 由于泛型可以体现在类、接口以及方法中,因此可以将泛型分为3种类型,分别是泛型类、泛型接口以及泛型方法。本节我们将对这3种类型进行详细讲解。 ### 泛型类 #### 泛型类的定义 **使用泛型标记的类,被称为泛型类**。 泛型类的使用分为两种情况,一种是泛型类**被用于实例化**,另一种是泛型类**被用于继承**。 当泛型类用于实例化时,需要传递具体的类型实参。 ##### (1)泛型类被用于实例化 ``` val list = ArrayList<String>() //String类型为泛型实参 val map = HashMap<String, Int>() //String、Int 为泛型实参 val set = HashSet<Long>() //Long 为泛型实参 ``` ##### (2)泛型类被用于继承 当泛型类被用于继承时,需要为泛型形参提供一个具体类型或者另一个类型的形参。具体示例代码如下: ``` class ArrayList<E> : AbstractList<E>(), List<E>, java.io.Serializable { override val size: Int = 0 override fun get(index: Int): E { TODO("not implemented") } } ``` 上述代码中,ArrayList<E>表示定义了一个泛型类,其中<E>表示声明了一个泛型形参,具体的实参类型在ArrayList被使用时决定。代码中的AbstractList<E>、List<E>两个泛型类是在ArrayList<E>泛型类中被使用,因此需要给它们传递一个具体的类型。由于传递的具体类型暂不确定,但是可以明确AbstractList<E>、List<E>需要的类型实参和ArrayList<E>需要的类型实参一致,因此AbstractList<E>、List<E>两个泛型类接收的类型是当前ArrayList<E>这个泛型类的类型形参。 #### 自定义泛型类 除了使用系统提供的泛型类之外,还可以自定义泛型类,Kotlin中自定义泛型类定义的格式如下所示: ``` [访问修饰符]class 类名<泛型符号1,泛型符号2,…>{ 泛型符号1 泛型成员1; 泛型符号2 泛型成员2; } ``` 上述格式中,泛型声明在类名之后, * 泛型的符号**一般使用大写字母**,如<T>、<E>、<K>、<V>等,泛型符号可以是**满足Kotlin命名规则的任意字符,甚至可以是某一个单词**,如<TYPE> * **一个类可以声明多个泛型,只要使用不同的泛型符号即可**,如{TODO}。 接下来我们通过一个案例来自定义一个泛型类,首先在IDEA中创建一个名为Chapter07的项目,包名指定为com.itheima.chapter07,该包用于存放后续案例中创建的文件,接着在该包中创建一个GenericsClass.kt文件,在该文件中自定义一个泛型类Box。具体代码如下所示。 ``` class Box<T> { var t: T? = null fun add(t: T): Unit { this.t = t; } fun get(): T? { return t } } data class Apple(val name: String) //Apple数据类 fun main(args: Array<String>) { val box = Box<Apple>() box.add(Apple("红富士苹果")) val apple = box.get() println(apple.toString()) } ``` 运行结果: ``` Apple(name=红富士苹果) ``` 上述代码中,自定义了一个泛型类Box与数据类Apple。在泛型类Box中,创建了一个类型为T的变量t,接着创建了add()方法与get()方法,分别用于设置和获取变量t的值。在数据类Apple的主构造函数中传递了一个String类型的变量name,在main()函数中,创建泛型类Box的实例对象,接着调用add()方法将泛型类Apple的实例传递到该方法中,最后通过get()方法获取变量t的值并打印。 ### 泛型接口 **使用泛型标记的接口,被称为泛型接口**。 泛型接口的使用分为两种情况, * 一种情况是泛型接口被实现时可以确定泛型接口对应的实参,直接传递实参即可; * 另一种情况是泛型接口被实现时不能确定泛型接口对应的实参,则需要使用当前类或者接口的泛型形参。 接下来我们针对这两种情况进行讲解。 * 情况一:泛型接口被实现时能够确定泛型接口对应的实参,直接传递实参即可。具体代码如下: ``` interface List<String> : Collection<String>{} ``` * 情况二:泛型接口被实现时不能够确定泛型接口对应的实参,则需要使用当前类或者接口的泛型形参。具体代码如下: ``` interface List<E> : Collection<E>{} ``` 上述代码中,`List<E>`属于定义了一个泛型接口。`List<E>`这里的`<E>`属于声明了一个泛型形参,具体是什么类型实参,由List被使用的时候决定。`List<E>`继承自`Collection<E>`,使用`List<E>`时,无论传递的是什么类型参数,`Collection<E>`的类型实参和`List<E>`的类型实参都是一致的。 ### 泛型方法 #### 泛型方法的定义 **使用泛型标记的方法,被称为泛型方法**。泛型方法在被调用时,只需传入具体的泛型实参即可,**Kotlin语言比较智能,在一些情况下,可以不用给具体的类型实参,程序会自动推断**。接下来我们先来看一下最简单的泛型方法main(),具体代码如下所示: ``` fun main(args: Array<String>) { //String类型为泛型实参,Kotlin自动推断 val list = arrayListOf("a","b","c") //String、Int 为泛型实参,Kotlin自动推断 val map = hashMapOf("a" to 1,"b" to 2) //Long 为泛型实参,Kotlin自动推断 val set = hashSetOf(1L,2L,3L) } ``` 上述代码中,定义了一个`<String>`类型的泛型方法,当创建具体的泛型实参时,IDEA编辑器会自动推断泛型实参的类型,然后在变量名称后通过“:+参数类型”的方式添加标识,如图所示。 ![](https://img.kancloud.cn/24/1e/241e0acaf26eeb7f039d43c2ea184cf4_740x202.png) :-: 自动判断泛型类型 在图中,**用方框标识出来的部分是编译器根据具体的泛型实参自动推断出的泛型实参的类型**。 #### 高阶函数中的泛型方法 在高阶函数中,自定义了很多泛型方法,具体代码如下所示。 ``` fun main(args: Array<String>) { val letters = ('a'..'z').toList() println(letters.slice<Char>(0..2)) //调用泛型方法,显示地指定类型实参 println(letters.slice(10..13)) //调用泛型方法,编译器推导出T是Char } ``` 运行结果: ``` [a, b, c] [k, l, m, n] ``` #### 自定义泛型方法 除了使用系统提供的泛型方法,还可以**自定义泛型方法**,只需要**把握自定义泛型方法的格式即可**。那么,Kotlin中自定义泛型方法的格式是怎样的呢?下面就来看一下自定义泛型方法的格式,具体如下: ``` 修饰符fun <泛型符号> 方法名(方法参数): 方法返回值 { … } ``` 根据上述自定义泛型方法的格式,接下来我们来自定义一个泛型方法,具体代码如下所示。 ``` fun <T> printInfo(content: T) { when (content) { is Int -> println("传入的$content,是一个Int类型") is String -> println("传入的$content,是一个String类型") else -> println("传入的$content,不是Int也不是String") } } fun main(args: Array<String>) { printInfo(10) printInfo("hello world") printInfo(true) } ``` 运行结果: ``` 传入的10,是一个Int类型 传入的hello world,是一个String类型 传入的ture,不是Int也不是String ```