scala - 类型参数不能在 Scala 的函数体中引用?

标签 scala

我来自 C++ 世界并且是 Scala 的新手,这种行为看起来很不寻常。

class G1[A]( val a : A) {
  //val c:A = new A //This gives compile error
  def fcn1(b: A): Unit = {
    //val aobj = new A // This gives compile error
    println(a.getClass.getSimpleName)
    println(b.getClass.getSimpleName)
  }
}

def fcnWithTP[A](): Unit = {
  //val a = new A // This gives compile error
  //println(a.getClass.getSimpleName)
}

我无法在函数体或类体的类中使用类型参数创建对象。我只能在函数参数中使用它。

这是什么原因?这是因为类型删除吗?在运行时,函数不知道实际的类型 A 是什么,所以它不能创建该类型的对象?

这样做的一般规则是什么?这是否意味着类型参数根本不能出现在函数体或类定义中?如果它们真的能出现,有哪些例子?

最佳答案

是的,你说得对,这是因为删除——你对 A 一无所知。在运行时,您没有明确声明它是方法签名中的约束。

JVM 上的类型删除只是部分的,所以你可以在 Scala 中做一些可怕的事情,比如请求一个值的类:

scala> List(1, 2, 3).getClass
res0: Class[_ <: List[Int]] = class scala.collection.immutable.$colon$colon

但是,一旦您使用泛型,所有内容都会被删除,因此例如您无法区分以下内容:
scala> List(1, 2, 3).getClass == List("a", "b", "c").getClass
res1: Boolean = true

(如果不清楚,我认为类型删除无疑是一件好事,JVM 上类型删除的唯一问题是它不够完整。)

您可以编写以下内容:
import scala.reflect.{ ClassTag, classTag }

class G1[A: ClassTag](val a: A) {
  val c: A = classTag[A].runtimeClass.newInstance().asInstanceOf[A]
}

并像这样使用它:
scala> val stringG1: G1[String] = new G1("foo")
stringG1: G1[String] = G1@33d71170

scala> stringG1.c
res2: String = ""

但是,这是一个非常糟糕的主意,因为它会在运行时因许多类型参数而崩溃:
scala> class Foo(i: Int)
defined class Foo

scala> val fooG1: G1[Foo] = new G1(new Foo(0))
java.lang.InstantiationException: Foo
  at java.lang.Class.newInstance(Class.java:427)
  ... 43 elided
Caused by: java.lang.NoSuchMethodException: Foo.<init>()
  at java.lang.Class.getConstructor0(Class.java:3082)
  at java.lang.Class.newInstance(Class.java:412)
  ... 43 more

更好的方法是传入构造函数:
class G1[A](val a: A)(empty: () => A) {
  val c: A = empty()
}

更好的方法是使用类型类:
trait Empty[A] {
  def default: A
}

object Empty {
  def instance[A](a: => A): Empty[A] = new Empty[A] {
    def default: A = a
  }

  implicit val stringEmpty: Empty[String] = instance("")
  implicit val fooEmpty: Empty[Foo] = instance(new Foo(0))
}

class G1[A: Empty](val a: A) {
  val c: A = implicitly[Empty[A]].default
}

进而:
scala> val fooG1: G1[Foo] = new G1(new Foo(10101))
fooG1: G1[Foo] = G1@5a34b5bc

scala> fooG1.c
res0: Foo = Foo@571ccdd0

这里我们指的是AG1的定义中,但我们只引用我们已确认保留或在编译时可用的属性和操作。

关于scala - 类型参数不能在 Scala 的函数体中引用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36388265/

相关文章:

scala - 位图向量 trie 为何比普通向量更快?

java - Spark 使用 sc.textFile 从 S3 读取文件(“s3n ://. ..)

scala - 帮助我理解这个 Scala 代码 : scalaz IO Monad

scala - 是否可以扩展 Scala 编译器来推断递归方法的返回类型?

java - 压缩:在Java/Scala中以指定比特率保存JPEG图像

scala - 这可以用 Scala 宏来完成吗?

scala - Maven setting.xml 等效于 sbt

scala - 升级到 Sbt 0.13.8,但对于带有美元符号的字符串出现多个警告 "detected an interpolated expression"

scala - 在 Scala 2 中,使用 .toSet 生成的 Set 类型推断失败?

scala-尝试打印覆盖的toString方法