Scala入门到精通——第二十四节 高级类型 (三)
本节主要内容
- Type Specialization
- Manifest、TypeTag、ClassTag
- Scala类型系统总结
在Scala中,类(class)与类型(type)是两个不一样的概念。我们知道类是对同一类型数据的抽象,而类型则更具体。比如定义class List[T] {}, 可以有List[Int] 和 List[String]等具体类型,称List为类,而List[Int]、List[String]则为类型。从这方面看:类型一致的对象它们的类也是一致的;而类一致的,其类型不一定一致。例如:
//List[Int]与List[String]它们的类是相同的即List
scala> classOf[List[Int]] == classOf[List[String]]
res1: Boolean = truescala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
//类相同,但它们的类型是不一样的
scala> typeOf[List[Int]] == typeOf[List[String]]
res3: Boolean = false
1. Type Specialization
Type Specialization,一般被翻译成类型专门化,它主要是用来解决泛型的类型擦除和自动装箱拆箱的问题。在Java语言当中,泛型生成字节码文件时会进行泛型类型擦除,类型擦除后利用上界类型(一般是Object)来替代,但这么做的话有问题,这是因为在Java语言中基本类型与对象类型是不能相互引用的,java中的基本类型不能使用泛型。解决方案是利用对应的对象类型来进行替代,例如int对应Integer类型,但这种方式并不能解决根本问题。为方便后面Type Specialization的理解,我们先从java的类型擦除、自装箱与拆箱讲起。
1 类型擦除
假设我们利用Java泛型定义了下面的Person类:
//Java泛型类
public class Person<T> {private T firstName;private T secondName;public Person(T firstName,T secondName){this.firstName=firstName;this.secondName=secondName;}public T getFirstName() {return firstName;}public void setFirstName(T firstName) {this.firstName = firstName;}public T getSecondName() {return secondName;}public void setSecondName(T secondName) {this.secondName = secondName;}}
- 22
经过类型擦除后,最终变为:
public class Person {private Object firstName;private Object secondName;public Person(Object firstName,Object secondName){this.firstName=firstName;this.secondName=secondName;}public Object getFirstName() {return firstName;}public void setFirstName(Object firstName) {this.firstName = firstName;}public Object getSecondName() {return secondName;}public void setSecondName(Object secondName) {this.secondName = secondName;}}
- 21
经过类型擦除后的类称为原始类型,从这点来看,java中的泛型其实是一个伪泛型,它只在编译层次进行实现,在生成字码码这部分泛型信息被擦除。下面的例子证明也证明了这一点:
public static void main(String[] args) {Person<String> p1=new Person<String>("张", "三");Person<Integer> p2=new Person<Integer>(1, 23);//下面的代码返回的是trueSystem.out.println(p1.getClass()==p2.getClass());}
java中的类型擦除会引起一些问题,具体可以参考http://blog.csdn.net/lonelyroamer/article/details/7868820
2 自动装箱与拆箱
在前面给的示例代码中,我们直接使用
Person<Integer> p2=new Person<Integer>(1, 23);
需要注意的是这里使用的是java的基本类型进行对象的创建,而给定的具体类型是Integer,此时Java会帮我们自动进行转换,这个转换操作被称为自动装箱(autoboxing),上面的代码相当于:Person<Integer> p2=new Person<Integer>(Integer.valueOf(1), Integer.valueOf(23));
。
下面的代码演示了拆箱(unboxing)
//Integer firstName =Integer.valueOf(23)
Integer firstName = 23; //自动装箱
//拆箱,实际执行 int name = firstName .intValue();
int name = firstName ;
自动装箱与拆箱需要损耗一定的性能,当性能要求较高时需要程序员手动云进行转换。scala中的Type Specialization解决了这些问题。它的语法很简单,通过注解进行类型专门化声明,如:
//在泛型参数前面加@specialized进行Type Specialization
abstract class List[@specialized T]{def apply(x:T)def map[S](f:T=>S)
}
上述代码编译后会生成下列字代码文件,如下图
从图中可以看到,共生成了九个版本的List,其中这九个文件分别对应scala中的九种基本类型即Unit, Boolean, Byte, Short, Char, Int, Long, Float, Double。感兴趣的可以利用javap命令进行查看,这里给出其Byte类型的实现:
D:\ScalaWorkspace\ScalaChapter24\bin\cn\scala\xtwy\advancedtype>javap -private L
ist$mcB$sp.class
Compiled from "TypeSpecilization.scala"
public abstract class cn.scala.xtwy.advancedtype.List$mcB$sp extends cn.scala.xt
wy.advancedtype.List<java.lang.Object> {public abstract void apply(byte);public abstract <S extends java/lang/Object> void map(scala.Function1<java.lan
g.Object, S>);public cn.scala.xtwy.advancedtype.List$mcB$sp();
}
@specialized 还可以更细致,限定某个或几个基本类型,例如:
abstract class List[@specialized T]{//指定生成Int类型的版本def apply[@specialized (Int) S](x:S):S指定生成Boolean及Double类型的版本def map[@specialized (Boolean,Double) S](f:T=>S)
}
- 1
在上一讲中我们看到了Function1及Function2的类定义中也使用@specialize进行注解,如:
@annotation.implicitNotFound(msg = "No implicit view available from ${T1} => ${R}.")
trait Function1[@specialized(scala.Int, scala.Long,scala.Float, scala.Double/*, scala.AnyRef*/) -T1,scala.Float, scala.Long, scala.Double/*,scala.AnyRef*/) +R] extends AnyRef
可以看到,Function1类也进行了类型专门化。
2. Manifest、TypeTag、ClassTag
本节内容大多来源于自官方文档http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html,大家在学习的时候,可看打开API文档,对本节内容进行理解。
由于类型擦除的影响,编译期存在的类型信息在编译后不存在了,在程序运行时不能获取该信息,但某些场景下可能需要得到编译期的类型信息,scala能够做到这一点,它通过Manifest和TypeTag来保存类型信息并在运行时使用该信息。那Manifest与TypeTag有什么区别呢?Manifest在scala.reflect包中,它在scala.reflect包中,而TypeTag 在scala.reflect.runtime.universe包中定义;TypeTag可以用来替代Manifest,功能更强大一点,Manifest不能识别路径依赖类型,例如对于class Outter{ class Inner},假设分别创建了两个不同的外部类,outter.Inner, outter2.Inner, Manifest就会识别为同一类型,而TypeTag不会,另外TypeTag可以使用typeOf[T] 来检查类型参数。
下面的代码给出了Manifest的用法:
object ManifestType extends App {def print1[T](x: List[T])(implicit m: Manifest[T]) = {if (m <:< manifest[String])println("字符串类型的List")elseprintln("非字符串类型的List")}print1(List("one", "two")) print1(List(1, 2)) print1(List("one", 2))
}
- 1
隐式参数m由编译器根据上下文自动传入,例如print1(List(“one”, “two”)) ,编译器会根据”one”,”two” 实际类型推断出 T 的类型是 String,再隐式地传入了Manifest[String]类型的对象参数,使得运行时可以根据这个参数做更多的事情。
下面的代码演示了如何使用TypeTag
import scala.reflect.runtime.universe._def getTypeTag[T: TypeTag](a: T) = typeTag[T]//下列语句返回TypeTag[List[Int]]println(getTypeTag(List(1, 2, 3)))
从上面的代码可以看到,typeTag返回的是具体的类型,而不是类型擦除之后的类型any,即TypeTag保存所有具体的类型。在运行时可以通过模式匹配来精确地对类型进行判断:
import scala.reflect.runtime.universe._def patternMatch[A : TypeTag](xs: List[A]) = typeOf[A] match { //利用类型约束进行精确匹配case t if t =:= typeOf[String] => "list of strings" case t if t <:< typeOf[Int] => "list of ints"}println(patterMatch(List(1,2)))
上边的typeOf[A]在传入参数为List(“String”)时,得到结果是java.lang.String。typeOf[A]接受一个类型为TypeTag[a]的隐式参数,编译器生成的TypeTag隐式参数会被传给typeOf[A] 。 有4种TypeTag:
1 scala.reflect.api.TypeTags#TypeTag. A full type descriptor of a Scala type. For example, a TypeTag[List[String]] contains all type information, in this case, of typescala.List[String].
2 scala.reflect.ClassTag. A partial type descriptor of a Scala type. For example, a ClassTag[List[String]] contains only the erased class type information, in this case, of type
3 scala.collection.immutable.List.ClassTags provide access only to the runtime class of a type. Analogous to scala.reflect.ClassManifest.
4 scala.reflect.api.TypeTags#WeakTypeTag. A type descriptor for abstract types (see corresponding subsection below).
这给出最常用的ClassTag的用法:ClassTag[T]保存了被泛型擦除后的原始类型T,提供给运行时程序使用。
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._scala> val tt = typeTag[Int]
tt: reflect.runtime.universe.TypeTag[Int] = TypeTag[Int]scala>scala> import scala.reflect._
import scala.reflect._//得到具体类型
scala> val ct = classTag[String]
ct: scala.reflect.ClassTag[String] = java.lang.String
3. Scala类型系统总结
到此,Scala的类型系统基本介绍完毕,下表给出了Scala中常见的类型
类型 | 语法类型 |
---|---|
类 | class Person |
特质 | trait Closable |
元组类型 | (T1,T2,T3,…) |
函数类型 | (T1,T2,t3,…)=>T |
参数类型(泛型) | class Person[T1,T2,…] |
单例类型 | this.type |
类型投影 | Outter#Inner |
复合类型 | A with B with C… |
结构体类型 | {def f():Unit ….} |
中置类型 | T1 A T2 |
存在类型 | T forSome {} |
Scala入门到精通——第二十四节 高级类型 (三)相关推荐
- Scala入门到精通——第二十六节 Scala并发编程基础
本节主要内容 Scala并发编程简介 Scala Actor并发编程模型 react模型 Actor的几种状态 Actor深入使用解析 1. Scala并发编程简介 2003 年,Herb Sutte ...
- Scala入门到精通——第十四节 Case Class与模式匹配(一)
本节主要内容 模式匹配入门 Case Class简介 Case Class进阶 1. 模式匹配入门 在Java语言中存在switch语句,例如: //下面的代码演示了java中switch语句的使用 ...
- Scala入门到精通——第二十九节 Scala数据库编程
本节主要内容 Scala Mavenproject的创建 Scala JDBC方式訪问MySQL Slick简单介绍 Slick数据库编程实战 SQL与Slick相互转换 本课程在多数内容是在官方教程 ...
- Scala入门到精通——第二十五节 提取器(Extractor)
本节主要内容 apply与unapply方法 零变量或变量的模式匹配 提取器与序列模式 scala中的占位符使用总结 1. apply与unapply方法 apply方法我们已经非常熟悉了,它帮助我们 ...
- Scala入门到精通——第二十节 类型参数(二)
本节主要内容 Ordering与Ordered特质 上下文界定(Context Bound) 多重界定 类型约束 1. Ordering与Ordered特质 在介绍上下文界定之前,我们对Scala中的 ...
- Scala入门到精通——第十九节 隐式转换与隐式参数(二)
本节主要内容 隐式参数中的隐式转换 函数中隐式参数使用概要 隐式转换问题梳理 1. 隐式参数中的隐式转换 前一讲中,我们提到函数中如果存在隐式参数,在使用该函数的时候如果不给定对应的参数,则编译器会自 ...
- Scala入门到精通——第十五节 Case Class与模式匹配(二)
本节主要内容 模式匹配的类型 for控制结构中的模式匹配 option类型模式匹配 1. 模式的类型 1 常量模式 object ConstantPattern{def main(args: Arra ...
- Scala入门到精通——第二十八节 Scala与JAVA互操作
本节主要内容 JAVA中调用Scala类 Scala中调用JAVA类 Scala类型参数与JAVA泛型互操作 Scala与Java间的异常处理互操作 1. JAVA中调用Scala类 Java可以直接 ...
- Scala入门到精通——第二十三节 高级类型 (二)
本节主要内容 中置类型(Infix Type) 存在类型 函数类型 抽象类型 关于语法糖的问题,在讲解程序语言时,我们常常听到"语法糖"这个术语,在百度百科中,它具有如下定义: 语 ...
最新文章
- rtp 多媒体流同步控制 实时传输协议 简介
- (第六场)Singing Contest 【模拟】
- ORA-01940,删除某用户的所有对象
- [编译原理学习]词法分析
- python画误差棒_给妹子讲python-S02E06matplotlib散点图、频次直方图与误差线图
- Go gomaxprocs 调高会引起调度性能损耗
- 销货清单打印软件_地磅软件管理系统 大宗物资称重管理车运版
- Linux系统基础(二)
- Dynamic Entity Representation with Max-pooling Improves Machine
- 详解: Spark 相对于MapReduce的优势(为什么MapReduce性能不理想)
- RK3288 error: undefined reference to 'LOGD'
- 使用Animation编辑器编辑动画
- 基于ssm医院病历管理系统
- 中国平面设计指导价格
- java linux cd命令无效,为什么“cd”不能在shell脚本中工作?
- 分享一个MentoHUST for Windows 锐捷认证使用方法,实现不用猎豹wifi第三方流氓软件破解校园网wifi共享限制。
- RuntimeError: sizes of tensors must match except in dimension 2. Got 37 and 36
- iOS——UINavigationController简单实用以及内存警告处理过程
- CAD二开之打开时插件命令自动加载(RibbonUI自动显示)
- 海康摄像头4G内网连接方案
热门文章
- 【解析】1057 数零壹 (20分)(进制转换)
- C语言:L1-031 到底是不是太胖了 (10分)(解题报告)
- 39行代码AC_HDU-6740 2019CCPC秦皇岛 J MUV LUV EXTRA(KMP变形)
- [Leetcode总结] 102.二叉树的层序遍历
- Online DDL
- 虚拟Web主机(基于域名配置,基于ip地址,基于端口)
- Linux使用parted进行分区及拓展实验
- 工业机器人打磨抛光编程员工资_让我们一起来谈谈,工业机器人行业的真实工资是多少?...
- liteos内核驱动和linux,移植RTOS必备基础知识
- c++获取数组长度_灵魂拷问:Java如何获取数组和字符串的长度?length还是length()?...