scala 如何在案例类伴侣中覆盖应用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5827510/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to override apply in a case class companion
提问by John S
So here's the situation. I want to define a case class like so:
所以这里的情况。我想像这样定义一个案例类:
case class A(val s: String)
and I want to define an object to ensure that when I create instances of the class, the value for 's' is always uppercase, like so:
并且我想定义一个对象以确保当我创建类的实例时,“s”的值总是大写,如下所示:
object A {
def apply(s: String) = new A(s.toUpperCase)
}
However, this doesn't work since Scala is complaining that the apply(s: String) method is defined twice. I understand that the case class syntax will automatically define it for me, but isn't there another way I can achieve this? I'd like to stick with the case class since I want to use it for pattern matching.
但是,这不起作用,因为 Scala 抱怨 apply(s: String) 方法被定义了两次。我知道 case 类语法会自动为我定义它,但是没有其他方法可以实现这一点吗?我想坚持使用 case 类,因为我想将它用于模式匹配。
回答by olle kullberg
The reason for the conflict is that the case class provides the exact same apply() method (same signature).
冲突的原因是 case 类提供了完全相同的 apply() 方法(相同的签名)。
First of all I would like to suggest you use require:
首先,我建议您使用 require:
case class A(s: String) {
require(! s.toCharArray.exists( _.isLower ), "Bad string: "+ s)
}
This will throw an Exception if the user tries to create an instance where s includes lower case chars. This is a good use of case classes, since what you put into the constructor also is what you get out when you use pattern matching (match).
如果用户尝试创建 s 包含小写字符的实例,这将引发异常。这是案例类的一个很好的用途,因为您放入构造函数的内容也是您在使用模式匹配 ( match) 时得到的内容。
If this is not what you want, then I would make the constructor privateand force the users to onlyuse the apply method:
如果这不是您想要的,那么我将创建构造函数private并强制用户仅使用 apply 方法:
class A private (val s: String) {
}
object A {
def apply(s: String): A = new A(s.toUpperCase)
}
As you see, A is no longer a case class. I am not sure if case classes with immutable fields are meant for modification of the incoming values, since the name "case class" implies it should be possible to extract the (unmodified) constructor arguments using match.
如您所见, A 不再是case class。我不确定具有不可变字段的案例类是否用于修改传入的值,因为名称“案例类”意味着应该可以使用match.
回答by chaotic3quilibrium
UPDATE 2016/02/25:
While the answer I wrote below remains sufficient, it's worth also referencing another related answer to this regarding the case class's companion object. Namely, how does one exactly reproduce the compiler generated implicit companion objectwhich occurs when one only defines the case class itself. For me, it turned out to be counter intuitive.
2016 年 2 月 25 日更新:
虽然我在下面写的答案仍然足够,但也值得参考另一个关于案例类的伴随对象的相关答案。即,如何准确地重现编译器生成的隐式伴随对象,该对象仅在定义 case 类本身时发生。对我来说,结果证明这是违反直觉的。
Summary:
You can alter the value of a case class parameter before it is stored in the case class pretty simply while it still remaining a valid(ated) ADT (Abstract Data Type). While the solution was relatively simple, discovering the details was quite a bit more challenging.
摘要:
您可以在将案例类参数存储到案例类中之前更改案例类参数的值,同时它仍然保持有效的(经过验证的)ADT(抽象数据类型)。虽然解决方案相对简单,但发现细节更具挑战性。
Details:
If you want to ensure only valid instances of your case class can ever be instantiated which is an essential assumption behind an ADT (Abstract Data Type), there are a number of things you must do.
详细信息:
如果您想确保只能实例化您的案例类的有效实例,这是 ADT(抽象数据类型)背后的基本假设,您必须做很多事情。
For example, a compiler generated copymethod is provided by default on a case class. So, even if you were very careful to ensure only instances were created via the explicit companion object's applymethod which guaranteed they could only ever contain upper case values, the following code would produce a case class instance with a lower case value:
例如,copy默认情况下在案例类上提供编译器生成的方法。因此,即使您非常小心地确保仅通过显式伴随对象的apply方法创建实例,以保证它们只能包含大写值,以下代码也会生成一个具有小写值的 case 类实例:
val a1 = A("Hi There") //contains "HI THERE"
val a2 = a1.copy(s = "gotcha") //contains "gotcha"
Additionally, case classes implement java.io.Serializable. This means that your careful strategy to only have upper case instances can be subverted with a simple text editor and deserialization.
此外,案例类实现java.io.Serializable. 这意味着可以通过简单的文本编辑器和反序列化来颠覆您只拥有大写实例的谨慎策略。
So, for all the various ways your case class can be used (benevolently and/or malevolently), here are the actions you must take:
因此,对于您的案例类可以使用的所有各种方式(善意和/或恶意),以下是您必须采取的行动:
- For your explicit companion object:
- Create it using exactly the same name as your case class
- This has access to the case class's private parts
- Create an
applymethod with exactly the same signature as the primary constructor for your case class- This will successfully compile once step 2.1 is completed
- Provide an implementation obtaining an instance of the case class using the
newoperator and providing an empty implementation{}- This will now instantiate the case class strictly on your terms
- The empty implementation
{}must be provided because the case class is declaredabstract(see step 2.1)
- Create it using exactly the same name as your case class
- For your case class:
- Declare it
abstract- Prevents the Scala compiler from generating an
applymethod in the companion object which is what was causing the "method is defined twice..." compilation error (step 1.2 above)
- Prevents the Scala compiler from generating an
- Mark the primary constructor as
private[A]- The primary constructor is now only available to the case class itself and to its companion object (the one we defined above in step 1.1)
- Create a
readResolvemethod- Provide an implementation using the apply method (step 1.2 above)
- Create a
copymethod- Define it to have exactly the same signature as the case class's primary constructor
- For each parameter, add a default value using the same parameter name (ex:
s: String = s) - Provide an implementation using the apply method (step 1.2 below)
- Declare it
- 对于您的显式伴随对象:
- 使用与案例类完全相同的名称创建它
- 这可以访问案例类的私有部分
- 创建一个
apply与您的案例类的主构造函数具有完全相同签名的方法- 一旦步骤 2.1 完成,这将成功编译
- 提供使用
new运算符获取 case 类实例并提供空实现的实现{}- 这现在将严格按照您的条件实例化案例类
{}必须提供空实现,因为已声明案例类abstract(请参阅步骤 2.1)
- 使用与案例类完全相同的名称创建它
- 对于您的案例类:
- 声明它
abstract- 防止 Scala 编译器
apply在伴随对象中生成一个方法,这是导致“方法被定义两次...”编译错误的原因(上面的步骤 1.2)
- 防止 Scala 编译器
- 将主构造函数标记为
private[A]- 主构造函数现在仅可用于 case 类本身及其伴随对象(我们在上面的步骤 1.1 中定义的对象)
- 创建
readResolve方法- 提供使用 apply 方法的实现(上面的步骤 1.2)
- 创建
copy方法- 将其定义为与 case 类的主构造函数具有完全相同的签名
- 对于每一个参数,使用相同的参数名称添加的默认值(例如:
s: String = s) - 提供使用 apply 方法的实现(下面的步骤 1.2)
- 声明它
Here's your code modified with the above actions:
这是使用上述操作修改的代码:
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)
}
And here's your code after implementing the require (suggested in the @ollekullberg answer) and also identifying the ideal place to put any sort of caching:
这是实现 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)
}
And this version is more secure/robust if this code will be used via Java interop (hides the case class as an implementation and creates a final class which prevents derivations):
如果此代码将通过 Java 互操作使用(将 case 类隐藏为实现并创建防止派生的最终类),则此版本更安全/健壮:
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)
}
While this directly answers your question, there are even more ways to expand this pathway around case classes beyond instance caching. For my own project needs, I have created an even more expansive solutionwhich I have documented on CodeReview(a StackOverflow sister site). If you end up looking it over, using or leveraging my solution, please consider leaving me feedback, suggestions or questions and within reason, I will do my best to respond within a day.
虽然这直接回答了您的问题,但除了实例缓存之外,还有更多方法可以围绕案例类扩展此途径。对于我自己的项目需求,我创建了一个更广泛的解决方案,我在 CodeReview(一个 StackOverflow 姊妹站点)上记录了该解决方案。如果您最终查看、使用或利用我的解决方案,请考虑给我留下反馈、建议或问题,并且在合理范围内,我将尽力在一天内回复。
回答by Frank S. Thomas
I don't know how to override the applymethod in the companion object (if that is even possible) but you could also use a special type for upper case strings:
我不知道如何覆盖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"))
The above code outputs:
上面的代码输出:
A(HELLO)
You should also have a look at this question and it's answers: Scala: is it possible to override default case class constructor?
您还应该看看这个问题及其答案:Scala: is it possible to override default case class constructor?
回答by Mehmet Emre
For the people reading this after April 2017: As of Scala 2.12.2+, Scala allows overriding apply and unapply by default. You can get this behavior by giving -Xsource:2.12option to the compiler on Scala 2.11.11+ as well.
对于 2017 年 4 月之后阅读本文的人:从 Scala 2.12.2+ 开始,Scala允许默认覆盖 apply 和unapply 。您也可以通过为-Xsource:2.12Scala 2.11.11+ 上的编译器提供选项来获得此行为。
回答by Mika?l Mayer
It works with var variables:
它适用于 var 变量:
case class A(var s: String) {
// Conversion
s = s.toUpperCase
}
This practice is apparently encouraged in case classes instead of defining another constructor. See here.. When copying an object, you also keep the same modifications.
在 case 类中显然鼓励这种做法,而不是定义另一个构造函数。看这里。. 复制对象时,您也会保留相同的修改。
回答by Peter Schmitz
Another idea while keeping case class and having no implicit defs or another constructor is to make the signature of applyslightly different but from a user perspective the same.
Somewhere I have seen the implicit trick, but can′t remember/find which implicit argument it was, so I chose Booleanhere. If someone can help me out and finish the trick...
保持 case class 并且没有隐式 defs 或另一个构造函数的另一个想法是使签名apply略有不同,但从用户的角度来看是相同的。我在某处看到了隐式技巧,但不记得/找到它是哪个隐式参数,所以我选择了Boolean这里。如果有人能帮我完成这个把戏......
object A {
def apply(s: String)(implicit ev: Boolean) = new A(s.toLowerCase)
}
case class A(s: String)
回答by Pere Ramirez
I faced the same problem and this solution is ok for me:
我遇到了同样的问题,这个解决方案对我来说没问题:
sealed trait A {
def s:String
}
object A {
private case class AImpl(s:String)
def apply(s:String):A = AImpl(s.toUpperCase)
}
And, if any method is needed, just define it in the trait and override it in the case class.
而且,如果需要任何方法,只需在特征中定义它并在案例类中覆盖它。
回答by critium
If you're stuck with older scala where you cant override by default or you dont want to add the compiler flag as @mehmet-emre showed, and you require a case class, you can do the following:
如果您坚持使用默认情况下无法覆盖的旧 scala,或者您不想添加编译器标志,如@mehmet-emre 所示,并且您需要一个案例类,您可以执行以下操作:
case class A(private val _s: String) {
val s = _s.toUpperCase
}
回答by mattrjacobs
I think this works exactly how you want it to already. Here's my REPL session:
我认为这正是您想要的方式。这是我的 REPL 会话:
scala> case class A(val s: String)
defined class A
scala> object A {
| def apply(s: String) = new A(s.toUpperCase)
| }
defined module A
scala> A("hello")
res0: A = A(HELLO)
This is using Scala 2.8.1.final
这是使用 Scala 2.8.1.final

