Scala 案例类和模式匹配

案例类

case 修饰符修饰的类称为案例类case class),例如:

1case class Person(name: String, age: Int)

案例类会自动添加一个跟类同名的工厂方法,因此在实例化对象时可以省略 new 关键字:

1val person = Person("Alice", 25)
2println(person) // Person(Alice,25)

案例类参数列表中的参数都隐式地获得了一个 val 前缀,因此它们会被当作字段处理。

1val person = Person("Alice", 25)
2println(person.name) // Alice
3println(person.age) // 25
4
5person.name = "Bob" // 编译错误
6person.age = 26 // 编译错误

编译器会帮助我们以自然的方式实现 toStringhashCodeequals 方法:

1val p1 = Person("Alice", 25)
2val p2 = Person("Bob", 25)
3println(p1.toString) // Person(Alice,25)
4println(p1.hashCode) // -369217488
5println(p1.equals(p2)) // false

编译器会添加一个 copy 方法用于制作修改过的拷贝,这个方法可以用于制作一两个属性不同之外其余完全相同的该类的新实例:

1val p1 = Person("Alice", 25)
2val p2 = p1.copy()
3val p3 = p2.copy(name = "Bob")
4println(p2) // Person(Alice,25)
5println(p3) // Person(Bob,25)

模式匹配

使用案例类的一个最大好处是它们天然支持模式匹配,示例如下:

match 语句支持多种类型的模式匹配:

  • 通配模式:配置任何值
  • 常量模式:匹配字面量或单例对象
  • 变量模式:匹配任何值,并将值赋给变量
  • 类型模式:匹配特定类型的值
  • 构造器模式:匹配具有特定构造器的对象
  • 序列模式:匹配序列(如 List 或 Array)
  • 元组模式:匹配元组
  • 模式守卫(Guard):通过添加条件表达式,对模式进行额外的过滤

通配模式

通配模式可以匹配任何对象,一种常见的用法是将其作为所有模式匹配的默认选项:

1def describe(x: Any): String = x match {
2  case _ => "Unknown"
3}
4println(describe(42)) // Unknown

常量模式

常量模式匹配任何字面量或单例对象,例如,53.14true"Hello, World!" 都可以作为常量模式,此外,任何 val 类型的值或单例对象也都可以作为常量模式。下面是一个简单的常量模式匹配示例:

1def describe(x: Any): String = x match {
2  case 0 => "Zero"
3  case 1 => "One"
4  case 2 => "Two"
5  case _ => "Unknown"
6}
7println(describe(1)) // One

变量模式

变量模式与通配模式类似,区别在于会将匹配的值赋给一个变量,示例如下:

1def describe(x: Any): String = x match {
2  case 0 => "Zero"
3  case somethingElse => s"Something else: $somethingElse"
4}
5println(describe(42)) // Something else: 42

类型模式

 1def describe(x: Any): String = x match {
 2  case i: Int      => "Int"
 3  case d: Double   => "Double"
 4  case s: String   => "String"
 5  case b: Boolean  => "Boolean"
 6  case l: List[_]  => "List"
 7  case a: Array[_] => "Array"
 8  case _           => "Unknown"
 9}
10
11println(describe(42)) // Int
12println(describe(3.14)) // Double
13println(describe(List(1, 2, 3))) // List

构造器模式

构造器模式可以和 Option 类型结合使用,例如:

1case class Person(name: String, age: Int)
2
3def describePerson(person: Option[Person]): String = person match {
4  case Some(p) => s"${p.name} is ${p.age} years old."
5  case None    => "No person found."
6}
7
8println(describePerson(Some(Person("Alice", 18)))) // Alice is 18 years old.
9println(describePerson(None)) // No person found.

在上面的示例中,通过匹配构造器模式 Some(p)None 来处理可选值。

序列模式

模式匹配可以用来识别并分解序列中的元素,可以匹配序列的整体结构,也可以匹配序列中的特定元素,甚至是序列的长度,示例如下:

1val nums = List(1, 2, 3)
2
3nums match {
4  case List(1, _, _) => println("List starts with 1 and has three elements.")
5  case List(1, _*)   => println("List starts with 1.")
6  case _             => println("Something else.")
7}
8// Expected output:
9// List starts with 1 and has three elements.

在上面的示例中,case List(1, _, _) 匹配所有以 1 开头的长度为 3 的序列,case List(1, _*) 匹配以 1 开头的任意长度的序列。_* 是一个特殊的模式,代表任意数量的元素。

元组模式

通过形如 (a, b, c) 这样的元组模式可以匹配任意长度为 3 的元组。下面是一个简单的元组模式匹配示例:

1val tuple = (123, "abc")
2
3tuple match {
4  case (number, string) => println(s"Number is $number, String is $string")
5}
6// Expected output:
7// Number is 123, String is abc

通过模式匹配,可以直接获取到元组的两个元素,并将它们绑定到 numberstring 变量上,分别代表元组的第一个元素(一个整数)和第二个元素(一个字符串)。

案例类模式

 1trait Animal
 2
 3case class Cat(name: String) extends Animal
 4
 5case class Dog(name: String) extends Animal
 6
 7val a: Animal = Cat("Tom")
 8
 9a match {
10  case Cat(name) => println(s"A cat named $name")
11  case Dog(name) => println(s"A dog named $name")
12  case _         => println("An unknown animal")
13}
14// Expected output:
15// A cat named Tom

模式守卫

 1val nums = List(1, 2, 3, 4, 5)
 2nums.foreach(num => num match {
 3  case even if even % 2 == 0 => println(s"$even is even")
 4  case odd => println(s"$odd is odd")
 5})
 6// Expected output:
 7// 1 is odd
 8// 2 is even
 9// 3 is odd
10// 4 is even
11// 5 is odd

密封类/特质

在编写模式匹配时,需要确保匹配了所有可能的情况。我们可以在模式匹配的末尾添加一个通配模式 case _ => 来处理未匹配到的情况。但是在某些情况下,使用通配模式无法很好地处理所有情况,这时可以使用 sealed 关键字标记超类、特质或枚举,这样编译器会检查该类的所有子类是否被完全覆盖。

 1sealed trait Animal
 2
 3case class Cat(name: String) extends Animal
 4
 5case class Dog(name: String) extends Animal
 6
 7val a: Animal = Cat("Tom")
 8
 9a match {
10  case Cat(name) => println(s"A cat named $name")
11  case Dog(name) => println(s"A dog named $name")
12}
13// Expected output:
14// A cat named Tom

在上面的示例中,使用了 sealed 关键字修饰 trait,这表示 Animal 是一个密封的特质,它的子类只能是 CatDog,并且不能再有子类。因此,在模式匹配中,无需再使用通配模式 case _ 进行兜底了。

上一页
下一页