导入
在之前我们学习了 Scala 中的一些数据结构 Set,我们知道 Set 中是不能放入重复的元素的。下面我们来看一个特殊的问题。在集合操作中,当我们使用普通类作为 Set 的元素时,即使两个对象的内容完全相同,Set 也可能无法正确识别它们是重复的。这是因为 Set 依赖对象的 hashCode 和 equals 方法来判断元素是否重复,而普通类默认的这两个方法是基于对象引用的。
一、case class 的定义和基本语法
定义
case class 是 Scala 中一种特殊的类,它用于创建不可变的数据容器。它被设计为轻量级的数据承载者,特别适合用于模式匹配和不可变数据建模。
语法
定义 case class 的语法非常简单明了:
case class ClassName(parameter1: Type1, parameter2: Type2, ...)
例如,我们要创建一个学生的 case class:
case class Student(name: String, age: Int, grade: String)
好处
- 自动方法生成:编译器自动生成常用方法,减少样板代码
- 不可变性:默认提供不可变特性,更适合函数式编程
- 模式匹配友好:天然支持模式匹配操作
- 值语义:基于值的相等性判断
二、继承的好处之复用代码
示例
让我们通过两个具体的例子来对比普通类和 case class 在实现相同功能时的差异,特别是在集合操作中的表现。
案例:普通类实现相等性判断
代码
package caseclass
import scala.collection.mutable
object caseclass1 {
class Book(val id: Int, val name: String) {
override def equals(obj: Any): Boolean = {
val other = obj.asInstanceOf[Book]
other.id == id && other.name == name
}
}
def main(args: Array[String]): Unit = {
val set1: mutable.Set[Int] = mutable.Set(1, 2)
set1 += 1
set1 += 1
set1 += 1
set1 += 1
println(set1)
val book1 = new Book(1, "西游记")
val book2 = new Book(1, "西游记")
println(book1 == book2)
val set2: mutable.Set[Book] = mutable.Set()
set2 += book1
set2 += book2
println(set2)
}
}
结果展示
代码分析
- 普通类的局限性:这个例子展示了普通类在使用时需要手动重写方法的问题
- 不完整的相等性实现:虽然我们重写了
equals方法来判断两个Book对象是否相等,但没有重写hashCode方法 - Set 去重失败的原因:Scala 的 Set 在判断元素是否重复时,会同时使用
hashCode和equals方法。由于hashCode方法没有被重写,两个内容相同的Book对象产生了不同的哈希码 - 结果解释:控制台输出显示
Set中确实有两个元素,虽然打印出来只显示了一个引用地址,但实际上它们被视为不同的对象
继承的特点
这个案例突显了普通类在实现值语义时的复杂性:
- 必须手动实现多个方法,工作量大
- 容易出错,特别是
hashCode和equals需要保持一致 - 代码重复,每个类都需要类似的样板代码
三、case class 的特点和方法重写
格式
case class 通过简洁的语法自动完成了很多工作:
case class 类名(参数列表)
案例:使用 case class 简化实现
代码
package caseclass
import scala.collection.mutable
object caseclass2 {
case class Book(val id: Int, val name: String){}
def main(args: Array[String]): Unit = {
val book1 = new Book(1, "西游记")
println(book1)
val book2 = Book(1, "西游记")
println(book1 == book2)
val set2: mutable.Set[Book] = mutable.Set()
set2 += book1
set2 += book2
println(set2)
}
}
结果展示
代码分析
- 语法简洁性:case class 的定义非常简洁,一行代码就完成了类的定义
- 自动方法生成:编译器自动为
Book类生成了:toString方法:提供格式化的字符串表示equals方法:基于所有属性值的比较hashCode方法:基于所有属性值的哈希计算apply方法:工厂方法,允许省略new关键字
- 省略 new 关键字:
Book(1, "西游记")这种创建方式更加简洁 - 正确的集合行为:由于
equals和hashCode都被正确实现,Set 能够正确识别重复元素 - 结果验证:Set 中只包含一个元素,证明去重功能正常工作
案例:case class 完整特性演示
代码
package caseclass
object caseclass3 {
case class Student (name:String, age:Int)
def main(args: Array[String]): Unit = {
// val st1 = new Student("小花", 18)
// 可以省略 new
val st1 = Student("小花", 18)
val st2 = Student("小花", 18)
// 属性默认是不可变的。val 修饰
// st1.name = "小花花"
println(st1)
println(st1 == st2)
}
}
结果展示
代码分析
- 不可变性:case class 的参数默认为
val类型,创建后不能修改 - 省略 new 的便利:使用伴生对象的
apply方法创建实例,代码更简洁 - 自动的 toString:自动生成的
toString方法提供了清晰的对象表示 - 基于值的相等性:
equals方法比较的是所有属性的值,而不是对象引用 - 其他隐含特性:
- 自动生成
unapply方法,支持模式匹配 - 提供
copy方法,用于创建修改后的新实例 - 自动生成伴生对象
- 自动生成
四、case class 与普通 class 的区别
区别1:更加轻量级,特别适合用于定义数据
case class 专门为承载数据而设计,它去掉了普通类中可能不需要的复杂性,专注于数据的存储和基本操作。
区别2:内置很多方法
case class 自动提供了以下方法,而普通类需要手动实现:
toString():友好的字符串表示equals()和hashCode():基于值的比较copy():创建修改副本apply():工厂方法unapply():模式匹配支持
五、总结
-
case class 的定义语法是什么?
- 答:
case class 类名(参数1: 类型, 参数2: 类型, ...)
- 答:
-
case class 的特点有哪些?
- 答:主要特点包括:
- 可以省略
new关键字创建实例 - 属性默认是不可变的(val 修饰)
- 自动实现
toString、equals、hashCode等方法 - 支持模式匹配
- 提供
copy方法
- 可以省略
- 答:主要特点包括: