Scala 泛型





5.00/5 (8投票s)
继续 Scala for .NET 系列文章。这次我们将看看如何在 Scala 中使用泛型。
继续 Scala for .NET 系列文章。这次我们将看看如何在 Scala 中使用泛型。
泛型的基本用法与 .NET 中的用法相差不远,在 Scala 中您可以使用泛型方法/类。
泛型方法
这是一个泛型方法的简单示例
object ClassesDemo {
def genericPrinter[A](theStuff : A) : Unit = {
System.out.println(s"theStuff =$theStuff")
}
def main(args: Array[String]) =
{
genericPrinter("A String")
genericPrinter(12)
genericPrinter(Some(12L))
System.in.read()
()
}
}
运行后将得到以下结果
泛型类
也可以创建泛型类。 这是一个创建泛型类的示例及其用法
class printer[A](theStuff : A) {
def printIt() : Unit = {
System.out.println(s"theStuff =$theStuff")
}
}
object ClassesDemo {
def main(args: Array[String]) =
{
new printer[String]("A String").printIt()
new printer[Int](12).printIt()
new printer[Tuple2[String,Int]]("A String",12).printIt()
System.in.read()
()
}
}
运行后将得到以下结果
View Bounds (视图界定)
在 .NET 中,我们可以应用泛型约束,例如这样
public class MyGenericClass<t> where T : IComparable
{
}
在 Scala 中,这可以通过使用“View Bounds”来实现
视图界定是 Scala 中引入的一种机制,允许将某种类型 A 用作某种类型 B。
典型的语法是这样的
def f[A <% B](a: A) = a.bMethod
换句话说,A 应该具有可用的到 B 的隐式转换,以便可以对类型 A 的对象调用 B 方法。视图界定在标准库中最常见的用法(无论如何,在 Scala 2.8.0 之前)是使用 Ordered,就像这样
def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b
因为可以将 A
转换为 Ordered[A]
,并且因为 Ordered[A]
定义了方法 <(other: A): Boolean,所以我可以使用表达式 a < b。
摘自 https://docs.scala-lang.org.cn/tutorials/FAQ/context-and-view-bounds.html 截至日期 2015/11/05
Context Bounds (上下文界定)
上下文界定是在 Scala 2.8.0 中引入的,通常与所谓的类型类模式一起使用,这是一种代码模式,用于模拟 Haskell 类型类提供的功能,但方式更为冗长。
虽然视图界定可以与简单类型一起使用(例如,A <% String
),但上下文界定需要参数化类型,例如上面的 Ordered[A]
,但与 String 不同。
上下文界定描述了一个隐式值,而不是视图界定的隐式转换。 它用于声明对于某种类型 A,存在类型为 B[A]
的可用隐式值。 语法如下
def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]
这比视图界定更令人困惑,因为它不能立即清楚如何使用它。 Scala 中常见的用法示例是这样的
def f[A : ClassManifest](n: Int) = new Array[A](n)
由于与类型擦除和数组的非擦除性质相关的神秘原因,参数化类型的数组初始化需要 ClassManifest
可用。
库中的另一个非常常见的示例稍微复杂一些
def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)
这里,隐式地用于检索我们想要的隐式值,类型为 Ordering[A]
,该类定义了方法 compare(a: A, b: A): Int。
摘自 https://docs.scala-lang.org.cn/tutorials/FAQ/context-and-view-bounds.html 截至日期 2015/11/05
Type Erasure (类型擦除)
与 .NET 不同,泛型没有构建到 JVM 中,就像在 .NET 中它们实际上是 CLR 的一部分一样。
Scala 的类型在编译时被擦除。 这意味着,如果您要检查某个实例的运行时类型,您可能无法访问 Scala 编译器在编译时可用的所有类型信息。
与 scala.reflect.Manifest
类似,TypeTag 可以被认为是将编译时可用的所有类型信息传递到运行时的对象。 例如,TypeTag[T]
封装了某个编译时类型 T 的运行时类型表示。 但请注意,TypeTag 应被视为对 pre-2.10 的 Manifest 概念的更丰富的替代方案,并且还与 Scala 反射完全集成。
ClassTag / TypeTag / Manifest
这 3 个类是保持类型信息最有用的类。
让我们考虑一下这段代码
import MyExtensions._
import scala.reflect.runtime.universe._
import scala.reflect._
object ClassesDemo {
def genericMeth[A](xs: List[A]) = xs match {
case _: List[String] => "list of strings"
case _: List[Foo] => "list of foos"
}
def main(args: Array[String]) =
{
val x = System.out.print(genericMeth(List("string")))
System.in.read()
()
}
}
编译时将给出以下错误
为了解决这个问题,Manifests 被引入到 Scala 中。 但它们存在无法表示许多有用类型的问题。
TypeTag(s)/ClassTag(s) 是首选的机制。 这是再次使用 TypeTag 编写的上述代码,这次代码编译正常
import MyExtensions._
import scala.reflect.runtime.universe._
import scala.reflect._
object ClassesDemo {
def genericMeth[A : TypeTag](xs: List[A]) = typeOf[A] match {
case t if t =:= typeOf[String] => "list of strings"
case t if t <:< typeOf[Foo] => "list of foos"
}
def main(args: Array[String]) =
{
val x =
System.out.print(genericMeth(List("string")))
System.in.read()
()
}
}
我个人发现的另一个用途是使用 TypeTag/ClassTag 来帮助我创建正确类型的实例。
例如
import scala.reflect.runtime.universe._
import scala.reflect._
trait Logger {
def log() : Unit
}
class LoggerA() extends Logger {
override def log(): Unit = {
println("LoggerA.log() called")
}
}
object ClassesDemo {
def doTheLoggingClassTag[T <: Logger]()(implicit tag: scala.reflect.ClassTag[T]) = {
val theObject = tag.runtimeClass.newInstance.asInstanceOf[T]
theObject.log()
println(s"theObject $theObject")
()
}
def main(args: Array[String]) =
{
doTheLoggingClassTag[LoggerA]()
System.in.read()
()
}
}
这将给出此输出
这里有一些很棒的信息来源,这里有一些链接