我在scala中写了这个,它不会编译:
class TestDoubleDef{
def foo(p:List[String]) = {}
def foo(p:List[Int]) = {}
}
编译通知:
[error] double definition:
[error] method foo:(List[String])Unit and
[error] method foo:(List[Int])Unit at line 120
[error] have same type after erasure: (List)Unit
我知道JVM对泛型没有原生支持,所以我理解这个错误。
我可以为List[String]
和List[Int]
编写包装,但我很懒:)
我很怀疑,但有没有另一种方式表达List[String]
与List[Int]
的类型不同?
感谢。
答案 0 :(得分:48)
我喜欢MichaelKrämer使用含义的想法,但我认为它可以更直接地应用:
case class IntList(list: List[Int])
case class StringList(list: List[String])
implicit def il(list: List[Int]) = IntList(list)
implicit def sl(list: List[String]) = StringList(list)
def foo(i: IntList) { println("Int: " + i.list)}
def foo(s: StringList) { println("String: " + s.list)}
我认为这是非常易读和直截了当的。
<强> [更新] 强>
还有另一种简单的方法似乎有效:
def foo(p: List[String]) { println("Strings") }
def foo[X: ClassManifest](p: List[Int]) { println("Ints") }
def foo[X: ClassManifest, Y: ClassManifest](p: List[Double]) { println("Doubles") }
对于每个版本,您需要一个额外的类型参数,因此这不会扩展,但我认为对于三个或四个版本来说没问题。
[更新2]
对于两种方法,我发现了另一个不错的技巧:
def foo(list: => List[Int]) = { println("Int-List " + list)}
def foo(list: List[String]) = { println("String-List " + list)}
答案 1 :(得分:48)
您可以使用DummyImplicit
中定义的Predef
,而不是发明虚拟隐式值,而class TestMultipleDef {
def foo(p:List[String]) = ()
def foo(p:List[Int])(implicit d: DummyImplicit) = ()
def foo(p:List[java.util.Date])(implicit d1: DummyImplicit, d2: DummyImplicit) = ()
}
似乎完全是为了这一点:
{{1}}
答案 2 :(得分:10)
由于类型擦除的奇迹,您的方法列表的类型参数在编译期间被擦除,从而将两种方法都减少到相同的签名,这是编译器错误。
答案 3 :(得分:10)
要理解Michael Krämer's solution,必须认识到隐式参数的类型并不重要。 重要的是它们的类型是不同的。
以下代码的工作方式相同:
class TestDoubleDef {
object dummy1 { implicit val dummy: dummy1.type = this }
object dummy2 { implicit val dummy: dummy2.type = this }
def foo(p:List[String])(implicit d: dummy1.type) = {}
def foo(p:List[Int])(implicit d: dummy2.type) = {}
}
object App extends Application {
val a = new TestDoubleDef()
a.foo(1::2::Nil)
a.foo("a"::"b"::Nil)
}
在字节码级别,两个foo
方法都成为双参数方法,因为JVM字节码不知道隐式参数或多个参数列表。在调用站点,Scala编译器通过查看传入的列表类型(直到稍后才擦除)来选择要调用的相应foo
方法(因此传入适当的虚拟对象)。
虽然它更冗长,但这种方法减轻了调用者提供隐式参数的负担。实际上,如果dummyN对象是TestDoubleDef
类的私有对象,它甚至可以工作。
答案 4 :(得分:8)
正如Viktor Klang所说,泛型类型将被编译器删除。幸运的是,有一个解决方法:
class TestDoubleDef{
def foo(p:List[String])(implicit ignore: String) = {}
def foo(p:List[Int])(implicit ignore: Int) = {}
}
object App extends Application {
implicit val x = 0
implicit val y = ""
val a = new A()
a.foo(1::2::Nil)
a.foo("a"::"b"::Nil)
}
感谢Michid提示!
答案 5 :(得分:6)
如果我在这里结合Daniel s response和Sandor Murakozi的回复,我会得到:
@annotation.implicitNotFound(msg = "Type ${T} not supported only Int and String accepted")
sealed abstract class Acceptable[T]; object Acceptable {
implicit object IntOk extends Acceptable[Int]
implicit object StringOk extends Acceptable[String]
}
class TestDoubleDef {
def foo[A : Acceptable : Manifest](p:List[A]) = {
val m = manifest[A]
if (m equals manifest[String]) {
println("String")
} else if (m equals manifest[Int]) {
println("Int")
}
}
}
我得到了一个类型安全(ish)变体
scala> val a = new TestDoubleDef
a: TestDoubleDef = TestDoubleDef@f3cc05f
scala> a.foo(List(1,2,3))
Int
scala> a.foo(List("test","testa"))
String
scala> a.foo(List(1L,2L,3L))
<console>:21: error: Type Long not supported only Int and String accepted
a.foo(List(1L,2L,3L))
^
scala> a.foo("test")
<console>:9: error: type mismatch;
found : java.lang.String("test")
required: List[?]
a.foo("test")
^
逻辑也可以包含在类型类中(感谢jsuereth): @ annotation.implicitNotFound(msg =“Foo不支持$ {T}只接受Int和String”) 密封特性Foo [T] {def apply(list:List [T]):Unit}
object Foo {
implicit def stringImpl = new Foo[String] {
def apply(list : List[String]) = println("String")
}
implicit def intImpl = new Foo[Int] {
def apply(list : List[Int]) = println("Int")
}
}
def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)
给出了:
scala> @annotation.implicitNotFound(msg = "Foo does not support ${T} only Int and String accepted")
| sealed trait Foo[T] { def apply(list : List[T]) : Unit }; object Foo {
| implicit def stringImpl = new Foo[String] {
| def apply(list : List[String]) = println("String")
| }
| implicit def intImpl = new Foo[Int] {
| def apply(list : List[Int]) = println("Int")
| }
| } ; def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)
defined trait Foo
defined module Foo
foo: [A](x: List[A])(implicit evidence$1: Foo[A])Unit
scala> foo(1)
<console>:8: error: type mismatch;
found : Int(1)
required: List[?]
foo(1)
^
scala> foo(List(1,2,3))
Int
scala> foo(List("a","b","c"))
String
scala> foo(List(1.0))
<console>:32: error: Foo does not support Double only Int and String accepted
foo(List(1.0))
^
请注意,我们必须编写implicitly[Foo[A]].apply(x)
,因为编译器认为implicitly[Foo[A]](x)
表示我们使用参数调用implicitly
。
答案 6 :(得分:3)
有(至少一种)另一种方式,即使它不太好而且不是真正的类型安全:
import scala.reflect.Manifest
object Reified {
def foo[T](p:List[T])(implicit m: Manifest[T]) = {
def stringList(l: List[String]) {
println("Strings")
}
def intList(l: List[Int]) {
println("Ints")
}
val StringClass = classOf[String]
val IntClass = classOf[Int]
m.erasure match {
case StringClass => stringList(p.asInstanceOf[List[String]])
case IntClass => intList(p.asInstanceOf[List[Int]])
case _ => error("???")
}
}
def main(args: Array[String]) {
foo(List("String"))
foo(List(1, 2, 3))
}
}
隐式清单参数可用于“重新统一”擦除类型,从而破解擦除。您可以在许多博客文章中了解更多信息,例如。 this one
发生的事情是,清单参数可以在删除之前回复T。然后基于T对各种实际实现进行简单的调度完成其余的工作。
可能有更好的方法来进行模式匹配,但我还没有看到它。人们通常做的是匹配m.toString,但我认为保持类更清晰(即使它更冗长)。不幸的是,Manifest的文档不是太详细,也许它还有一些可以简化它的东西。
它的一个很大的缺点是它不是真正的类型安全:foo会对任何T感到满意,如果你不能处理它你会遇到问题。我想它可以解决T上的一些约束,但它会使它进一步复杂化。
当然这整件事情也不太好,我不确定是否值得这样做,特别是如果你很懒; - )
答案 7 :(得分:1)
答案 8 :(得分:0)
我从http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic-type-erasure-td2327664.html找到的好戏法 作者:Aaron Novstrup
再打这匹死马......
在我看来,更干净的黑客是使用独特的虚拟类型 对于签名中具有已擦除类型的每种方法:
object Baz {
private object dummy1 { implicit val dummy: dummy1.type = this }
private object dummy2 { implicit val dummy: dummy2.type = this }
def foo(xs: String*)(implicit e: dummy1.type) = 1
def foo(xs: Int*)(implicit e: dummy2.type) = 2
}
[...]
答案 9 :(得分:0)
我尝试改进Aaron Novstrup和Leo的答案,使一组标准证据对象可导入且更简洁。
final object ErasureEvidence {
class E1 private[ErasureEvidence]()
class E2 private[ErasureEvidence]()
implicit final val e1 = new E1
implicit final val e2 = new E2
}
import ErasureEvidence._
class Baz {
def foo(xs: String*)(implicit e:E1) = 1
def foo(xs: Int*)(implicit e:E2) = 2
}
但是当foo
调用另一个需要相同类型的隐式参数的方法时,这会导致编译器抱怨隐含值存在模糊选择。
因此,我只提供以下内容,在某些情况下更为简洁。这种改进适用于价值类(那些extend AnyVal
)。
final object ErasureEvidence {
class E1[T] private[ErasureEvidence]()
class E2[T] private[ErasureEvidence]()
implicit def e1[T] = new E1[T]
implicit def e2[T] = new E2[T]
}
import ErasureEvidence._
class Baz {
def foo(xs: String*)(implicit e:E1[Baz]) = 1
def foo(xs: Int*)(implicit e:E2[Baz]) = 2
}
如果包含的类型名称相当长,请声明内部trait
以使其更简洁。
class Supercalifragilisticexpialidocious[A,B,C,D,E,F,G,H,I,J,K,L,M] {
private trait E
def foo(xs: String*)(implicit e:E1[E]) = 1
def foo(xs: Int*)(implicit e:E2[E]) = 2
}
但是,值类不允许内部特征,类和对象。因此,请注意Aaron Novstrup和Leo的答案不适用于价值类。
答案 10 :(得分:-3)
我没有对此进行测试,但为什么上限不起作用?
def foo[T <: String](s: List[T]) { println("Strings: " + s) }
def foo[T <: Int](i: List[T]) { println("Ints: " + i) }
擦除转换是否从foo(List [Any] s)改为两次,改为foo(List [String] s)和foo(List [Int] i):
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ108
我想我在版本2.8中读到,上限现在以这种方式编码,而不是始终是Any。
要在协变类型上重载,请使用不变的边界(在Scala中是否有这样的语法?...啊我认为没有,但请将以下内容作为上述主要解决方案的概念附录):
def foo[T : String](s: List[T]) { println("Strings: " + s) }
def foo[T : String2](s: List[T]) { println("String2s: " + s) }
然后我假设在删除的代码版本中消除了隐式转换。
更新:问题是JVM在方法签名上删除了比“必要”更多的类型信息。我提供了一个链接。它从类型构造函数中删除类型变量,甚至是这些类型变量的具体边界。有一个概念上的区别,因为擦除函数的类型绑定没有概念上的非实现优势,因为它在编译时是已知的并且不随通用的任何实例而变化,并且调用者不需要调用具有不符合类型绑定的类型的函数,那么如果JVM被删除,JVM如何强制执行类型绑定?好one link表示类型绑定保留在编译器应该访问的元数据中。这解释了为什么使用类型边界不会启用重载。这也意味着JVM是一个广泛开放的安全漏洞,因为可以在没有类型边界的情况下调用类型有界方法(yikes!),所以请原谅我假设JVM设计者不会做这样一个不安全的事情。
在我写这篇文章的时候,我不明白stackoverflow是一个按照一些竞争而不是声誉的答案质量对人们进行评级的系统。我认为这是一个分享信息的地方。在我写这篇文章的时候,我从概念层面(比较许多不同的语言)比较了具体化和非具体化,因此在我看来,删除类型边界没有任何意义。