侧边栏壁纸
博主头像
Z同学博主等级

工作磨平激情前,坚持技术的热忱。 欢迎光临Z同学的技术小站。 分享最新的互联网知识。

  • 累计撰写 283 篇文章
  • 累计创建 55 个标签
  • 累计收到 93 条评论

Kotlin学习笔记-数据类,密封类,泛型和枚举

Z同学
2021-07-17 / 0 评论 / 0 点赞 / 516 阅读 / 8,528 字
温馨提示:
本文最后更新于 2021-12-03,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

数据类(关键字:data)

介绍:在Kotlin之中可以创建一个只包含数据的类,关键字为:data

实例1:

data class User(val name:String ,val age:Int){

}

数据类会自动生成以下几个通用函数。 当然是在编译器编译时自动生成的。

  • equals()
  • hashCode()
  • toString() : 按照上面的例子来说,格式会自动生成“User(name=xxx,age=xxx)”
  • componentN() 这个N会替代数字,按下面例子可以参考
  • copy()

实例2:

data class User(var name: String, var age: Int) {

}

fun main(array: Array<String>) {
    var user =User("Zinyan",18)
    //根据构造器之中的顺序和数量自动生成对应的componentN
    user.component1()  //返回 Zinyan
    user.component2()  //返回 18

    println(user.toString())
}

输出:

User(name=Zinyan, age=18)

注意:为了确保数据类生成代码的一致性。数据类需要满足以下条件

  • 主构造器至少包含一个参数
  • 所有主构造函数的参数必须标识 val或者var 代表该参数属于类变量
  • 数据类不可以声明为abstractopensealedinner
  • 数据类不能继承其他类,但是可以继承接口对象。

实例3:

interface  fs{
}

data class User(var name: String, var age: Int):fs {

}

复制

介绍:复制一个数据类的数据时,我们应该使用copy()函数。所有数据类对象都有一个自动生成的copy()函数

我们可以在复制的过程之中,修改指定参数的值。

实例1:

data class User(var name: String, var age: Int)  {
}


fun main(array: Array<String>) {
    val user1 = User(name = "zinyan", age = 12)
    val user2 = user1.copy(age = 18)
    println(user1.toString())
    println(user2.toString())
}

输出:

User(name=zinyan, age=12)
User(name=zinyan, age=18)

那么我们在复制之后,修改了user1会影响user2的结果么?

实例2:

data class User(var name: String, var age: Int)  {
}


fun main(array: Array<String>) {
    val user1 = User(name = "zinyan", age = 12)
    val user2 = user1.copy(age = 18)
    println(user1.toString())
    user1.name="Z同学"
    println(user2.toString())
}

输出:

User(name=zinyan, age=12)
User(name=zinyan, age=18)

结果没有变化,说明我们的copy()是数据复制。而不是引用复制。

解构声明

介绍:数据类对象的属性,允许在解构声明之中使用。

实例:

data class User(var name: String, var age: Int) {
}

fun main(array: Array<String>) {
    val user1 = User(name = "zinyan", age = 12)
    val (name, age) = user1
    println("打印这两个参数值:$name,$age ")
}

输出:

打印这两个参数值:zinyan,12 

标准数据类

介绍:Kotlin给我们提供了两个标准的数据类。我们也可以直接使用这两个类。

它们分别是: PairTriple 。 Pair是一个二元数据类,Triple 是一个三元数据类。

实例1:

var pair1 = Pair(1, "2")
pair1.first// 得到第一项参数
pair1.second //得到第二项的参数
var pai2 = Triple(1, "2", User(name = "zinyan", age = 12))
pai2.first  //得到第一项的参数
pai2.second //得到第二项的参数
pai2.third //得到第三项的参数

注意:Pair 和Triple 的属性是泛型,也就是说可以存储任何对象进去。

实例2:

fun main(array: Array<String>) {
    val user1 = User(name = "zinyan", age = 12)
    println(user1)
    var s = mapOf("name" to "Zinyan","age" to 12)
    println(s)
}

输出:

User(name=zinyan, age=12)
{name=Zinyan, age=12}

我们可以通过mapOf() 创建 一元,二元,三元。最多能创建三元对象。因为Triple

实例3:

var s = mapOf("name" to "Zinyan","age" to 12,"six" to "男")
println(s)

密封类(关键字:sealed)

介绍:密封类用来表示受限的类继承结构。关键字 sealed修饰。当一个值为有限几种的类型而不能有任何其他类型时表示密封。

枚举对象也是值的集合,但是每一个值只有一个实例。而密封类的值可以有多个实例。

密封类可以有子类,被继承。但是所有的子类都必须是密封类的嵌套类。

注意:密封类不能使用interface,abstract进行修饰。

实例1:

//密封类
sealed class  Expr{

}

其实,我们大部分密封类都是配合When语句一起使用。

实例1:

//密封类
sealed class Expr {
    object Show : Expr()
    object Hide : Expr()
}

fun execute(op: Expr) = when (op) {
    Expr.Show -> println("Show 显示")
    Expr.Hide -> println("Hide 隐藏")
}


fun main(array: Array<String>) {
    var expr:Expr = Expr.Show
    execute(expr)
}

输出:

Show 显示

密封类,实现子类扩展:

实例2:

//密封类
sealed class Expr {
    object Show : Expr()
    object Hide : Expr()

    //普通嵌套类
    class Exxx1(val name: String) : Expr()

    //数据类嵌套类
    data class Exxx2(val age: Int) : Expr()
}

fun execute(op: Expr) = when (op) {
    Expr.Show -> println("Show 显示")
    Expr.Hide -> println("Hide 隐藏")
    is Expr.Exxx1 -> println(op.name)
    is Expr.Exxx2 -> println(op.age)
}


fun main(array: Array<String>) {
    var expr: Expr = Expr.Show
    execute(expr)
    var e1  = Expr.Exxx1("Z同学")
    var e2  = Expr.Exxx2(100)
    execute(e1)
    execute(e2)
}

输出:

Show 显示
Z同学
100

密封类里面 封装的类,这种实现思路是枚举等不容易实现的。

泛型

介绍:Kotlin泛型的概念和java之中的概念是一样的。都是将数据类型参数化。可以不同固定参数类。用在类,接口,函数等传参上。

其实泛型主要就是为了消除类型的强制转换的烦恼。 和java之中一样也是用<T>指带

类传参泛型

实例:

class A<T>(t: T) {
    var value = t
}

fun main(array: Array<String>) {
    var ss1 = A("Zinyan")
    println(ss1.value)
    var ss2 = A(21)
    println(ss2.value)
}

输出:

Zinyan
21

函数传参泛型

介绍:Kotlin之中泛型函数的声明和java相同。类型参数要放在函数名的前面

实例1:

class A<T>(t: T) {
    var value = t
}
/**
 * 定义一个泛型传参,返回一个泛型参数
 */
fun <T> fanxin(value: T): A<T> {
    return A(value)
}
//上一个方法的简写
fun <T> fanxin1(value: T) = A(value)

fun main(array: Array<String>) {
    var ff1 = fanxin1("Z同学")
    println(ff1.value)
    var ff2 = fanxin("Zinyan")
    println(ff2.value)
}

输出:

Z同学
Zinyan

加深印象,在When之中使用泛型

实例2:

fun <T> doPrintln(value: T) {
    when (value) {
        is Int -> println("这是一个整数$value")
        is String -> println("这是一个字符串$value")
        is Long -> println("这是一个Long 长整数$value")
        else -> println("这个数据格式我没判断 $value")
    }
}

fun main(array: Array<String>) {
    doPrintln("Zinyan")
    doPrintln(10)
    doPrintln(10L)
    doPrintln(A("Zinyan"))
}

输出:

这是一个字符串Zinyan
这是一个整数10
这是一个Long 长整数10
这个数据格式我没判断 cn.zinyan.demo.kotlin.A@6e0be858

泛型约束

介绍:可以使用泛型约束来设定一个给定参数的允许使用的类型。

Kotlin之中使用 : 符号对泛型类型上限进行约束。

实例1:

fun <T : Comparable<T>> sort(list: List<T>) {
    
}

针对泛型T 的范围进行了约束

型变

介绍:Kotlin之中没有通配符类型。而是其他两个变型。

分别为:声明处型变(declaration-site variance)

​ 类型投射(type projections)

声明处型变

介绍:声明处的类型变异,使用协变注解修饰符:inout

其中:int 指消费者。 out 指生产者。

in 关键字

介绍:in会使得一个类型参数逆变,逆变类型参数只能用作输入,可以作为入参的类型,但是不能作为返回值的类型。

实例:

class Zinyan<in A>(a: A) {
    fun test(a: A) {
    }
}

fun main(array: Array<String>) {
    var strD = Zinyan("a")
    var strE = Zinyan<Any>("b")
    strD = strE
}
out 关键字

介绍:out 会使得一个类型参数协变。协变类型参数只能作为输出。可以作为返回值类型但是无法作为入参类型。

和in是相反的。

实例:

class Zinyan1<out A>(val a: A) {

    //返回a的参数
    fun test(): A {
        return a
    }
}

fun main(array: Array<String>) {
    var s= Zinyan1("A")
    var any = Zinyan1<Any>("B")
    any = s
    println(any.test())
}

输出:

A

总结: 主要就是申明泛型的时候,如果入参和出参想规范。定义入参不能作为出参。那就在入参添加 in 出参添加 out

数据类Pair 就是将两个入参作为out 声明。我们可以随意定义两个入参类型。

了解一下就可以。

类型投射

介绍:如果你想表示你并不知道类型参数的任何信息,但是任然希望能够安全的使用它。这个将会对泛型类型定义一个类型投射。要求这个泛型类型的所有实体实例,都是这个投射的子类型。

Kotlin提供了一种语法。该语法称为 星号投射(start-projection):

概念:

  • 假如类型定义为 Foo<out T>: T是一个协变的类型参数,范围约束为TUpper,Foo<> 等价于Foo<out TUpper>. 表示:当前T未知时,你可以安全的从Foo<>获取TUpper类型的值。
  • 假如类型定义为Foo<int T>: T是一个逆变的类型参数,Foo<>等价于Foo<in Nothing>.表示:当前T未知时,你不能安全的向Foo<>写入任何东西。
  • 假如类型定义为Foo<T>,T是一个协变的类型参数,范围约束为TUpper。对于读取值的场合,Foo<*>等价于Foo<out Tupper> 。对于写入值的场合,等价于Foo<in Nothing>

如果定义泛型为: interface Funcation<in T,out U>。那么可以出现以下几种星号投射:

1. Funcation<*,String>,代表Function<in Nothing,String>;
2. Funcation<Int,*>,代表Funcation<Int,out Any?>;
3. Funcation<*,*>,代表Funcation<in Nothing,out Any?>;

注意:星号投射与java的原生类型非常相似,但是在Kotlin中可以安全使用。

实例:

//定义了一个XX 类,传参为泛型,三个常量
class XX<T>(val t1: T, val t2: T, val t3: T)

//定义了一个Apple类,传参为String类型。一个变量
class Apple(var name: String)


fun main(array: Array<String>) {
    val a1: XX<*> = XX(12, "String", Apple("苹果"))
    val a2: XX<Any?> = XX(12, "String", Apple("苹果"))
    //a1 和a2 的实现是一样的。

    val apple = a1.t3 //参数类型为Any
    println(apple.toString()) //因为是Any 类型所以没有name 参数

    val apple2 = apple as Apple //类型转换,强转成Apple类
    println(apple2.name)

    //数组
    val temp: ArrayList<*> = arrayListOf("String", 1, 1.2f, Apple("苹果"))
    for (item in temp) {
        println(item)
    }

}

输出:

cn.zinyan.demo.kotlin.Apple@6e0be858
苹果
String
1
1.2
cn.zinyan.demo.kotlin.Apple@2626b418

所以,如果对这个投射还是不太能理解的话,可以将*理解成指代了所有的类型,相当于Any?。 可以是Null的Any类。

枚举(关键字:menu)

介绍:枚举最基本的使用方法就是实现一个类型安全的枚举。

枚举常量用逗号,分割,每个枚举常量都是一个对象。

前面有简单提及到枚举,这篇将详细介绍枚举。

创建

实例1:

//定义了一个枚举类,Color
enum class Color {
    RED, BLACK, BLUE, GREEN, WHITE
}

我们还可以针对常量进行初始化赋值。

实例2:

enum class Color1(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

两个枚举对象输出以下

实例3:

fun main(array: Array<String>) {
    //默认值
    println(Color.RED)
    println(Color.BLACK)
    println(Color.BLUE)
    //赋值后
    println(Color1.RED.rgb)
    println(Color1.GREEN.rgb)
    println(Color1.BLUE.rgb)
}

输出:

RED
BLACK
BLUE
16711680
65280
255

枚举类还支持以声明自己的匿名类及相关方法,以及覆盖基类的方法。

实例4:

enum class State {
    WAITING {
        override fun signal() = TALKING
    },
    TALKING {
        override fun signal() = WAITING
    };

    abstract fun signal(): State

}

fun main(array: Array<String>) {
     println(State.WAITING.signal())
}

输出:

TALKING

如果枚举类定义任何成员,要使用分号将成员定义中的枚举常亮定义分隔。

也即是说,我们在枚举类中写其他函数的时候,要在最后一个枚举对象的后面加上;。 上面的例子也体现了这点

使用

介绍:Kotlin之中,枚举类具有合成方法,允许遍历定义的枚举常亮,并通过其名称获取枚举常数。

有两个基本常量:

  • val name:String //获取枚举名称
  • val ordial:Int //获取枚举值在所有枚举数组中定义的顺序

有两个常用函数:

  • values() //获取枚举对象
  • valueOf() //通过枚举名称得到枚举对象。

实例1:

//定义了一个枚举类,Color
enum class Color {
    RED, BLACK, BLUE, GREEN, WHITE
}

fun main(array: Array<String>) {
    var temp: Color = Color.RED
    println(Color.values())
    println(Color.valueOf("RED"))
    println(temp.name)
    println(temp.ordinal)
}

输出:

[Lcn.zinyan.demo.kotlin.Color;@6e0be858
RED
RED
0

还可以使用 enumValues() 和enumValueOf()函数,以泛型的方式访问枚举类中的常量

实例2:

//定义了一个枚举类,Color
enum class Color {
    RED, BLACK, BLUE, GREEN, WHITE
}

inline fun <reified T : Enum<T>> printAllValues() {
    println(enumValues<T>().joinToString { it.name })
}

inline fun <reified T : Enum<T>> printAllValues(s: String) {
    println(enumValueOf<T>(s).name)
}

fun main(array: Array<String>) {
    printAllValues<Color>()
    printAllValues<Color>("RED")
}

输出:

RED, BLACK, BLUE, GREEN, WHITE	
RED	

附录
上面例子的Demo代码。
https://zinyan.com/upload/2021/07/User-3ffd0ddc5269483aaf27a945a6b6865b.kt

0

评论区