我正在努力更好地了解apply
和unapply
方法的正确用法。
考虑我们想要序列化和反序列化的对象,这是使用apply
和unapply
的正确用法(即 Scala方式)吗?
case class Foo
object Foo {
apply(json: JValue): Foo = json.extract[Foo]
unapply(f: Foo): JValue = //process to json
}
答案 0 :(得分:52)
首先,apply
和unapply
不一定是对方的对立面。实际上,如果在类/对象上定义一个,则不必定义另一个。
apply
可能更容易解释。基本上,当你将对象视为一个函数时,apply是被调用的方法,因此,Scala转向:
obj(a, b, c)
至obj.apply(a, b, c)
。
unapply
有点复杂。它用于Scala的模式匹配机制,我见过的最常用的是Extractor Objects。
例如,这是一个玩具提取器对象:
object Foo {
def unapply(x : Int) : Option[String] =
if(x == 0) Some("Hello, World") else None
}
所以现在,如果你使用它就像这样的模式匹配:
myInt match {
case Foo(str) => println(str)
}
我们假设myInt = 0
。然后会发生什么?在这种情况下,Foo.unapply(0)
会被调用,如您所见,将返回Some("Hello, World")
。 Option
的内容将分配给str
,因此最后,上述模式匹配将打印出“Hello,world”。
但如果myInt = 1
怎么办?然后Foo.unapply(1)
返回None
,因此不会调用该模式的相应表达式。
在作业的情况下,如val Foo(str) = x
,这是语法糖:
val str : String = Foo.unapply(x) match {
case Some(s) => s
case None => throw new scala.MatchError(x)
}
答案 1 :(得分:4)
因此,apply和unapply只是具有额外语法支持的def。
Apply接受参数,按照惯例,将返回与对象名称相关的值。如果我们将Scala的case类作为“正确”用法,那么对象Foo的apply将构造一个Foo实例而不需要添加“new”。您可以自由地使应用做任何你想做的事情(在Map中设置值的关键,在Set中设置包含值,并在Seq中记录索引)。
取消应用,如果返回选项或布尔值,则可以在匹配{}和模式匹配中使用。就像应用它只是一个def所以可以做任何你梦想的事情,但常见的用法是从对象的伴侣类的实例中提取值。
从我使用序列化/反序列化的库中,defs倾向于明确命名。例如,写/读,显示/读,到X / fromX等
如果您想为此目的使用apply / unapply,我建议的唯一一件就是改为
def unapply(f: Foo): Option[JValue]
然后你可以这样做:
val myFoo = Foo("""{name: "Whiskers", age: 7}""".asJson)
// use myFoo
val Foo(jval) = myFoo
// use jval
答案 2 :(得分:2)
apply
方法就像一个构造函数,它接受参数并创建一个对象,而unapply
方法则接受一个对象并尝试返回参数。
一个简单的例子:
object Foo {
def apply(name: String, suffix: String) = name + "." + suffix
def unapply(name: String): Option[(String, String)] = {
//simple argument extractor
val parts = name.split("\\.")
if (parts.length == 2) Some(parts(0), parts(1)) else None
}
}
致电时
val file = Foo("test", "txt")
它实际上调用Foo.apply("test", "txt")
并返回test.txt
如果要解构,请致电
val Foo(name) = file
这实际上会调用val name = Foo.unapply(file).get
并返回(test, txt)
(通常使用模式匹配)
顺便说一句,unapply
的返回类型按照惯例是Option
。