如何在案例类同伴中覆盖 apply
情况是这样的。我想像这样定义一个案例类:
case class A(val s: String)
并且我想定义一个对象以确保当我创建该类的实例时,“s”的值始终为大写,如下所示:
object A {
def apply(s: String) = new A(s.toUpperCase)
}
但是,这在 Scala 中不起作用抱怨 apply(s: String) 方法被定义了两次。我知道案例类语法会自动为我定义它,但是没有其他方法可以实现这一点吗?我想坚持使用案例类,因为我想用它来进行模式匹配。
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:
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
冲突的原因是案例类提供了完全相同的 apply() 方法(相同的签名)。
首先,我建议您使用 require:
如果用户尝试创建 s 包含小写字符的实例,这将引发异常。这是案例类的一个很好的用途,因为您放入构造函数中的内容也是您在使用模式匹配 (
match
) 时得到的内容。如果这不是您想要的,那么我会将构造函数设置为私有,并强制用户仅使用 apply 方法:
如您所见,A 不再是 <代码>案例类。我不确定具有不可变字段的案例类是否用于修改传入值,因为名称“案例类”意味着应该可以使用
match
提取(未修改的)构造函数参数。The reason for the conflict is that the case class provides the exact same apply() method (same signature).
First of all I would like to suggest you use require:
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
).If this is not what you want, then I would make the constructor
private
and force the users to only use the apply method: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 usingmatch
.2016/02/25更新:
虽然我在下面写的答案仍然足够,但还值得参考有关案例类的伴随对象的另一个相关答案。即,如何准确地重现编译器生成的隐式伴生对象,当仅定义案例类本身时,就会发生这种情况。对我来说,结果是违反直觉的。
摘要:
您可以在将案例类参数的值存储在案例类中之前非常简单地更改它,同时它仍然保持有效(已验证)ADT(抽象数据类型)。虽然解决方案相对简单,但发现细节却更具挑战性。
详细信息:
如果您想确保只能实例化案例类的有效实例(这是 ADT(抽象数据类型)背后的基本假设),那么您必须执行许多操作。
例如,案例类默认提供编译器生成的
copy
方法。因此,即使您非常小心地确保仅通过显式伴生对象的apply
方法创建实例(保证它们只能包含大写值),以下代码也会生成一个带有小写值:此外,案例类实现 java.io.Serialized。这意味着您只使用大写实例的谨慎策略可以通过简单的文本编辑器和反序列化来破坏。
因此,对于您的案例类可以使用的所有各种方式(善意和/或恶意),以下是您必须采取的操作:
apply
方法,其签名与案例类的主构造函数完全相同new
运算符获取案例类的实例并提供一个空实现{}
{}
,因为案例类被声明为abstract
(请参阅步骤 2.1)抽象
apply
方法,这会导致“方法被定义两次...”编译错误(上面的步骤 1.2)private[A]
readResolve
方法复制
方法s: String = s
)这是使用上述操作修改的代码:
这是实现 require(在 @ollekullberg 答案中建议)并确定放置任何类型缓存的理想位置后的代码:
这是如果此代码将通过 Java 互操作使用(隐藏案例类作为实现并创建一个防止派生的最终类),则该版本会更安全/健壮:
虽然这直接回答了您的问题,但还有更多方法可以扩展此路径实例缓存之外的案例类。对于我自己的项目需求,我创建了一个更广泛的解决方案,我有记录在 CodeReview(StackOverflow 姊妹网站)上。如果您最终查看、使用或利用我的解决方案,请考虑给我留下反馈、建议或问题,在合理范围内,我将尽力在一天内回复。
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 object which occurs when one only defines the case class itself. For me, it turned out to be counter intuitive.
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.
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.
For example, a compiler generated
copy
method 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'sapply
method which guaranteed they could only ever contain upper case values, the following code would produce a case class instance with a lower case value: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.So, for all the various ways your case class can be used (benevolently and/or malevolently), here are the actions you must take:
apply
method with exactly the same signature as the primary constructor for your case classnew
operator and providing an empty implementation{}
{}
must be provided because the case class is declaredabstract
(see step 2.1)abstract
apply
method in the companion object which is what was causing the "method is defined twice..." compilation error (step 1.2 above)private[A]
readResolve
methodcopy
methods: String = s
)Here's your code modified with the above actions:
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:
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):
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 solution which 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.
我不知道如何重写伴随对象中的
apply
方法(如果可能的话),但您也可以对大写字符串使用特殊类型:上面的代码输出:
您还应该看看这个问题及其答案: Scala: is it possible to override default case class constructor?
I don't know how to override the
apply
method in the companion object (if that is even possible) but you could also use a special type for upper case strings:The above code outputs:
You should also have a look at this question and it's answers: Scala: is it possible to override default case class constructor?
对于 2017 年 4 月之后阅读本文的人:从 Scala 2.12.2+ 开始,Scala 允许默认情况下覆盖应用和取消应用。您也可以通过为 Scala 2.11.11+ 上的编译器提供
-Xsource:2.12
选项来获得此行为。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.12
option to the compiler on Scala 2.11.11+ as well.它适用于 var 变量:
在案例类中显然鼓励这种做法,而不是定义另一个构造函数。 请参阅此处。。复制对象时,您还保留相同的修改。
It works with var variables:
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 类并且没有隐式定义或其他构造函数的同时,另一个想法是使
apply
的签名略有不同,但从用户角度来看是相同的。我在某个地方看到了隐式技巧,但无法记住/找到它是哪个隐式参数,所以我在这里选择了
Boolean
。如果有人可以帮助我并完成这个技巧......Another idea while keeping case class and having no implicit defs or another constructor is to make the signature of
apply
slightly 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
Boolean
here. If someone can help me out and finish the trick...我遇到了同样的问题,这个解决方案对我来说没问题:
而且,如果需要任何方法,只需在特征中定义它并在案例类中覆盖它。
I faced the same problem and this solution is ok for me:
And, if any method is needed, just define it in the trait and override it in the case class.
如果你坚持使用旧版 scala,默认情况下无法覆盖,或者你不想添加编译器标志(如 @mehmet-emre 所示),并且你需要一个案例类,你可以执行以下操作:
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:
截至 2020 年,在 Scala 2.13 上,上述使用相同签名重写案例类 apply 方法的场景完全可以正常工作。
上面的代码片段在 Scala 2.13 中可以在 REPL 和 REPL 中编译并运行得很好。非 REPL 模式。
As of 2020 on Scala 2.13, the above scenario of overriding a case class apply method with same signature works totally fine.
the above snippet compiles and runs just fine in Scala 2.13 both in REPL & non-REPL modes.
我认为这已经完全按照你想要的方式工作了。这是我的 REPL 会话:
这是使用 Scala 2.8.1.final
I think this works exactly how you want it to already. Here's my REPL session:
This is using Scala 2.8.1.final