Scala官方文档中对于ClassTag的定义如下:

ClassTag[T]保存着在运行时被JVM擦除的类型T的信息。当我们在运行时想获得被实例化的Array的类型信息的时候,这个特性会比较有用。

下面请看一个具体的场景:

场景

假定有一个Map[String, Any],给定一个指定的key,我们需要检查Map中是否存在该key对应的value,如果存在,则优雅地返回这个值。看起来很简单,下面让我们来实现它,并且逐渐理解ClassTag

解决方案一

我们的第一次尝试如下所示:

def main(args: Array[String]): Unit = {
class Animal
val myMap: collection.Map[String, Any] = Map("Number" -> 1, "Greeting" -> "Hello World",
"Animal" -> new Animal)
/* 下面注释的代码将会不通过编译
* Any不能被当时Int使用
*/
//val number:Int = myMap("Number")
//println("number is " + number)
//使用类型转换,可以通过编译
val number: Int = myMap("Number").asInstanceOf[Int]
println("number is " + number)
//下面的代码将会抛出ClassCastException
val greeting: String = myMap("Number").asInstanceOf[String]
}

运行结果如下:

number  is 1
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at learnscala.Test$.main(Test.scala:25)
at learnscala.Test.main(Test.scala)

上面的代码有几个很显然的问题:

首先,当我们把Any直接当成Int来使用的时候,编译器是不会通过的:

// 这将不会通过编译
val number:Int = myMap("Number")

编译器看起来没有什么错误,但是问题在于,我们没有办法使用Map中值的具体类型。换句话说,如果我们只是把值的类型设置成Any我们没办法受益于Scala提供的类型系统,所以我们需要修改代码。

为了通过编译,我们使用了如下的类型转换把获取的值变成了Int类型:

val number:Int = myMap("Number").asInstanceOf[Int]

这种做法虽然能有效果,但是当我们尝试转换一个不相关的类型的时候,asInstanceOf会抛出一个ClassCastException异常。

所以下面这行代码,会在运行时抛出异常,因为我们试图转换一个Int值为String。

val greeting:String = myMap("Number").asInstanceOf[String]

我们又引入了一个新的问题。

就算我们使用Map的get()方法,编译器还是不会通过,因为Option[Any]也没有办法当成Option[Int]来使用:

//下面的代码将不会通过编译
val number:Option[Int] = myMap.get("Number")

你可能会想什么谁的代码里面会使用Map[String, Any],这显然不是一个好的设计。但是我们先忽略这一点,并假设这个结构确实存在,下面我们回到这个问题。

使用asInstanceOf显然也不是一个好的方案,处理ClassCastException的办法之一是使用try/catch,但是这个方案并不是一个可靠并且优雅的方案,所以我们并不会采用。

解决方案二

class Animal {
override def toString = "I am Animal"
} // getValueFromMap for the Int, String and Animal
def getValueFromMapForInt(key: String, dataMap: collection.Map[String, Any]):Option[Int] =
dataMap.get(key) match {
case Some(value: Int) => Some(value)
case _ => None
} def getValueFromMapForString(key: String, dataMap: collection.Map[String, Any]: Option[String] =
dataMap.get(key) match {
case Some(value: String) => Some(value)
case _ => None
} def getValueFromMapForAnimal(key: String, dataMap: collection.Map[String, Any]: Option[Animal] =
dataMap.get(key) match {
case Some(value: Animal) => Some(value)
case _ => None
} def main(args: Array[String]): Unit = {
val myMap: collection.Map[String, Any] = Map("Number" -> 1, "Greeting" -> "Hello World", "Animal" -> new Animal)
// returns Some(1)
val number1: Option[Int] = getValueFromMapForInt("Number", myMap)
println("number is " + number1)
// returns None
val numberNotExists: Option[Int] = getValueFromMapForInt("Number2", myMap)
println("number is " + numberNotExists)
println
// returns Some(Hello World)
val greeting: Option[String] = getValueFromMapForString("Greeting", myMap)
println("greeting is " + greeting)
// returns None
val greetingDoesNotExists: Option[String] = getValueFromMapForString("Greeting1", myMap)
println("greeting is " + greetingDoesNotExists)
println()
// returns Some[Animal]
val animal: Option[Animal] = getValueFromMapForAnimal("Animal", myMap)
println("Animal is " + animal)
// returns None
val animalDoesNotExist: Option[Animal] = getValueFromMapForAnimal("Animal1", myMap)
println("Animal is " + animalDoesNotExist)
}

运行结果如下:

number is Some(1)
number is None greeting is Some(Hello World)
greeting is None Animal is Some(I am Animal)
Animal is None

现在我们使用getValueFromMapForXXX方法来获取Map中XXX类型的值,从而避免了ClassCastException

虽然解决了之前的问题,但是现在的这个解决方案任然不够好,因为,当我们增加一个新的类型的时候,就要提供一个新的getValueFromMapForXXX方法。

解决方案三

我们尝试使用类型参数来解决之前方案中getValueFromMapForXXX方法的可扩展性不足的问题。

def getValueFromMap[T](key: String, dataMap: collection.Map[String, Any]): Option[T] =
dataMap.get(key) match {
case Some(value: T) => Some(value)
case _ => None
}

现在我们只有一个getValueFromMap[T]的方法,它的参数和之前的版本一样,但是现在需要传入一个类型参数T

def getValueFromMap[T](key: String, dataMap: collection.Map[String, Any]): Option[T] =
dataMap.get(key) match {
case Some(value: T) => Some(value)
case _ => None
} def main(args: Array[String]): Unit = {
class Animal {
override def toString = "I am Animal"
}
val myMap: collection.Map[String, Any] = Map("Number" -> 1, "Greeting" -> "Hello World",
"Animal" -> new Animal)
// returns Some(1)
val number1: Option[Int] = getValueFromMap[Int]("Number", myMap)
println("number is " + number1)
// returns None
val numberNotExists: Option[Int] = getValueFromMap[Int]("Number2", myMap)
println("number is " + numberNotExists)
println
// returns Some(Hello World)
val greeting: Option[String] = getValueFromMap[String]("Greeting", myMap)
println("greeting is " + greeting)
// returns None
val greetingDoesNotExists: Option[String] = getValueFromMap[String]("Greeting1", myMap)
println("greeting is " + greetingDoesNotExists)
println()
// returns Some[Animal]
val animal: Option[Animal] = getValueFromMap[Animal]("Animal", myMap)
println("Animal is " + animal)
// returns None
val animalDoesNotExist: Option[Animal] = getValueFromMap[Animal]("Animal1", myMap)
println("Animal is " + animalDoesNotExist)
println
// 注意,这里开始出现问题了
// 现在编译器不会报错,因为所有的都发生在运行时
// 即使getValueFromMap 返回的是 Option[String]
val greetingInt: Option[Int] = getValueFromMap[Int]("Greeting", myMap)
// 输出 Some(Hello World)
println("greetingInt is " + greetingInt)
// 这里会抛出 ClassCastException
val somevalue = greetingInt.map((x) => x + 5)
// 下面的不会打印
println(somevalue)
}

运行结果如下:

number is Some(1)
number is None greeting is Some(Hello World)
greeting is None Animal is Some(I am Animal)
Animal is None greetingInt is Some(Hello World)
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:103)
at scala.runtime.java8.JFunction1$mcII$sp.apply(JFunction1$mcII$sp.java:23)
at scala.Option.map(Option.scala:163)
at learnscala.Test$.main(Test.scala:63)
at learnscala.Test.main(Test.scala)

现在,我们所需要做的就是调用getValueFromMap[T]方法,并且传递我们期待的类型和key,这样Map就可以返回我们需要的value。假如我们需要获取一个key为Number的Int类型的值,我们只需要这样做就可以了:

val number1:Option[Int] = getValueFromMap[Int]("Number", myMap)

到现在为止,我们看起来有了一个可靠的解决方案,因为我们只有一个getValeFromMap方法,并且我们也没有编译错误或者因为调用asInstanceOf不当而抛出的运行时ClassCastException异常。

在解决方案三中,我们解决了如下的几个问题:

  1. 用户需要更加了解他正在处理的类型,以减少不必要的运行时异常。
  2. 不需要asInstanceOf方法,如果key找到了,那么我们返回对应的Some[value],否则,返回None
  3. 可扩展性更好。

到现在为止看起来还好,但是就像上面代码中所展示的,当用户不小心使用了错误的类型时,还是会有错误发生。

下面的代码虽然可以正常工作,但是并不符合逻辑:

//尝试转换 Option[String] 为 Option[Int]
val greetingInt:Option[Int] = getValueFromMap[Int]("Greeting", myMap)

Map中key为Greeting的value值为String类型,但是我们把它赋值给Option[Int]。惊人的是,下面的代码可以正常工作:

// prints Some(Hello World)
println("greetingInt is " + greetingInt)

但是当我们做一个Int操作的时候,会抛出异常。

如下所示的代码,会抛出ClassCaseException

// 这行代码会抛出 ClassCastException 异常
val somevalue = greetingInt.map((x) => x + 5)
// 下面这行代码不会被打印
println(somevalue)

是不是getValueFromMap[T]需要捕获这个异常呢,我们的方法有问题吗?让我们继续看一下这个方法:

def getValueFromMap[T](key:String, dataMap: collection.Map[String, Any]): Option[T] =
dataMap.get(key) match {
case Some(value:T) => Some(value)
case _ => None
}

方法看起来是好的,我们所做的就是调用get(key)方法,并且检查value是不是T类型的,如果是,并且存在这个value,那么返回Some(value: T),否则返回None

所以,在上面的例子中,key为Greeting的value类型为Int,它应该检查Some(value: Int)是否是符合,显然,它应该返回None,因为key为Greeting的value的值为:Hello World,这是一个String而不是Int。但是我们使用了一个Int去接收。

问题在于,为什么下面这行代码没有捕捉到异常:

case Some(value:T) => Some(value)

运行时只会检查key有没有对应的value,而不会检查value是否是我们传递的T类型。因为运行时不包含我们传递的任何类型信息,在运行时,T已经被擦除不存在了。在JVM中被称为类型擦除。

所以我们最好可以保证,我们传递的类型T在运行时可以存在,去帮助运行时实现一些逻辑。下面我们引入ClassTag

我们需要做的就是传递一个隐式的ClassTag[T]参数,如果我们需要获取Map中Int类型的value时,我们需要传递一个隐式的ClassTag[Int]参数。然后,当我们调用这个方法的时候,我们可以不传递隐式参数(参考Scala隐式转换),编译器会自动为我们提供。

所以现在的getValuleFromMap看起来是下面这样的:

def getValueFromMap[T](key:String, dataMap: collection.Map[String, Any])(implicit t:ClassTag[T]): Option[T] = {
dataMap.get(key) match {
case Some(value: T) => Some(value)
case _ => None
}
}

上面的方法也可以写成这样,效果是以一样的,但是看起来优雅一些:

def getValueFromMap[T : ClassTag](key:String, dataMap: collection.Map[String, Any]): Option[T] = {
dataMap.get(key) match {
case Some(value: T) => Some(value)
case _ => None
}

方法调用不需要做任何改变,因为编译器会为我们提供隐式的ClassTag[T]参数。

最终的方案

// getValueFromMap
def getValueFromMap[T: ClassTag](key: String, dataMap: collection.Map[String, Any]): Option[T] = {
dataMap.get(key) match {
case Some(value: T) => Some(value)
case _ => None
}
} def main(args: Array[String]): Unit = {
class Animal {
override def toString = "I am Animal"
}
val myMap: collection.Map[String, Any] = Map("Number" -> 1, "Greeting" -> "Hello World",
"Animal" -> new Animal)
// returns Some(1)
val number1: Option[Int] = getValueFromMap[Int]("Number", myMap)
println("number is " + number1)
// returns None
val numberNotExists: Option[Int] = getValueFromMap[Int]("Number2", myMap)
println("number is " + numberNotExists)
println
// returns Some(Hello World)
val greeting: Option[String] = getValueFromMap[String]("Greeting", myMap)
println("greeting is " + greeting)
// returns None
val greetingDoesNotExists: Option[String] = getValueFromMap[String]("Greeting1", myMap)
println("greeting is " + greetingDoesNotExists)
println()
// returns Some[Animal]
val animal: Option[Animal] = getValueFromMap[Animal]("Animal", myMap)
println("Animal is " + animal)
// returns None
val animalDoesNotExist: Option[Animal] = getValueFromMap[Animal]("Animal1", myMap)
println("Animal is " + animalDoesNotExist)
println
// 现在这行代码没有问题,我们会得到None
val greetingInt: Option[Int] = getValueFromMap[Int]("Greeting", myMap)
// prints None
println("greetingInt is " + greetingInt)
// 没有 ClassCastException 并且返回 None
val somevalue = greetingInt.map((x) => x + 5)
// print None
println(somevalue)
println
// other map with list
val someMap = Map("Number" -> 1, "Greeting" -> "Hello World",
"Animal" -> new Animal, "goodList" -> List("good", "better", "best"))
// gets the list from map
val goodList: Option[List[String]] = getValueFromMap[List[String]]("goodList", someMap)
// prints the list
println(goodList)
println
println("Now let us try to get bad list")
// tries to get bad list from the map
val badListNotExists: Option[List[String]] = getValueFromMap[List[String]]("badList", someMap)
// prints None
println(badListNotExists)
}

代码的输出如下:

number is Some(1)
number is None greeting is Some(Hello World)
greeting is None Animal is Some(I am Animal)
Animal is None greetingInt is None
None Some(List(good, better, best)) Now let us try to get bad list
None

现在,所有的问题都解决了,感谢Scala提供的ClassTag,帮我们在运行时获得我们传递的类型参数的信息。

结论

ClassTag对于我们在运行时获得类型参数T的信息是至关重要的,在多数情况下,它会提供许多便利。然而,它也有一些局限性,我们只能从ClassTag获取更高类型的信息,而不是参数类型更高的信息,这听起来有点绕,让我们看下面的例子。

如果我们使用下面的代码:

val goodList:Option[List[Int]] = getValueFromMap[List[Int]]("goodList", someMap)
// prints the list
println(goodList)

替换:

val goodList:Option[List[String]] = getValueFromMap[List[String]]("goodList", someMap)
// prints the list
println(goodList)

它仍然能正常工作,因为ClassTag仅仅在运行时提供更高的类型信息(List)不是参数类型的信息(Int、String)。这就是即使我们使用getValueFromMap[List[Int]]去接受key为goodList的value还能正常工作的原因。但是在有些情况下,我们需要在运行时获得参数的类型信息,这时候我们就需要使用TypeTag

原文链接

参考

How to Use the Scala ClassTag

如何使用Scala的ClassTag的更多相关文章

  1. Scala Reflection - Mirrors,ClassTag,TypeTag and WeakTypeTag

    反射reflection是程序对自身的检查.验证甚至代码修改功能.反射可以通过它的Reify功能来实时自动构建生成静态的Scala实例如:类(class).方法(method).表达式(express ...

  2. Scala 深入浅出实战经典 第46讲: ClassTag 、Manifest、ClasMainifest TagType实战

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  3. Scala模式匹配和类型系统

    1.模式匹配比java中的switch case强大很多,除了值,类型,集合等进行匹配,最常见的Case class进行匹配,Master.scala有大量的模式匹配. Case "_&qu ...

  4. 了解Scala反射

    本篇文章主要让大家理解什么是Scala的反射, 以及反射的分类, 反射的一些术语概念和一些简单的反射例子. 什么是反射 我们知道, Scala是基于JVM的语言, Scala编译器会将Scala代码编 ...

  5. 一篇入门 -- Scala 反射

    本篇文章主要让大家理解什么是Scala的反射, 以及反射的分类, 反射的一些术语概念和一些简单的反射例子. 什么是反射 我们知道, Scala是基于JVM的语言, Scala编译器会将Scala代码编 ...

  6. Scala泛型[T]的使用

    package com.dtspark.scala.basics /** * 1,scala的类和方法.函数都可以是泛型. * * 2,关于对类型边界的限定分为上边界和下边界(对类进行限制) * 上边 ...

  7. Scala的类与类型

    类和类型 List<String>和List<Int>类型是不一样的,但是jvm运行时会采用泛型擦除.导致List<String>和List<Int>都 ...

  8. Scala零基础教学【41-60】

    第41讲:List继承体系实现内幕和方法操作源码揭秘 def main(args: Array[String]) { /** * List继承体系实现内幕和方法操作源码揭秘 * * List本身是一个 ...

  9. Spark createDirectStream 维护 Kafka offset(Scala)

    createDirectStream方式需要自己维护offset,使程序可以实现中断后从中断处继续消费数据. KafkaManager.scala import kafka.common.TopicA ...

随机推荐

  1. 在python_request 中 nb-log 日志模块的使用,应用到项目实际使用

    一.安装 pip install nb-log pycham 中安装: 二.基本使用 2.1 pycham中调整设置控制台日志打印出的颜色 2.2 设置完成后去掉console弹出的颜色设置 2.3  ...

  2. P1045 [NOIP2003 普及组] 麦森数

    题目描述 形如2^P−1的素数称为麦森数,这时P一定也是个素数.但反过来不一定,即如果P是个素数,2^P−1不一定也是素数. 到1998年底,人们已找到了37个麦森数.最大的一个是P=3021377, ...

  3. pytest初始化与清除fixture(二)

    @pytest.fixture用法 1.导入pytest模块:import pytest 2.调用装饰器函数:@pytest.fixture(callable_or_scope=None,*args, ...

  4. WordPress安全篇(1):WordPress网站启用HTTPS详细教程

    以前我们浏览网页使用的都是HTTP协议,HTTP使用明文传输,所以传输过程中很容易遭受黑客窃取.篡改数据,很不安全.在WordPress网站上启用HTTPS协议访问后,能大大提升站点的安全性,启用HT ...

  5. [Azure DevOps] 使用 Inno Setup 制作桌面软件安装包

    1. 桌面应用程序的 CI/CD 桌面应用程序的 CI/CD 过程和网站有一些不同,毕竟桌面应用程序的"部署"只是将安装包分发到目标位置,连应用商店都不用上,根据公司的管理流程可以 ...

  6. 【C++】map容器的用法

    检测map容器是否为空: 1 #include <iostream> 2 #include<map> 3 #include<string> 4 using name ...

  7. 04:全局解释器锁(GIL)

    1 全局解释器锁(GIL) 0 pypy(没有全局解释器锁) cpython(99.999999%)    -pypy python好多模块用不了,1 全局解释器锁,GIL锁(cpython解释器的问 ...

  8. 【题解】codeforces 219D Choosing Capital for Treeland 树型dp

    题目描述 Treeland国有n个城市,这n个城市连成了一颗树,有n-1条道路连接了所有城市.每条道路只能单向通行.现在政府需要决定选择哪个城市为首都.假如城市i成为了首都,那么为了使首都能到达任意一 ...

  9. Docker笔记--镜像&基于GO项目创建Docker镜像

    Docker笔记--镜像&基于GO项目创建Docker镜像 核心概念 Doker镜像--包含一个基本的操作系统运行环境和应用程序,镜像是创建Docker容器的基础. Docker容器--如果把 ...

  10. 六QT使用mqtt

    QT官方的mqtt是qmqtt,头文件是 #include <qmqttclient.h> 官方的文档地址 https://doc.qt.io/QtMQTT/qmqttclient.htm ...