Scala - 绕过覆盖的 toString 方法
Scala - bypass overridden toString method
假设 class 不幸地重写了 toString 方法,有什么方法可以绕过该方法?
即:
case class Foo(s:String){
override def toString = s
}
然后
val foo = Foo("Hello World")
println(foo)
会产生
Hello World
如果我只得到 foo
(但不是 Foo
),我可以对 foo
做任何事情以便打印
Foo(Hello World)
而不只是字符串?
不,您不能更改值的方法"ad-hoc"。您将需要依赖类型 class 而不是
trait Show[A] {
def show(x: A): String // replaces the `toString` method
}
// allows you to write foo.show instead of implicitly[Show[Foo]].show(foo)
implicit class ShowOp[A](private val x: A) extends AnyVal {
def show(implicit s: Show[A]): String = s.show(x)
}
case class Foo(s: String) { override def toString = s }
val foo = Foo("bar")
foo.show // error -- no implicit for Show[Foo]
// define the Show type class for foo
implicit object ShowFoo extends Show[Foo] {
def show(f: Foo) = s"Foo(${f.s})"
}
foo.show // ok: "Foo(bar)"
您基本上是在询问是否可以在运行时修补特定实例的 toString
方法。
这在像 scala 这样的语言中是不可能的。
您可以使用隐含的自定义方法扩展 Foo
(请参阅 0__ 的回答中的类型 class 示例),但是无法更改toString
方法。
如 0__ 所示,使用 Show
类型 class 是一个不错的选择。不幸的是 toString
是如此普遍(很多代码依赖它来格式化给定对象的字符串)以至于在很多情况下类型 class 对你没有任何好处。例如,如果您有一个 List[Foo]
,对其调用 toString
将调用 Foo.toString
。使用类型 class 的正确解决方案是为列表定义一个实例,它本身将在 Foo
实例上调用 show
。但这只会将问题推得更远,因为很可能您将此列表传递给您无法控制的某些第三方代码,并且这将调用 List.toString
(而不是 Show.show
).在这种特定情况下,唯一可行的解决方案是将 Foo
class 包装在您自己的 class(比如 MyFoo
)中并在那里覆盖 toString
。显然,这只有在您可以将 List|[Foo]
更改为 List[MyFoo]
时才有用
implicit class MyFoo(val foo: Foo) extends AnyVal {
override def toString = s"Foo(${foo.s})"
}
object MyFoo {
implicit def unwrap(myFoo: MyFoo): Foo = myFoo.foo
}
重复测试:
scala> val list1: List[Foo] = List(Foo("foo"), Foo("bar"))
list1: List[Foo] = List(foo, bar)
scala> val list2: List[MyFoo] = List(Foo("foo"), Foo("bar"))
list2: List[MyFoo] = List(Foo(foo), Foo(bar))
如您所见,list2
的字符串表示使用您自己的 toString
实现。
显然,这个解决方案并不理想。在许多情况下,它甚至可能完全不切实际(例如,您不能将 MyFoo
传递给需要 Foo
的方法)。如果有的话,它表明依赖 Any
中直接使用的 toString
方法并不是最好的设计,但是唉,我们不得不使用大量代码(包括整个 java 生态系统)就是这样做的。
与上述解决方案一样笨拙,如果您无法控制谁将调用 Foo.toString
(这意味着您无法将该调用更改为其他任何内容,例如 Show.show
)你可能无法做得更好。
假设 class 不幸地重写了 toString 方法,有什么方法可以绕过该方法?
即:
case class Foo(s:String){
override def toString = s
}
然后
val foo = Foo("Hello World")
println(foo)
会产生
Hello World
如果我只得到 foo
(但不是 Foo
),我可以对 foo
做任何事情以便打印
Foo(Hello World)
而不只是字符串?
不,您不能更改值的方法"ad-hoc"。您将需要依赖类型 class 而不是
trait Show[A] {
def show(x: A): String // replaces the `toString` method
}
// allows you to write foo.show instead of implicitly[Show[Foo]].show(foo)
implicit class ShowOp[A](private val x: A) extends AnyVal {
def show(implicit s: Show[A]): String = s.show(x)
}
case class Foo(s: String) { override def toString = s }
val foo = Foo("bar")
foo.show // error -- no implicit for Show[Foo]
// define the Show type class for foo
implicit object ShowFoo extends Show[Foo] {
def show(f: Foo) = s"Foo(${f.s})"
}
foo.show // ok: "Foo(bar)"
您基本上是在询问是否可以在运行时修补特定实例的 toString
方法。
这在像 scala 这样的语言中是不可能的。
您可以使用隐含的自定义方法扩展 Foo
(请参阅 0__ 的回答中的类型 class 示例),但是无法更改toString
方法。
如 0__ 所示,使用 Show
类型 class 是一个不错的选择。不幸的是 toString
是如此普遍(很多代码依赖它来格式化给定对象的字符串)以至于在很多情况下类型 class 对你没有任何好处。例如,如果您有一个 List[Foo]
,对其调用 toString
将调用 Foo.toString
。使用类型 class 的正确解决方案是为列表定义一个实例,它本身将在 Foo
实例上调用 show
。但这只会将问题推得更远,因为很可能您将此列表传递给您无法控制的某些第三方代码,并且这将调用 List.toString
(而不是 Show.show
).在这种特定情况下,唯一可行的解决方案是将 Foo
class 包装在您自己的 class(比如 MyFoo
)中并在那里覆盖 toString
。显然,这只有在您可以将 List|[Foo]
更改为 List[MyFoo]
implicit class MyFoo(val foo: Foo) extends AnyVal {
override def toString = s"Foo(${foo.s})"
}
object MyFoo {
implicit def unwrap(myFoo: MyFoo): Foo = myFoo.foo
}
重复测试:
scala> val list1: List[Foo] = List(Foo("foo"), Foo("bar"))
list1: List[Foo] = List(foo, bar)
scala> val list2: List[MyFoo] = List(Foo("foo"), Foo("bar"))
list2: List[MyFoo] = List(Foo(foo), Foo(bar))
如您所见,list2
的字符串表示使用您自己的 toString
实现。
显然,这个解决方案并不理想。在许多情况下,它甚至可能完全不切实际(例如,您不能将 MyFoo
传递给需要 Foo
的方法)。如果有的话,它表明依赖 Any
中直接使用的 toString
方法并不是最好的设计,但是唉,我们不得不使用大量代码(包括整个 java 生态系统)就是这样做的。
与上述解决方案一样笨拙,如果您无法控制谁将调用 Foo.toString
(这意味着您无法将该调用更改为其他任何内容,例如 Show.show
)你可能无法做得更好。