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. Java知识,面试总会问到虚拟机,虚拟机类加载机制你懂吗?

    虚拟机把描述类的数据从Class文件文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 与那些在编译时需要进行连接工作的语言不同 ...

  2. Nginx为什么快到根本停不下来?

    Nginx 是一个免费的,开源的,高性能的 HTTP 服务器和反向代理,以及 IMAP / POP3 代理服务器. 图片来自 Pexels Nginx 以其高性能,稳定性,丰富的功能,简单的配置和低资 ...

  3. ES6学习笔记之 let与const

    在js中,定义变量时要使用var操作符,但是var有许多的缺点,如:一个变量可以重复声明.没有块级作用域.不能限制修改等. //缺点1:变量可以重复声明 var a=1; var a=2; conso ...

  4. Html中的canvas

    使用cancas完成袋鼠跳跃的界面,这个是怎么做得呀,我哭了呀

  5. Zabbix5.0钉钉报警(centos7)

    2.1.到钉钉官网下载pc版钉钉,安装.注册.登陆: 钉钉下载地址:https://www.dingtalk.com/ 2.2.创建群聊和钉钉机器人: 1.创建群聊,把需要收到报警的人员都拉到这个群: ...

  6. C# DataGridView单元格画斜线

    功能要求:不符合条件的单元格使用斜线形式表现出来. 1.定义两个变量,一个是存储单元格位置的数组,一个是Graphics 变量 Graphics gdi; List<DataGridViewCe ...

  7. 使用gitlab自带的ci/cd实现.net core应用程序的部署

    这两天在折腾持续集成和交付,公司考虑使用gitlab自带的ci/cd来处理,特此记下来整个流程步骤. 好记性不如一支烂笔头---尼古拉斯-古人言 第一步: 安装gitlab,这个自然不用多说 第二步: ...

  8. CentOS-搭建MinIO集群

    一.基础环境 操作系统:CentOS 7.x Minio在线演示 Minio下载 二.准备工作 2.1.机器资源 192.168.1.101 /data1 192.168.1.102 /data2 1 ...

  9. Docker:Linux离线安装docker-compose

    1)首先访问 docker-compose 的 GitHub 版本发布页面 https://github.com/docker/compose/releases 2)由于服务器是 CentOS 系统, ...

  10. Linux使用shell脚本监控

    (1)性能监控脚本 performance.sh #!/bin/bash #-------------------------------------------------------------- ...