kotlin入门>2-类与对象2

内容:

  • 数据类
  • 密封类
  • 嵌套类、内部类
  • 枚举类
  • 内联类

一、数据类

1、定义

  • 只保存数据的类,标记为data
    在这些类中,一些标准函数往往是从数据机械推导而来的。
    1
    data class User(val name: String, val age: Int)

1.1、要求:

  • 主构造函数 需要至少有一个参数;
  • 主构造函数 的所有参数需要标记为 val 或 var;
  • 数据类不能是抽象(abstract)、开放(open)、密封(sealed)或者内部(inner)的;
  • 数据类只能实现接口(interface)(在1.1之前)

1.2、导出成员:

编译器自动从主构造函数中声明的所有属性导出以下成员:

  • equals()/hashCode() 对;
  • toString() 格式是 "User(name=John, age=42)"
  • componentN() 函数 按声明顺序对应于所有属性;(数据类【只针对主构造函数内的属性】自动声明 componentN() 函数)
  • copy() 函数(见下文)。

1.3、成员生成遵循规则:

  • 如果在数据类体中有显式实现 equals()hashCode() 或者 toString(),或者这些函数在父类中有 final 实现,那么不会生成这些函数,而会使用现有函数;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    data class Person(var name: String, var age: Int) {
    var sex: Int = 0;
    override fun toString(): String {
    return "name=$name, age=$age, sex=$sex"
    }
    }

    data class Student(var name: String, var age: Int) {
    var sex: Int = 0;
    }

    fun foo() {
    var p = Person("zs", 23);
    p.sex = 1;
    println(p) //name=zs, age=23, sex=1

    var s = Student("ls", 21)
    s.sex = 3;
    println(s) //Student(name=ls, age=21)
    }

    fun main() {
    foo()
    }
  • 如果超类型具有 opencomponentN() 函数并且返回兼容的类型, 那么会为数据类生成相应的函数,并覆盖超类的实现。如果超类型的这些函数由于签名不兼容或者是 final 而导致无法覆盖,那么会报错;

  • 从一个已具 copy(……) 函数且签名匹配的类型派生一个数据类在 Kotlin 1.2 中已弃用,并且在 Kotlin 1.3 中已禁用

  • 不允许为 componentN() 以及 copy() 函数提供显式实现。

Tip:
在 JVM 中,如果生成的类需要含有一个无参的构造函数,则所有的属性必须指定默认值。

1
data class User(val name: String = "", val age: Int = 0)

2、属性

2.1、类体中声明属性

对于那些自动生成的函数(toString等),编译器 只使用在主构造函数内部定义的属性。如需在生成的实现中排出一个属性,请将其声明在类体中:

1
2
3
data class Person(val name: String) {
var age: Int = 0 //自动生成的函数中不存在
}

上述数据类中,在 toString()、 equals()、 hashCode() 以及 copy() 的实现中只会用到 `name 属性,并且只有一个 component 函数 component1()。虽然两个 Person 对象可以有不同的年龄,但它们会视为相等。

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val person1 = Person("John")
val person2 = Person("John")
person1.age = 10
person2.age = 20
println("person1 == person2: ${person1 == person2}")
println("person1 with age ${person1.age}: ${person1}")
println("person2 with age ${person2.age}: ${person2}")
//person1 == person2: true
//person1 with age 10: Person(name=John)
//person2 with age 20: Person(name=John)
}

2.2、复制 copy

  • copy 可以复制一个对象改变它的一些属性,但其余部分保持不变。

    1
    2
    3
    data class User(val name: String, val age: Int)
    //用法
    fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    fun main(agrs: Array<String>) {
    val u1 = User("zs", 23)
    println("u1 : $u1")
    val u2 = u1.copy()
    println("u2 拷贝u1为:: $u2")
    val u3 = u1.copy(name = "ls")
    println("u3 拷贝 u1 为 : $u3")
    }

    // u1 : User(name=zs, age=23)
    // u2 拷贝u1为:: User(name=zs, age=23)
    // u3 拷贝 u1 为 : User(name=ls, age=23)

2.3、数据类与解构声明

  • 解构声明:把一个对象 解构 成很多变量,并且可单独使用每个变量
    1
    2
    3
    4
    5
    6
    7
    8
    data class Person(var name: String, var age: Int)
    //解构
    val (name, age) = person

    //示例
    val jane = Person("Jane", 35)
    val (name, age) = jane
    println("$name, $age years of age") // 输出 "Jane, 35 years of age"

一个解构声明会被编译成以下代码:

1
2
val name = person.component1()
val age = person.component2()

其中的 component1() 和 component2() 函数是在 Kotlin 中广泛使用的 约定原则 的另一个例子。

二、密封类

1、定义

  • 修饰符:sealed

  • 密封类用来表示受限的类继承结构:当一个值为有限集中的类型、而不能有任何其他类型时。

  • 在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。

  • 密封类的构造函数在默认情况下是私有的,它也不能允许声明为非私有。

  • 密封类的子类都必须在与密封类自身相同的文件中声明。

    1
    2
    3
    4
    sealed class Expr
    data class Const(val number: Double) : Expr()
    data class Sum(val e1: Expr, val e2: Expr) : Expr()
    object NotANumber : Expr()
  • 一个密封类自身是抽象的,它不能直接实例化,并可以有抽象方法

    1
    2
    3
    4
    5
    sealed class MyClass
    fun main(args: Array<String>)
    {
    var myClass = MyClass() // 编译器错误,密封类型无法实例化。
    }
  • 扩展密封类子类的类(间接继承者)可以放在任何位置,而无需在同一个文件中。即继承 密封类的子类 的类,可以在其他文件中。

    1
    2
    3
    4
    5
    6
    sealed class Expr
    //和Expr在一个文件中
    data class Const(val number: Double) : Expr()

    //可以放在其他文件中
    data class Test(val number: Double, val name: Int) : Const(number)
  • 密封类通常与 when 表达式一起使用
    由于密封类的子类将自身类型作为一种情况。 因此,密封类中的when表达式涵盖所有情况,从而避免使用else子句。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    sealed class Shape{
    class Circle(var radius: Float): Shape()
    class Square(var length: Int): Shape()
    class Rectangle(var length: Int, var breadth: Int): Shape()
    // object NotAShape : Shape()
    }

    fun eval(e: Shape) =
    when (e) {
    is Shape.Circle ->println("Circle area is ${3.14*e.radius*e.radius}")
    is Shape.Square ->println("Square area is ${e.length*e.length}")
    is Shape.Rectangle ->println("Rectagle area is ${e.length*e.breadth}")
    //else -> "else case is not require as all case is covered above"
    // Shape.NotAShape ->Double.NaN
    }
    fun main(args: Array<String>) {

    var circle = Shape.Circle(5.0f)
    var square = Shape.Square(5)
    var rectangle = Shape.Rectangle(4,5)

    eval(circle) //Circle area is 78.5
    eval(square) //Square area is 25
    eval(rectangle) //Rectagle area is 20
    }

三、嵌套类、内部类

1、嵌套类

1
2
3
4
5
6
7
8
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}

val demo = Outer.Nested().foo() // == 2

2、内部类

  • 修饰符:inner
  • 能够访问外部类的成员
    1
    2
    3
    4
    5
    6
    7
    8
    class Outer {
    private val bar: Int = 1
    inner class Inner {
    fun foo() = bar
    }
    }

    val demo = Outer().Inner().foo() // == 1

3、匿名内部类

1
2
3
4
5
6
window.addMouseListener(object : MouseAdapter() {

override fun mouseClicked(e: MouseEvent) { …… }

override fun mouseEntered(e: MouseEvent) { …… }
})

对于 JVM 平台, 如果对象是函数式 Java 接口(即具有单个抽象方法的 Java 接口)的实例, 你可以使用带接口类型前缀的lambda表达式创建它:

1
2
3
4
5
6
7
var run = Runnable { println("run-----") }

var run1 = Runnable {
override fun run() {
println("run-----")
}
}

4、伴生对象

  • 1、声明:
    类内部的对象声明可以用 companion 关键字标记

    1
    2
    3
    4
    5
    6
    7
    8
    class MyClass {
    companion object Factory {
    fun create(): MyClass = MyClass()
    }
    }

    //该伴生对象(Factory)的成员(create函数)可通过只使用类名作为限定符来调用:
    val instance = MyClass.create()
  • 2、省略名称:
    可以省略伴生对象(如Factory)的名称,在这种情况下将使用名称 Companion

    1
    2
    3
    4
    5
    class MyClass {
    companion object { }
    }

    val x = MyClass.Companion

其自身所用的类的名称(不是另一个名称的限定符)可用作对该类的伴生对象 (无论是否命名)的引用:

1
2
3
4
5
6
7
8
9
10
11
class MyClass1 {
companion object Named { }
}

val x = MyClass1

class MyClass2 {
companion object { }
}

val y = MyClass2

注意:
请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员

实现接口:

1
2
3
4
5
6
7
8
9
10
11
interface Factory<T> {
fun create(): T
}

class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}

val f: Factory<MyClass> = MyClass

四、枚举类

  • 修饰符:enum
  • 枚举类的最基本的用法是实现类型安全的枚举
  • 每个枚举常量都是一个对象。枚举常量用逗号分隔。
1
2
3
enum class Direction {
NORTH, SOUTH, WEST, EAST
}

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

1
2
3
4
5
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
  • 覆盖基类的方法的匿名类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    enum class ProtocolState {
    WAITING {
    override fun signal() = TALKING
    },

    TALKING {
    override fun signal() = WAITING
    }; //使用分号(;)将成员定义中的枚举常量定义分隔开。与signal()分开

    abstract fun signal(): ProtocolState
    }
  • 枚举类中实现接口
    枚举类可实现接口,但不能从类继承。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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 ")
    }
    };

    abstract fun print()
    }

    fun main(args: Array<String>) {
    ConsoleColor.BLACK.print() //我是枚举常量 BLACK
    }
  • 属性方法

    • 每个枚举常量都包含两个属性:name(枚举常量名)和 ordinal(枚举常量位置)
    • 提供了 values()valueOf() 方法来检测指定的名称与枚举类中定义的任何枚举常量是否匹配。
    • 自 Kotlin 1.1起,可以使用 enumValues<T>()enumValueOf<T>() 函数以泛型的方式访问枚举类中的常量。
    • 如果指定的名称与类中定义的任何枚举常量均不匹配,valueOf() 方法将抛出 IllegalArgumentException 异常。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      enum class Color(var argb : Int){
      RED(0xFF0000),
      WHITE(0xFFFFFF),
      BLACK(0x000000),
      GREEN(0x00FF00)
      }

      fun main(args: Array<String>) {
      //name = RED ordinal = 0
      println("name = " + Color.RED.name + "\tordinal = " + Color.RED.ordinal)

      //RED, WHITE, BLACK, GREEN
      println(enumValues<Color>().joinToString { it.name })
      //RED
      println(enumValueOf<Color>("RED"))

      println(Color.valueOf("RED")) //RED
      println(Color.values()[0]) //RED
      }

五、内联类

内联类仅在 Kotlin 1.3 之后版本可用,目前还是实验性的。关于详情请参见内联类

-------------我是有底线的-------------