Kotlin——程序核心构成元素之包、类、接口以及对象基本语法(二)

来源:互联网 发布:淘宝买家怎么改差评 编辑:程序博客网 时间:2024/05/29 11:17

引言

前两篇Kotlin——程序核心构成元素之包、类、接口以及对象基本语法(一)介绍了可见性修饰符、包、类、接口和对象的基本语法,这一篇就具体总结下Kotlin中抽象类、数据类、密封类、嵌套类、枚举类、类的继承机制以及相关的知识(里面有些术语Kotlin中不一定有我是借鉴Java的)。

一、抽象类

总所周知,抽象是面向对象编程的特征之一,类本身或类中的部分成员都可以声明为抽象的且抽象成员在类中不存在具体的实现,抽象类也不能直接通过构造函数实例化。在Kotlin中也是用关键字abstract 标志为抽象类(无需对抽象类或抽象成员标注open注解,前一篇文章介绍了open 修饰符代表可被继承覆盖)

open class Base {    open fun f() {}}//抽象类Derived abstract class Derived : Base() {    override abstract fun f()}///Derived().f()  语法错误Cannot create an instance of an abstract class

二、嵌套类和内部类

所谓嵌套类就是把类嵌套在其他类内部内部类则是使用 inner 关键字来表示的嵌套类(在class 之前加上 inner 关键字)

1、嵌套类

  • 嵌套类没有自动持有外部类的引用,因而不能访问外部类的成员
  • 调用格式:外部类.嵌套类实例.嵌套类方法/属性
class Outer {                  // 外部类    private val name: String= "CrazyMo_"    class Nested {             // 嵌套类        fun foo() {println("Kotlin by Crazy.Mo")}        ///fun foo() {println(name)} 语法错误 Unresolved reference: name    }}/****/Outer.Nested().foo() // 调用格式:外部类.嵌套类实例.嵌套类方法/属性

2、内部类

内部类会自动持有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数

  • 为了避免歧义,可以使用this@label访问来自外部作用域的 this(其中 @label 是一个 代指 this 来源的标签)
class Outer {    private val name: String= "CrazyMo_"    var v = "外部类的成员属性"    /**内部类**/    inner class Inner {        fun foo() = bar  // 访问外部类成员        fun innerTest() {            var o = this@Outer //this@Outer获取外部类的成员变量            println("内部类可以引用外部类的成员" + o.v)        }    }}

还有一种匿名内部类机制,参见上一篇文章的中对象表达式和对象声明。

三、数据类

数据类是一种非常强大的类,它可以让你避免创建Java中的用于保存状态但又操作非常简单的POJO的模版代码。它们通常只提供了用于访问它们属性的简单的getter和setter。

1、数据类的定义

数据类的定义很简单,在class 关键字前使用data 关键字修饰即可,但还要满足以下的约束:

  • 主构造函数至少包含一个参数
  • 所有的主构造函数的参数必须标识为val 或者 var
  • 数据类不可以声明为 abstract, open, sealed 或者 inner
  • 数据类不能继承其他类 (密封类除外),但是可以实现接口
data class Person(val name: String, val phone: Long, val age: Int)

2、编译器自动从主构造函数中声明的所有属性生成的函数

  • equals()—— 它可以比较两个对象的属性来确保他们是相同的。
  • hashCode()——我们可以得到一个hash值,也是从属性中计算出来的。
  • copy()——你可以拷贝一个对象,可以根据你的需要去修改里面的属性。
  • 一系列可以映射对象到变量中的函数,如componentN() functions 对应于属性,按声明顺序排列
  • toString() 格式如 “Person(name=”CrazyMo_”,phone=1678986, age=1)”

但是如果这些函数在类中已经被明确定义了或者从父类中继承而来就不再会自动生成。

3、数据类的拷贝

在传统编程语言中,如果我们使用不可修改的对象,假设我们想修改这个对象状态,就必须要创建一个新的一个或者多个属性被修改的实例,在Kotlin里使用数据类拷贝就可以避免重新创建新的实例来实现。

//定义了一个数据类data class User(val name: String, val age: Int)//使用copy函数赋值并修改属性值val cmo = User(name = "CrazyMO_", age = 1)val cpy = cmo.copy(age = 2)println("原始值"+cmo)println("拷贝后的新值"+cpy)//运行结果原始值User(name=CrazyMO_, age=1)拷贝后的新值User(name=CrazyMO_, age=2)

4、数据类的解构声明

所谓解构声明即对象映射到变量,具体关于解构声明见后续文章。

val user = User(name = "CrazyMO_", age = 100)val (name, age) = userprintln(user.component1()+"$name, $age years of age") // prints "Jane, 35 years of age"

四、密封类

密封类用于限制类的继承关系,即密封类的子类数量是固定的(因为所有⼦类都必须在与密封类⾃⾝相同的⽂件中声明)。和枚举类类似,当你想在一个密封类的子类中寻找一个指定的类的时候,你可以事先知道所有的子类。不同之处在于枚举的实例是唯一的,而密封类可以有很多实例且可以有不同的状态。要声明⼀个密封类,需要在类名前⾯添加 sealed 修饰符。虽然密封类也可以有⼦类,但是所有⼦类都必须在与密封类⾃⾝相同的⽂件中声明,而且sealed 不能修饰 interface ,abstract class(会报 warning,但是不会出现编译错误)

sealed class Exprdata class Const(val number: Double) : Expr()data class Sum(val e1: Expr, val e2: Expr) : Expr()object NotANumber : Expr()fun eval(expr: Expr): Double = when (expr) {    is Const -> expr.number    is Sum -> eval(expr.e1) + eval(expr.e2)    NotANumber -> Double.NaN}

使用密封类在使用when 的时候有明显好处:如果能够验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了。

fun eval(expr: Expr): Double = when(expr) {    is Expr.Const -> expr.number    is Expr.Sum -> eval(expr.e1) + eval(expr.e2)    Expr.NotANumber -> Double.NaN    // 不再需要 `else` 子句,因为我们已经覆盖了所有的情况}

五、枚举类

枚举类最基本的用法是实现一个类型安全的枚举。枚举常量用逗号分隔,每个枚举常量都是一个对象,默认索引值从0开始且枚举根据它的顺序实现了 Comparable接口,所以可以很方便地把它们进行排序。其实枚举类中也是可以有自己的成员函数的,但是特别之处在于成员函数是相当于属于所有枚举变量共有的,得通过枚举变量来调用

1、枚举类的定义

//定义enum class Color{    RED,BLACK,BLUE,GREEN,WHITE}

枚举还可以带有参数也可以初始化

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

使用

//使用var color:Color=Color.BLUEprintln(Color.values())println(Color.valueOf("RED"))println(color.name)println(color.ordinal)//获取索引值//结果[LColor2;@54bedef2REDBLUE2

2、枚举匿名类

枚举还支持以声明自己的匿名类及相应的方法、以及覆盖基类的方法。如果枚举类定义任何成员,要使用分号将成员定义中的枚举常量定义分隔开

enum class ProtocolState {WAITING {override fun signal() = TALKING},TALKING {override fun signal() = WAITING};abstract fun signal(): ProtocolState}

3、泛型方式访问枚举

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

enum class RGB { RED, GREEN, BLUE }inline fun <reified T : Enum<T>> printAllValues() {    print(enumValues<T>().joinToString { it.name })}fun main(args: Array<String>) {    printAllValues<RGB>() // 输出 RED, GREEN, BLUE}
原创粉丝点击