Kotlin学习历程六:kotlin中的类之枚举类、接口类和数据类

一、枚举类 Enum

1.1 声明

关键字:enum

声明格式:

enum class 类名{
    ...
}

1.2 枚举常量

即枚举类下的对象,每个枚举类有0到多个枚举常量。

枚举类中的每一个枚举常量都是一个对象,并且他们之间用逗号分隔。

/**
 * 例:关于一个网络请求结果的枚举类
 */
enum class State{
    /*
         NORMAL : 正常
         NO_DATA : 数据为空
         NO_INTERNET : 网络未连接
         ERROR : 错误
         OTHER : 其他
     */

    NORMAL,NO_DATA,NO_INTERNET,ERROR,OTHER
}

不需要实例化枚举类就可以访问枚举常量。使用方式:枚举类名.枚举常量.属性

// 使用中缀符号访问枚举常量
State.NORMAL.name
State.NO_DATA.name
State.NO_INTERNET.name

因为每一个枚举都是枚举类的实例,所以他们可以是初始化过的。

 enum class Color(var argb : Int){
     RED(0xFF0000),
     WHITE(0xFFFFFF),
     BLACK(0x000000),
     GREEN(0x00FF00)
}

1.3 枚举常量的匿名类

要实现枚举常量的匿名类,则必须提供一个抽象方法(必须重写的方法)。且该方法定义在枚举类内部。而且必须在枚举变量的后面。

枚举变量之间使用“,”分割开。但是最后一个枚举变量必须使用分号结束。不然定义不了抽象方法。

fun main(args: Array<String>) {
    ConsoleColor.BLACK.print()
}

enum class ConsoleColor(var argb : Int){
    RED(0xFF0000){
        override fun print() {
            println("我是枚举常量 RED ")
        }
    },
    WHITE(0xFFFFFF){
        override fun print() {
            println("我是枚举常量 WHITE ")
        }
    },
    BLACK(0x000000){
        override fun print() {
            println("我是枚举常量 BLACK ")
        }
   },
    GREEN(0x00FF00){
        override fun print() {
            println("我是枚举常量 GREEN ")
        }
    };

    abstract fun print()
}

输出结果为:
我是枚举常量 BLACK 

1.4 枚举类的使用

  • 每个枚举常量都包含两个属性:name(枚举常量名)和ordinal(枚举常量位置)
  • 提供了values()和valueOf()方法来检测指定的名称与枚举类中定义的任何枚举常量是否匹配。
  • 自 Kotlin 1.1起,可以使用 enumValues<T>()和 enumValueOf<T>()函数以泛型的方式访问枚举类中的常量。

 

二、接口类 Interface

2.1 声明

关键字:interface

声明格式:

interface 接口名{ 
    ...
}

2.2 用法

关键字:冒号(:),这一点是和Java不同的。Java中使用接口使用的是implements关键字

在Kotlin中冒号(:)使用的地方很多:

  用于变量的定义
  用于继承
  用于接口
  方法的返回类型声明

使用格式:

class 类名 : 接口名{ 
    // 重写的接口函数、属性等 
    ... 
}

fun main(args: Array<String>) {

   // 类的初始化
   var demo = Demo1()

   demo.fun1()
}

/**
 * 我定义的接口
 */
interface Demo1Interface{

    // 定义的方法
    fun fun1()
}

/**
 * 接口的实现类
 */
class Demo1 : Demo1Interface{
    override fun fun1() {
        println("我是接口中的fun1方法")
    }
}

输出结果为:
我是接口中的fun1方法

2.3 接口中方法的使用

不带结构体的函数可以省略大括号,且不用强制重写带结构体的函数就可以直接调用。

fun main(args: Array<String>) {
    var demo = Demo2()

    demo.fun1()
    demo.fun2(5)
    println(demo.fun3(10))
    println(demo.fun4())

    //可以不重写该方法直接调用
    demo.fun5()
}

interface Demo2Interface{

    /**
     * 定义一个无参数无返回值的方法
     */
    fun fun1()

    /**
     * 定义一个有参数的方法
     */
    fun fun2(num: Int)

    /**
     * 定义一个有参数有返回值的方法
     */
    fun fun3(num: Int) : Int

    // 下面的两个方法是有结构体, 故可以不重写

    /**
     * 定义一个无参数有返回值的方法
     */
    fun fun4() : String{
        return "fun4"
    }

    /**
     * 定义一个无结构体函数,大括号是可以省略的
     */
    fun fun5(){
        // 如果函数中不存在表达式,大括号可以省略。
        // 如fun1一样
    }
}

class Demo2 : Demo2Interface{

    override fun fun1() {
        println("我是fun1()方法")
    }

    override fun fun2(num: Int) {
        println("我是fun2()方法,我的参数是$num")
    }

    override fun fun3(num: Int): Int {
        println("我是fun3()方法,我的参数是$num,并且返回一个Int类型的值")
        return num + 3
    }

    override fun fun4(): String {
        println("我是fun4()方法,并且返回一个String类型的值")
   
        /*
            接口中的fun4()方法默认返回”fun4“字符串.
            可以用super.fun4()返回默认值
            也可以不用super关键字,自己返回一个字符串
        */
        return super.fun4()
    }

    /*
         接口中的fun5()带有结构体,故而可以不用重写,
         fun4()同样
    */

    //    override fun fun5() {
    //        super.fun5()
    //    }
}

输出结果为:

我是fun1()方法
我是fun2()方法,我的参数是5
我是fun3()方法,我的参数是10,并且返回一个Int类型的值
13
我是fun4()方法,并且返回一个String类型的值
fun4

2.4 接口中属性的使用

在接口中声明属性。接口中的属性要么是抽象的,要么提供访问器的实现。接口属性不可以有后备字段。而且访问器不可以引用它们。

2.4.1 作为抽象

即重写属性的时候是在实现类的类参数中。

fun main(args: Array<String>) {
    var demo = Demo3(1,2)
    println(demo.sum())
}

interface Demo3Interface{

    val num1: Int

    val num2 : Int  
}

class Demo3(override val num1: Int, override val num2: Int) : Demo3Interface{
    fun sum() : Int{
        return num1 + num2
    }
}

输出结果为:
3

2.4.2 作为访问器

即手动方式去实现重写,并提供get()方法

fun main(args: Array<String>) {
    println(demo.result())

    // 在这里也可以改变接口属性的值
    demo.num4 = 10
    println(demo.result())
}

interface Demo3Interface{

     // 声明比那俩和提供默认值
     // 注意: val num3: Int = 3  这种方式不提供,为直接报错的
    val num3: Int
    get() = 3

    val num4: Int
}

class Demo3(override val num1: Int, override val num2: Int) : Demo3Interface{

    // 提供访问器实现
    override val num3: Int
        get() = super.num3

    // 手动赋值
    override var num4: Int = 4

    fun result() : Int{
        return num3 + num4
    }
}

输出结果为:
7
13

2.5 接口的冲突问题

该问题是指当我们在父类中声明了许多类型,有可能出现一个方法的多种实现。

fun main(args: Array<String>) {

    // 类的初始化
    val demo = Demo4()

    demo.fun1()
    demo.fun2()
}

interface Demo4InterfaceOne{
    fun fun1(){
        println("我是Demo4InterfaceOne中的fun1()")
    }

    fun fun2(){
        println("我是Demo4InterfaceOne中的fun2()")
    }
}

interface Demo4InterfaceTwo{
    fun fun1(){
        println("我是Demo4InterfaceTwo中的fun1()")
    }

    fun fun2(){
        println("我是Demo4InterfaceTwo中的fun2()")
    }
}

class Demo4 : Demo4InterfaceOne,Demo4InterfaceTwo{

    override fun fun1() {
        super<Demo4InterfaceOne>.fun1()
        super<Demo4InterfaceTwo>.fun1()
    }

    override fun fun2() {
        super<Demo4InterfaceOne>.fun2()
        super<Demo4InterfaceTwo>.fun2()
    }

}

说明:Demo4实现了Demo4InterfaceOne和Demo4InterfaceTwo两个接口,而两个接口中都存在两个相同方法名的方法。因此编译器不知道应该选哪个,故而我们用super<接口名>.方法名来区分。

三、数据类 data

3.1 声明

关键字:data

声明格式:

data class 类名(var param1 :数据类型,...){}

或者

data class 类名 可见性修饰符 constructor(var param1 : 数据类型 = 默认值,...)

说明:

  • data为声明数据类的关键字,必须书写在class关键字之前。
  • 在没有结构体的时候,大括号{}可省略。
  • 构造函数中必须存在至少一个参数,并且必须使用val或var修饰。
  • 参数的默认值可有可无。(若要实例一个无参数的数据类,则就要用到默认值)

注意:数据类也有其约定俗成的一些规定,这只是为增加代码的阅读性。即当构造函数中的参过多时,为了代码的阅读性,一个参数的定义占据一行。

 

当我们声明一个数据类时,编辑器自动为这个类做了一些事情,不然它怎么又比Java简洁呢。它会根据主构造函数中所定义的所有属性自动生成下列方法:

  • 生成equals()函数与hasCode()函数
  • 生成toString()函数,由类名(参数1 = 值1,参数2 = 值2,....)构成
  • 由所定义的属性自动生成component1()、component2()、...、componentN()函数,其对应于属性的声明顺序。
  • copy()函数。在下面会实例讲解它的作用。

其中,当这些函数中的任何一个在类体中显式定义或继承自其基类型,则不会生成该函数

 

3.2 数据类的特性

数据类有着和Kotlin其他类不一样的特性。除了含有其他类的一些特性外,还有着其独特的特点。并且也是数据类必须满足的条件:

  • 主构造函数需要至少有一个参数
  • 主构造函数的所有参数需要标记为 val 或 var;
  • 数据类不能是抽象、开放、密封或者内部的;
  • 数据类是可以实现接口的,如(序列化接口),同时也是可以继承其他类的,如继承自一个密封类。

3.3 和java对比

1.数据类的对比

Kotlin版:

data class User(val name : String, val pwd : String)

Java版:

public class User {
    private String name;
    private String pwd;

    public User(){}

    public User(String name, String pwd) {
        this.name = name;
        this.pwd = pwd;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

分析:实现同一个功能,从代码量来说,Koltin比Java少了很多行代码,比起更简洁。

 

2.修改数据类属性

Kotlin版:

Koltin要修改数据类的属性,则使用其独有的copy()函数。其作用就是:修改部分属性,但是保持其他不变

val mUser = User("kotlin","123456")
println(mUser)
val mNewUser = mUser.copy(name = "new Kotlin")
println(mNewUser)

输出结果为:
User(name=kotlin, pwd=123456)
User(name=new Kotlin, pwd=123456)

Java版:

User mUser = new User("Java","123456");
System.out.println(mUser);
mUser.setName("new Java");
System.out.println(mUser);    

输出结果为:
User{name='Java', pwd='123456'}
User{name='new Java', pwd='123456'}

分析:从上面对两种方式的实现中可以看出,Kotlin是使用其独有的copy()函数去修改属性值,而Java是使用setXXX()去修改

 

3.4 解析声明

在前面讲到,Kotlin中定义一个数据类,则系统会默认自动根据参数的个数生成component1() ... componentN()函数。其...,componentN()函数就是用于解构声明的

val mUser = User("kotlin","123456")
val (name,pwd) = mUser
println("name = $name\tpwd = $pwd")

输出结果为:
name = kotlin   pwd = 123456

 

3.5 系统标准库中的标准数据类

标准库提供了 Pair Triple。尽管在很多情况下命名数据类是更好的设计选择, 因为它们通过为属性提供有意义的名称使代码更具可读性。

其实这两个类的源码部分不多,故而贴出这个类的源代码来分析分析

1.源码分析

@file:kotlin.jvm.JvmName("TuplesKt")
package kotlin

// 这里去掉了源码中的注释
public data class Pair<out A, out B>(
        public val first: A,
        public val second: B) : Serializable {
    
    // toString()方法
    public override fun toString(): String = "($first, $second)"
}

// 转换
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

// 转换成List集合
public fun <T> Pair<T, T>.toList(): List<T> = listOf(first, second)

// 这里去掉了源码中的注释
public data class Triple<out A, out B, out C>(
        public val first: A,
        public val second: B,
        public val third: C ) : Serializable {

    // toString()方法
    public override fun toString(): String = "($first, $second, $third)"
}

// 转换成List集合
public fun <T> Triple<T, T, T>.toList(): List<T> = listOf(first, second, third)

分析:从上面的源码可以看出,标准库中提供了两个标准的数据类,Pair类以及Triple类.其中:

  • 两个类中都实现了toList()方法以及toString()方法。
  • to()方法乃Pair类特有,起作用是参数转换
  • Pair类需要传递两个参数,Triple类需要传递三个参数。

 

2.用法

val pair = Pair(1,2)        // 实例
val triple = Triple(1,2,3)  // 实例
println("$pair \t $triple") // 打印:即调用了各自的toString()方法
println(pair.toList())      // 转换成List集合
println(triple.toList())    // 转换成List集合
println(pair.to(3))         // Pair类特有: 其作用是把参数Pair类中的第二个参数替换

输出结果为:
(1, 2)   (1, 2, 3)
[1, 2]
[1, 2, 3]
((1, 2), 3)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值