特殊类

特殊类 #

Scala #

样本类 Case Class #

特点 #

  • 样本类是使用关键字 case class 声明的类。它可以隐式调用构造方法进行初始化,样本类没有副构造方法。
  • 样本类默认基于构造方法实现了 toString(), equals()hashCode() 方法,不在构造方法中定义的属性不会产生在 toString() 的结果中。
  • 样本类可以直接使用 == 进行比较,同样不在构造方法中定义的属性不会用在比较上
  • 样本类被大量使用在模式匹配中

定义样本类 #

case class Number(n: Int) {
  var comment: String = "Default Comment"
  def eval: Int = n
}

case class Sum(n1: Number, n2: Number) {
  var comment: String = "Default Comment"
  def eval: Int = n1.eval + n2.eval
}

以上定义了两个样本类,其中样本类 Sum 用于计算两个样本类 Number 的和。

使用样本类 #

样本类在初始化时可以隐式调用构造方法,所以无需使用 new 关键字。

val n = Number(1)

样本类默认实现了 toString() 方法,该方法覆盖了构造方法中的所有参数,而类中定义的变量不会被写到 toString() 方法中

println(n.toString)

样本类默认实现了 equals() 方法,该方法覆盖了构造方法中的所有参数,而类中定义的变量不会被写到 equals() 方法中

val result1 = Sum(Number(10), Number(20))
result1.comment = "result1"
val result2 = Sum(Number(10), Number(20))
result2.comment = "result2"
println(result1 == result2) //  true

Object #

单例对象 #

在 Java 中实现单例对象通常需要自己实现一个类并创建 getInstance() 的方法然后在该方法里使用两次同步块或者使用更为优雅的基于 enum 的方式。而 Scala 中则更加简单,只要使用 object 声明就能创建一个单例对象。实际上之前我们创建的拥有 main() 方法的都是单例对象。

object Singleton {
  private var num = 0
  def sequence() = {
    num += 1
    num
  }
}

在方法的章节我们已经知道了在 Scala 中是没有静态成员这一说的,但是有时我们可能又真的需要有这种机制。Scala 就是采用了使用单例对象来实现类似的静态成员。

assert(1 == Singleton.sequence())
assert(2 == Singleton.sequence())
assert(3 == Singleton.sequence())

可以看到这种方式和在 Java 中调用静态方法一模一样,但是 Scala 中这其实是调用了一个对象的方法。

通常来说,Scala 中的单例对象一般用于提供常量及共享不可变对象。

伴生对象 #

我们知道使用 class 可以创建一个类,使用 object 可以创建一个单例对象,而类和单例对象是可以重名的,此时我们称该单例对象为此类的伴生对象。

伴生对象有以下特点

  • 伴生对象可以用于让一个类即拥有实例化方法又有静态方法。
  • 伴生对象可以用于创建同名类的实例。
  • 伴生对象必须与同名类位于同一源文件中。
  • 伴生对象与类可以互相访问各自的私有成员。

创建一个类

class Companion(private var balance: Int = 0) {
  val id = Companion.sequence()
}

创建该类的伴生对象

object Companion {
  private var num = 0

  def getInfo(account: Companion): String = {
    "Balance is " + account.balance
  }

  private def sequence(): Int = {
    num += 1
    num
  }

  def apply(initBalance: Int) = {
    new Companion(initBalance)
  }
}

使用类与伴生对象

//  通过伴生对象的 apply() 方法创建类的实例
val account = Companion(30)

//  在类中调用伴生对象的私有方法
assert(1 == account.id)

//  在伴生对象中访问类的私有属性
assert("Balance is 30" == Companion.getInfo(account))

继承 #

单例对象不能继承另一个单例对象,但是单例对象本身可以继承另一个类,从而扩展该类的功能,实现该类的可共享的默认对象

abstract class UndoableAction(val description: String) {
  def undo

  def redo
}

object DoNothing extends UndoableAction("Do nothing") {

  override def undo: Unit = {
    println("undo")
  }

  override def redo: Unit = {
    println("redo")
  }

}

以上 DoNothing 继承了 UndoableAction 类,可以用于作为 UndoableAction 的一个默认对象来使用。

应用程序对象 #

应用程序对象是继承类 App 的单例对象,该对象是一个应用程序的入口,可以通过 args 获得命令行参数。即使用了应用程序对象后你就不需要显示声明 main() 方法了,应用程序对象中定义的所有语句都会在 main() 方法中执行。

object Hello extends App {

  println("Hello World")

  if (args.length > 0) {
    println("The passed arguments are " + args)
  } else {
    println("There is no argument passed.")
  }
}

Kotlin #

Data Class #

特点 #

  • Data 类是使用关键字 data 声明的类
  • Data 类默认基于构造方法实现了 toString(), componentN(), copy(), equals()hashCode() 方法,不在构造方法中定义的属性不会产生在 toString() 结果中。
  • Data 类可以直接使用 == 进行比较,同样不在构造方法中定义的属性不会用在比较上
  • Data 类只表示数据,不能拥有行为,所以通常来说我们没有必要为 Data 类构建类体

定义 Data 类 #

大部分情况下没有必要构建类体,所以省略大括号

data class Customer(var name: String, var email: String, var friends: ArrayList<String>)

使用 Data 类 #

创建对象

val peter = Customer("Peter", "peter@example.com", arrayListOf("Jane", "Tom"))

复制对象

Data class 默认实现了 copy() 方法,可以进行属性的复制,该复制仅为浅复制

val tom = peter.copy()
val jane = peter.copy(name = "")
peter.friends.add("Tina")

println(peter)  //  Customer(name=Peter, email=peter@example.com, friends=[Jane, Tom, Tina])
println(tom)    //  Customer(name=Peter, email=peter@example.com, friends=[Jane, Tom, Tina])
println(jane)   //  Customer(name=, email=peter@example.com, friends=[Jane, Tom, Tina])

以上例子我们可以看到修改了 peterfriends 列表后 janetomfriends 列表也改变了,证明复制的仅是引用,不是值本身。

ComponentN #

val (name, email) = peter
println("name=$name,email=$email")

Object #

Kotlin 中的 Object 用法类似 Scala,但与 Scala 中的 Object 的最大区别是 Kotlin 不允许 Object 和类同名。

单例对象 #

object Singleton {
    private var num = 0

    fun sequence(): Int {
        num += 1
        return num
    }
}

同 Scala 一样,Kotlin 也通过 Object 来实现静态方法的功能。

assert(1 == Singleton.sequence())
assert(2 == Singleton.sequence())
assert(3 == Singleton.sequence())

伴生对象 #

伴生对象的概念与 Scala 基本一致,但是 Kotlin 的伴生对象是通过关键字 companion object 定义在类中,而不是类外,伴生对象不与类同名。

创建类和伴生对象

class Companion(private var balance: Int = 0) {
    //  Access private method of companion object
    val id = Companion.sequence()

    companion object Factory {
        private var num = 0

        fun getInfo(account: Companion): String {
            return "Balance is " + account.balance
        }

        fun create(initBalance: Int): Companion {
            return Companion(initBalance)
        }

        private fun sequence(): Int {
            num += 1
            return num
        }
    }
}

使用类与伴生对象

//  通过伴生对象的方法创建类的实例
val account = Companion.create(30)

//  在类中调用伴生对象的私有方法
assert(1 == account.id)

//  在伴生对象中访问类的私有属性
assert("Balance is 30" == Companion.getInfo(account))

object 表达式 #

object 表达式用于扩展类的功能,使用时类似 Java 中的匿名内部类。

定义一个类和接口

open class A(x: Int) {
    public open val y: Int = x
}

interface B {
    fun info()
}

使用 object 表达式扩展类和接口

val ab = object : A(1), B {
    override fun info() {
        println("info")
    }

    override val y: Int
        get() = 15
}
println(ab.y)
println(ab.javaClass)

以上示例本质上是生成了一个实现类和接口的匿名内部类的对象。在 Kotlin 中也可以不指定任何父类,直接产生匿名内部类:

val obj = object {
    val x = 10
    val y = 20
}
println(obj.x)
println(obj.javaClass)

总结 #

  • Scala 和 Kotlin 使用单例对象来提供其它语言中的全局常量和静态方法的特性
  • Scala 的伴生对象与类同名且平级,Kotlin 的伴生对象定义在类中且不同名
  • Kotlin 的 object 表达式可以实现简单的继承功能

项目源码见 JGSK/_18_dataclass

沪ICP备17055033号-2