我来自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)
}
我无法使用函数体或类体中的类中的type参数来创建对象。我只能在函数参数中使用它。
这是什么原因?这是因为类型擦除吗?在运行时,该函数不知道实际类型A是什么,因此它无法创建该类型的对象?
这是一般规则是什么?这是否意味着类型参数根本不能出现在函数体或类定义中?如果他们真的可以出现,那么这些例子是什么?
答案 0 :(得分:5)
是的,你是对的,这是因为删除 - 你在运行时对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
我们在A
的定义中引用G1
,但我们只是参考我们已确认持有或可用的属性和操作在编译时。
答案 1 :(得分:4)
泛型与模板不同。在C ++中,Foo<Bar>
和Foo<Bat>
是两个不同的类,在编译时生成。
在scala或java中,Foo[T]
是具有类型参数的单个类。考虑一下:
class Foo(val bar)
class Bar[T] {
val foo = new T // if this was possible ...
}
new Bar[Foo]
在C ++中,(相当于)这将无法编译,因为没有Foo
的可访问构造函数不带参数。当编译器尝试为Bar<Foo>
类实例化模板时,编译器会知道并且失败。
在scala中,Bar [Foo]没有单独的类,因此,在编译时,编译器对T
一无所知,除了它是某种类型。它无法知道调用构造函数(或任何其他方法)是可行的还是合理的(例如,您无法实例化特征或抽象类),因此在该上下文中new T
具有失败:它根本没有意义。
粗略地说,您可以在可以使用任何类型的地方使用类型参数(例如,声明返回类型或变量),但是当您尝试仅执行某些操作时适用于某些类型,而不适用于其他类型,您必须使您的类型参数更具体。例如,这样:def foo[T](t: T) = t.intValue
不起作用,但是:def foo[T <: Number](t: T) = t.intValue
确实如此。
答案 2 :(得分:1)
编译器不知道如何创建类型为A
的实例。您需要提供返回A
实例的工厂函数,或使用Manifest
从反射创建A
实例。
有工厂功能:
class G1[A](val a:A)(f: () => A) {
val c:A = f()
}
使用Manifest
:
class G1[A](val a: A)(implicit m: scala.reflect.Manifest[A]) {
val c: A = m.erasure.newInstance.asInstanceOf[A]
}
使用type参数时,通常会指定有关A
类型的更多详细信息,除非您为A
实现某种不直接与{{1}交互的容器}。如果您需要与A
进行交互,则需要对其进行一些说明。您可以说A
必须是A
B
现在编译器会知道class G1[A <: B](val a : A)
是A
的子类,因此您可以在B
上调用B
中定义的所有函数。