Scala - 枚举 vs. 案例类

14

我已经创建了一个名为LogActor的akka actor。LogActor的receive方法处理来自其他actors的消息,并将它们记录到指定的日志级别。

我可以通过两种方式区分不同的级别,第一种方式:

import LogLevel._
object LogLevel extends Enumeration {
    type LogLevel = Value
    val Error, Warning, Info, Debug = Value
}
case class LogMessage(level : LogLevel, msg : String) 
第二点:(编辑
abstract class LogMessage(msg : String)
case class LogMessageError(msg : String) extends LogMessage(msg)
case class LogMessageWarning(msg : String) extends LogMessage(msg)
case class LogMessageInfo(msg : String) extends LogMessage(msg)
case class LogMessageDebug(msg : String) extends LogMessage(msg)

哪种方式更高效?匹配 case class 还是匹配 enum 值需要更少的时间?

(我阅读了这个问题,但没有任何答案涉及运行时问题)


1
有一件重要的事情需要记住:在您(可能过于简化的)第二个代码片段中,您没有一个基类适用于所有“xxxLogMessage”案例类。 - Malte Schwerhoff
那么这样做会更好吗: case object LogMessage
case class ErrorLogMessage(msg : String) extends LogMessage
case class WarningLogMessage(msg : String) extends LogMessage
case class InfoLogMessage(msg : String) extends LogMessage
case class DebugLogMessage(msg : String) extends LogMessage? 有没有适当的方法将'msg'作为LogMessage的参数编写(这样就不需要在每个case类中编写它了..)?
- user1768906
3
您可以使用“抽象类LogMessage(msg:String); case class ErrorLogMessage(msg:String) extends LogMessage(msg); ...”进行操作,但这并不能避免您重复使用“msg”。顺便说一下,这是正确的方法,高度不建议使用case类继承。 - pedrofurla
3个回答

27

我完全同意Alexey和Dennis的观点,认为在这种情况下性能不应该成为你的顾虑,因为这更多是编译器优化的问题,而不是开发人员的问题,我想象不出性能差异会变得明显的场景。

让你困扰的应该是你的代码一致性,在这方面,你应该根据自己是否想坚持旧有的枚举(enums)方式来决定,该方式在你的第一个示例中已经正确描述了,或者选择最近越来越受欢迎的代数数据类型(Algebraic Data Types,ADT)模式。你在第二个例子中尝试了后者,但存在一些错误。

接下来介绍如何使用ADT模式正确解决这个问题:

ADT解决方案#1

// 1. marked `sealed` to make pattern matching exhaustive
// 2. used a trait to avoid double storage of msg` and 
//    make the inheritance easier
sealed trait LogMessage { def msg : String }
// A better solution for isolation than names like "LogMessageError".
// Allows you to either address the members with a namespace like 
// "LogMessage.Error" or do "import LogMessage._" and address them 
// directly
object LogMessage { 
  case class Error (msg : String) extends LogMessage
  case class Warning (msg : String) extends LogMessage
  case class Info (msg : String) extends LogMessage
  case class Debug (msg : String) extends LogMessage
}

ADT解决方案#2

很抱歉可能会让您感到困惑,但值得注意的是,在类似情况下还存在另一种替代的ADT方法,它与您使用枚举的方法有点相似。

sealed trait LogLevel 
object LogLevel {
  case object Error extends LogLevel
  case object Warning extends LogLevel
  case object Info extends LogLevel
  case object Debug extends LogLevel
}
case class LogMessage ( level : LogLevel, msg : String )

8

“不要过早优化”这个原则很重要。相比传递消息或记录日志的时间,它们之间的差别并不会很明显。但是,为了获得最佳性能,最好创建一个Java enum(可以轻松地从Scala中访问和使用)来表示日志级别,而不是使用Scala Enumeration


0

对于日志记录器来说,最常见的操作之一是将当前的记录级别与消息级别进行比较。使用枚举类型,你可以免费获得这种功能,而如果使用案例类进行设置,则可能会有些麻烦。我同意@AlexeyRomanov的观点:在这里匹配不应该成为瓶颈。

编辑:就性能而言,使用案例类进行匹配时,在字节码中会使用 instanceof,而对于枚举类型,Scala编译器生成的代码无法处理所有我尝试过的反编译程序。它似乎正在使用equals。因此,从技术上讲,枚举类型可能更快,但实际上在性能上没有区别。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接