在 Scala 中对泛型类型使用 asInstanceOf
Using asInstanceOf on Generic Types in Scala
说,我有一个 class 这样的:
class Funky[A, B](val foo: A, val bar: B) {
override def toString: String = s"Funky($foo, $bar)"
}
使用如下所示的一些方法:
def cast(t: Any): Option[Funky[A, B]] = {
if (t == null) None
else if (t.isInstanceOf[Funky[_, _]]) {
val o = t.asInstanceOf[Funky[_, _]]
for {
_ <- typA.cast(o.foo)
_ <- typB.cast(o.bar)
} yield o.asInstanceOf[Funky[A, B]]
} else None
}
isInstanceOf 和 asInstanceOf 是如何工作的?运行时没有关于 Funky 中包含的实际类型的信息。那么这段代码是如何工作的呢?有什么线索吗?
您可以检查元素是否为 特定 类型,例如:
Funky(1, 2).foo.isInstanceOf[String] // false
Funky(1, 2).foo.isInstanceOf[Int] // true
但是如果您尝试检查 generic 类型,它将不起作用。例如:
def check[A](x: Any) = x.isInstanceOf[A]
check[String](1) // true
check[String](Funky(1, 2).foo) // true
并且编译器会给出一条解释错误的警告消息:
abstract type A is unchecked since it is eliminated by erasure
但是,您显示的代码似乎可以通过此处的其他方法解决此问题:
_ <- typA.cast(o.foo)
_ <- typB.cast(o.bar)
没有看到这些对象的实现,我猜他们有某种 TypeTag
或 ClassTag
并使用它。这几乎总是解决擦除问题的推荐方法。
我们拆开看吧
假设您以某种方式获得实例 typA: Typable[A]
和 typB: Typable[B]
,因此 typA
有一个方法
def cast(a: Any): Option[A] = ...
typB
也是如此。如果参数确实是 A
类型,则 cast
方法应为 return Some[A]
,否则为 None
。这些实例显然可以为原始类型 Int
和 String
构造(它们已经由库提供)。
现在您想使用 typA
和 typB
为 Funky[A, B]
实现 cast
。
null
检查应该很清楚,你可以对任何东西执行它。但接下来是第一个 isInstanceOf
:
else if (t.isInstanceOf[Funky[_, _]]) {
请注意,Funky
的类型参数已被下划线替换。这是因为 Funky
的通用参数 被擦除,并且在 运行 时不可用。然而,我们仍然可以区分 Funky[_, _]
和 Map[_, _]
,因为参数化类型本身被保留,即使参数被擦除。此外,isInstanceOf
甚至可以区分 TreeMap
和 HashMap
,即使两个实例都有编译时类型 Map
:运行time 类型可用,只是忘记了通用参数。
同样,一旦知道 t
是 Funky
类型,就可以将其转换为
Funky[_, _]
、
val o = t.asInstanceOf[Funky[_, _]]
用现有类型替换泛型参数。这意味着:你只知道有 some 类型 X
和 Y
这样 o
是 Funky[X, Y]
类型,但是你不知道 X
和 Y
是什么。但是,现在您至少知道 o
有方法 foo
和 bar
(即使您不知道它们的 return 类型是什么)。
但现在您可以将 o.foo
和 o.bar
放入 typA.cast
和 typeB.cast
中。 monadic 绑定左侧的下划线表示您丢弃 A
和 B
类型的实例,它们被 return 包裹在 Some
中。你只关心两个演员都不 return a None
:
for {
_ <- typA.cast(o.foo)
_ <- typB.cast(o.bar)
} yield /* ... */
如果其中一个转换失败并且 return 编辑了一个 None
,整个单子表达式的计算结果将是 None
,因此该方法将 return None
,表示整体转换为Funky[A, B]
失败。
如果两个转换都成功,那么我们知道 o
确实是 Funky[A, B]
类型,所以我们可以将 o
转换为 Funky[A, B]
:
o.asInstanceOf[Funky[A, B]]
你可能会想 "how is this possible, we don't know anything about A
and B
at runtime!",但这没关系,因为这个 asInstanceOf
只是为了满足编译器的类型检查阶段。它不能在运行时间做任何事情,因为它只能检查Funky
部分,而不能检查擦除的参数A
和B
].
以下是该现象的简短说明:
val m: Map[Long, Double] = Map(2L -> 100d)
val what = m.asInstanceOf[Map[Int, String]]
println("It compiles, and the program does not throw any exceptions!")
只需将其保存为脚本并将其提供给 scala
解释器。它会毫无怨言地编译和 运行,因为 asInstanceOf
对 Map
之外的任何东西都是盲目的。所以,第二个 asInstanceOf
只是为了说服类型检查器 returned 值确实是 Funky[A, B]
类型,程序员有责任不做任何荒谬的说法。
总结一下:isInstanceOf
是在 运行 时间做某事的东西(它检查实例是否符合某种具体类型,并且 returns 运行 时间-值 true
-false
)。 asInstanceOf
有两个不同的功能。第一个(转换为具体的 class Funky[_, _]
)在 运行 时有副作用(转换可能会失败并抛出异常)。第二个(asInstanceOf[Funky[A, B]]
)只是为了满足编译时的类型检查阶段。
希望这能有所帮助。
说,我有一个 class 这样的:
class Funky[A, B](val foo: A, val bar: B) {
override def toString: String = s"Funky($foo, $bar)"
}
使用如下所示的一些方法:
def cast(t: Any): Option[Funky[A, B]] = {
if (t == null) None
else if (t.isInstanceOf[Funky[_, _]]) {
val o = t.asInstanceOf[Funky[_, _]]
for {
_ <- typA.cast(o.foo)
_ <- typB.cast(o.bar)
} yield o.asInstanceOf[Funky[A, B]]
} else None
}
isInstanceOf 和 asInstanceOf 是如何工作的?运行时没有关于 Funky 中包含的实际类型的信息。那么这段代码是如何工作的呢?有什么线索吗?
您可以检查元素是否为 特定 类型,例如:
Funky(1, 2).foo.isInstanceOf[String] // false
Funky(1, 2).foo.isInstanceOf[Int] // true
但是如果您尝试检查 generic 类型,它将不起作用。例如:
def check[A](x: Any) = x.isInstanceOf[A]
check[String](1) // true
check[String](Funky(1, 2).foo) // true
并且编译器会给出一条解释错误的警告消息:
abstract type A is unchecked since it is eliminated by erasure
但是,您显示的代码似乎可以通过此处的其他方法解决此问题:
_ <- typA.cast(o.foo)
_ <- typB.cast(o.bar)
没有看到这些对象的实现,我猜他们有某种 TypeTag
或 ClassTag
并使用它。这几乎总是解决擦除问题的推荐方法。
我们拆开看吧
假设您以某种方式获得实例 typA: Typable[A]
和 typB: Typable[B]
,因此 typA
有一个方法
def cast(a: Any): Option[A] = ...
typB
也是如此。如果参数确实是 A
类型,则 cast
方法应为 return Some[A]
,否则为 None
。这些实例显然可以为原始类型 Int
和 String
构造(它们已经由库提供)。
现在您想使用 typA
和 typB
为 Funky[A, B]
实现 cast
。
null
检查应该很清楚,你可以对任何东西执行它。但接下来是第一个 isInstanceOf
:
else if (t.isInstanceOf[Funky[_, _]]) {
请注意,Funky
的类型参数已被下划线替换。这是因为 Funky
的通用参数 被擦除,并且在 运行 时不可用。然而,我们仍然可以区分 Funky[_, _]
和 Map[_, _]
,因为参数化类型本身被保留,即使参数被擦除。此外,isInstanceOf
甚至可以区分 TreeMap
和 HashMap
,即使两个实例都有编译时类型 Map
:运行time 类型可用,只是忘记了通用参数。
同样,一旦知道 t
是 Funky
类型,就可以将其转换为
Funky[_, _]
、
val o = t.asInstanceOf[Funky[_, _]]
用现有类型替换泛型参数。这意味着:你只知道有 some 类型 X
和 Y
这样 o
是 Funky[X, Y]
类型,但是你不知道 X
和 Y
是什么。但是,现在您至少知道 o
有方法 foo
和 bar
(即使您不知道它们的 return 类型是什么)。
但现在您可以将 o.foo
和 o.bar
放入 typA.cast
和 typeB.cast
中。 monadic 绑定左侧的下划线表示您丢弃 A
和 B
类型的实例,它们被 return 包裹在 Some
中。你只关心两个演员都不 return a None
:
for {
_ <- typA.cast(o.foo)
_ <- typB.cast(o.bar)
} yield /* ... */
如果其中一个转换失败并且 return 编辑了一个 None
,整个单子表达式的计算结果将是 None
,因此该方法将 return None
,表示整体转换为Funky[A, B]
失败。
如果两个转换都成功,那么我们知道 o
确实是 Funky[A, B]
类型,所以我们可以将 o
转换为 Funky[A, B]
:
o.asInstanceOf[Funky[A, B]]
你可能会想 "how is this possible, we don't know anything about A
and B
at runtime!",但这没关系,因为这个 asInstanceOf
只是为了满足编译器的类型检查阶段。它不能在运行时间做任何事情,因为它只能检查Funky
部分,而不能检查擦除的参数A
和B
].
以下是该现象的简短说明:
val m: Map[Long, Double] = Map(2L -> 100d)
val what = m.asInstanceOf[Map[Int, String]]
println("It compiles, and the program does not throw any exceptions!")
只需将其保存为脚本并将其提供给 scala
解释器。它会毫无怨言地编译和 运行,因为 asInstanceOf
对 Map
之外的任何东西都是盲目的。所以,第二个 asInstanceOf
只是为了说服类型检查器 returned 值确实是 Funky[A, B]
类型,程序员有责任不做任何荒谬的说法。
总结一下:isInstanceOf
是在 运行 时间做某事的东西(它检查实例是否符合某种具体类型,并且 returns 运行 时间-值 true
-false
)。 asInstanceOf
有两个不同的功能。第一个(转换为具体的 class Funky[_, _]
)在 运行 时有副作用(转换可能会失败并抛出异常)。第二个(asInstanceOf[Funky[A, B]]
)只是为了满足编译时的类型检查阶段。
希望这能有所帮助。