如何覆盖案例类同伴中的申请

所以情况就是这样。 我想要像这样定义一个case类:

case class A(val s: String)

我想定义一个对象来确保当我创建类的实例时,'s'的值总是大写,如下所示:

object A {
  def apply(s: String) = new A(s.toUpperCase)
}

但是,这是行不通的,因为Scala抱怨apply(s:String)方法被定义了两次。 我知道案例类的语法会自动为我定义它,但是不是有另一种方法可以实现吗? 我想坚持案例类,因为我想用它来进行模式匹配。


冲突的原因是case类提供了完全相同的apply()方法(相同的签名)。

首先我想建议你使用require:

case class A(s: String) {
  require(! s.toCharArray.exists( _.isLower ), "Bad string: "+ s)
}

如果用户试图创建一个包含小写字符的实例,这将抛出异常。 这是case类的一个很好的用法,因为你在构造函数中放入的东西也是当你使用模式匹配( match )时你得到的东西。

如果这不是你想要的,那么我会使构造函数变为private并强制用户只使用apply方法:

class A private (val s: String) {
}

object A {
  def apply(s: String): A = new A(s.toUpperCase)
}

如你所见,A不再是一个case class 。 我不确定具有不可变字段的case类是否用于修改传入值,因为名称“case class”意味着应该可以使用match来提取(未修改的)构造函数参数。


更新2016/02/25:
虽然我在下面写的答案仍然充足,但值得引用关于案例类的伴侣对象的其他相关答案。 也就是说,当一个人只定义案例类本身时,如何正确地重现编译器生成的隐式伴随对象。 对我来说,结果是违反直觉的。


概要:
您可以在它保存在case类中之前更改case类参数的值,只需保持它有效(有效)的ADT(抽象数据类型)。 虽然解决方案相对简单,但发现细节却颇具挑战性。

细节:
如果你想确保只有你的case类的有效实例可以被实例化,这是ADT(抽象数据类型)背后的一个基本假设,那么你需要做很多事情。

例如,编译器生成的copy方法默认情况下在案例类中提供。 因此,即使您非常小心地确保只通过显式伴随对象的apply方法创建了实例,以确保它们只能包含大写字母值,但下面的代码将生成一个带有小写字母值的case实例:

val a1 = A("Hi There") //contains "HI THERE"
val a2 = a1.copy(s = "gotcha") //contains "gotcha"

另外,case类实现java.io.Serializable 。 这意味着只有大写实例的谨慎策略才能被简单的文本编辑器和反序列化所颠覆。

所以,对于你的案例课程可以使用的各种方式(仁慈和/或恶意),以下是你必须采取的行动:

  • 对于您的显式伴侣对象:
  • 使用与您的案例类完全相同的名称创建它
  • 这可以访问案例类的私人部分
  • 创建一个与您的案例类的主构造函数完全相同的签名的apply方法
  • 这将在步骤2.1完成后成功编译
  • 提供一个使用new运算符获取case类实例并提供一个空实现的实现{}
  • 现在将严格按照您的条款实例化案例类
  • 必须提供空实现{} ,因为case类声明为abstract (参见步骤2.1)
  • 对于你的案例班级:
  • 声明它是abstract
  • 防止Scala编译器在伴随对象中生成一个apply方法,该方法导致“方法被定义两次......”编译错误(上面的步骤1.2)
  • 将主构造器标记为private[A]
  • 主构造函数现在只对case类本身和其伴侣对象(我们在步骤1.1中定义的那个)
  • 创建一个readResolve方法
  • 使用apply方法提供实现(上面的步骤1.2)
  • 创建一个copy方法
  • 将其定义为与案例类的主构造函数具有完全相同的签名
  • 对于每个参数,使用相同的参数名称添加一个默认值(例如: s: String = s
  • 使用apply方法提供一个实现(下面的步骤1.2)
  • 以上操作修改了您的代码:

    object A {
      def apply(s: String, i: Int): A =
        new A(s.toUpperCase, i) {} //abstract class implementation intentionally empty
    }
    abstract case class A private[A] (s: String, i: Int) {
      private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
        A.apply(s, i)
      def copy(s: String = s, i: Int = i): A =
        A.apply(s, i)
    }
    

    在实现require(在@ollekullberg答案中建议)之后,以下是您的代码,并且还指出了放置任何缓存的理想位置:

    object A {
      def apply(s: String, i: Int): A = {
        require(s.forall(_.isUpper), s"Bad String: $s")
        //TODO: Insert normal instance caching mechanism here
        new A(s, i) {} //abstract class implementation intentionally empty
      }
    }
    abstract case class A private[A] (s: String, i: Int) {
      private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
        A.apply(s, i)
      def copy(s: String = s, i: Int = i): A =
        A.apply(s, i)
    }
    

    如果通过Java interop使用此代码(隐藏案例类作为实现并创建一个阻止派生的最终类),则此版本更安全/更健壮:

    object A {
      private[A] abstract case class AImpl private[A] (s: String, i: Int)
      def apply(s: String, i: Int): A = {
        require(s.forall(_.isUpper), s"Bad String: $s")
        //TODO: Insert normal instance caching mechanism here
        new A(s, i)
      }
    }
    final class A private[A] (s: String, i: Int) extends A.AImpl(s, i) {
      private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
        A.apply(s, i)
      def copy(s: String = s, i: Int = i): A =
        A.apply(s, i)
    }
    

    虽然这直接回答了您的问题,但还有更多方法可以扩展实例缓存之外的案例类。 对于我自己的项目需求,我创建了一个更加广阔的解决方案,我在CodeReview(一个StackOverflow姊妹站点)上记录了这个解决方案。 如果你最终看到它,使用或利用我的解决方案,请考虑留下我的反馈意见,建议或问题,并在合理范围内,我会尽我所能在一天内做出回应。


    我不知道如何重写伴随对象中的apply方法(如果这是可能的话),但也可以使用特殊类型的大写字符串:

    class UpperCaseString(s: String) extends Proxy {
      val self: String = s.toUpperCase
    }
    
    implicit def stringToUpperCaseString(s: String) = new UpperCaseString(s)
    implicit def upperCaseStringToString(s: UpperCaseString) = s.self
    
    case class A(val s: UpperCaseString)
    
    println(A("hello"))
    

    以上代码输出:

    A(HELLO)
    

    你也应该看看这个问题,它的答案是:Scala:是否可以重写默认的case类构造函数?

    链接地址: http://www.djcxy.com/p/72819.html

    上一篇: How to override apply in a case class companion

    下一篇: Scala right associative methods