Scala 2.13中如何实现Case Class的多类型联合?
在Scala 2.13中实现多类型约束的Case Class字段
问题背景
我定义了三个case class:
case class Teacher(name: String, subject: String, age: Int)
case class Student(name: String, subject: String, section: String, class: Int)
case class School(user: Teacher | Student)
希望School的user字段只能是Teacher或Student中的一种,请问在Scala 2.13中该如何实现?
补充1
有评论建议用Either,但如果需要支持三种类型呢?比如:
case class School(user: Teacher | Student | Principal)
补充2
我尝试用sealed trait实现,但写法存在问题,不清楚如何实例化。比如怎么创建包含Principal(name="ABC",year=2022)的School实例?我的代码如下:
sealed trait SchoolUser case class Student(name: String, subject: String, section: String, `class`: Int) case class Teacher(name: String, subject: String, age: Int) case class Principal(name: String, year: Int) case object Student extends SchoolUser case object Teacher extends SchoolUser case object Principal extends SchoolUser case class School(user: SchoolUser) val school = School(user = ???) println("Complete")
解决方案
1. 修正Sealed Trait的写法
你之前的代码错误在于重复定义了同名的case class和case object,导致Student/Teacher/Principal的case class并未继承SchoolUser特质。正确的写法是让每个实体case class直接继承SchoolUser,不需要额外的case object:
// 密封特质,限定所有实现类必须在当前文件中定义 sealed trait SchoolUser // 让每个实体类继承SchoolUser case class Student(name: String, subject: String, section: String, `class`: Int) extends SchoolUser case class Teacher(name: String, subject: String, age: Int) extends SchoolUser case class Principal(name: String, year: Int) extends SchoolUser // School类接收SchoolUser类型的参数,自然限定了user只能是上述三种类型之一 case class School(user: SchoolUser)
2. 实例化方式
现在可以直接创建具体的实体实例,传入School的构造函数:
// 创建包含Principal的School实例 val principal = Principal(name = "ABC", year = 2022) val schoolWithPrincipal = School(user = principal) // 创建包含Student的School实例 val student = Student(name = "小明", subject = "数学", section = "A", `class` = 3) val schoolWithStudent = School(student) // 创建包含Teacher的School实例 val teacher = Teacher(name = "李老师", subject = "英语", age = 35) val schoolWithTeacher = School(teacher)
3. Sealed Trait的优势
- 编译时类型检查:因为
SchoolUser是sealed的,编译器会确保所有实现类都在当前文件中,后续无法新增未声明的类型,严格限制School.user的可选范围。 - 完善的模式匹配支持:对
School.user做模式匹配时,编译器会检查是否覆盖了所有可能的类型,避免遗漏分支:
schoolWithPrincipal.user match { case Student(n, s, sec, cls) => println(s"学生:$n,班级:$cls") case Teacher(n, s, a) => println(s"老师:$n,年龄:$a") case Principal(n, y) => println(s"校长:$n,任职年份:$y") }
4. 为什么不推荐嵌套Either?
Either仅适用于二选一的场景,当需要支持三种及以上类型时,嵌套Either(比如Either[Teacher, Either[Student, Principal]])会让代码结构复杂、可读性下降,而sealed trait可以优雅支持任意数量的类型。
内容的提问来源于stack exchange,提问作者Shivam Sahil
相关产品推荐
相关产品推荐

