本节主要内容

  1. Type Specialization
  2. Manifest、TypeTag、ClassTag
  3. 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入门到精通——第二十四节 高级类型 (三)相关推荐

  1. Scala入门到精通——第二十六节 Scala并发编程基础

    本节主要内容 Scala并发编程简介 Scala Actor并发编程模型 react模型 Actor的几种状态 Actor深入使用解析 1. Scala并发编程简介 2003 年,Herb Sutte ...

  2. Scala入门到精通——第十四节 Case Class与模式匹配(一)

    本节主要内容 模式匹配入门 Case Class简介 Case Class进阶 1. 模式匹配入门 在Java语言中存在switch语句,例如: //下面的代码演示了java中switch语句的使用 ...

  3. Scala入门到精通——第二十九节 Scala数据库编程

    本节主要内容 Scala Mavenproject的创建 Scala JDBC方式訪问MySQL Slick简单介绍 Slick数据库编程实战 SQL与Slick相互转换 本课程在多数内容是在官方教程 ...

  4. Scala入门到精通——第二十五节 提取器(Extractor)

    本节主要内容 apply与unapply方法 零变量或变量的模式匹配 提取器与序列模式 scala中的占位符使用总结 1. apply与unapply方法 apply方法我们已经非常熟悉了,它帮助我们 ...

  5. Scala入门到精通——第二十节 类型参数(二)

    本节主要内容 Ordering与Ordered特质 上下文界定(Context Bound) 多重界定 类型约束 1. Ordering与Ordered特质 在介绍上下文界定之前,我们对Scala中的 ...

  6. Scala入门到精通——第十九节 隐式转换与隐式参数(二)

    本节主要内容 隐式参数中的隐式转换 函数中隐式参数使用概要 隐式转换问题梳理 1. 隐式参数中的隐式转换 前一讲中,我们提到函数中如果存在隐式参数,在使用该函数的时候如果不给定对应的参数,则编译器会自 ...

  7. Scala入门到精通——第十五节 Case Class与模式匹配(二)

    本节主要内容 模式匹配的类型 for控制结构中的模式匹配 option类型模式匹配 1. 模式的类型 1 常量模式 object ConstantPattern{def main(args: Arra ...

  8. Scala入门到精通——第二十八节 Scala与JAVA互操作

    本节主要内容 JAVA中调用Scala类 Scala中调用JAVA类 Scala类型参数与JAVA泛型互操作 Scala与Java间的异常处理互操作 1. JAVA中调用Scala类 Java可以直接 ...

  9. Scala入门到精通——第二十三节 高级类型 (二)

    本节主要内容 中置类型(Infix Type) 存在类型 函数类型 抽象类型 关于语法糖的问题,在讲解程序语言时,我们常常听到"语法糖"这个术语,在百度百科中,它具有如下定义: 语 ...

最新文章

  1. rtp 多媒体流同步控制 实时传输协议 简介
  2. (第六场)Singing Contest 【模拟】
  3. ORA-01940,删除某用户的所有对象
  4. [编译原理学习]词法分析
  5. python画误差棒_给妹子讲python-S02E06matplotlib散点图、频次直方图与误差线图
  6. Go gomaxprocs 调高会引起调度性能损耗
  7. 销货清单打印软件_地磅软件管理系统 大宗物资称重管理车运版
  8. Linux系统基础(二)
  9. Dynamic Entity Representation with Max-pooling Improves Machine
  10. 详解: Spark 相对于MapReduce的优势(为什么MapReduce性能不理想)
  11. RK3288 error: undefined reference to 'LOGD'
  12. 使用Animation编辑器编辑动画
  13. 基于ssm医院病历管理系统
  14. 中国平面设计指导价格
  15. java linux cd命令无效,为什么“cd”不能在shell脚本中工作?
  16. 分享一个MentoHUST for Windows 锐捷认证使用方法,实现不用猎豹wifi第三方流氓软件破解校园网wifi共享限制。
  17. RuntimeError: sizes of tensors must match except in dimension 2. Got 37 and 36
  18. iOS——UINavigationController简单实用以及内存警告处理过程
  19. CAD二开之打开时插件命令自动加载(RibbonUI自动显示)
  20. 海康摄像头4G内网连接方案

热门文章

  1. 【解析】1057 数零壹 (20分)(进制转换)
  2. C语言:L1-031 到底是不是太胖了 (10分)(解题报告)
  3. 39行代码AC_HDU-6740 2019CCPC秦皇岛 J MUV LUV EXTRA(KMP变形)
  4. [Leetcode总结] 102.二叉树的层序遍历
  5. Online DDL
  6. 虚拟Web主机(基于域名配置,基于ip地址,基于端口)
  7. Linux使用parted进行分区及拓展实验
  8. 工业机器人打磨抛光编程员工资_让我们一起来谈谈,工业机器人行业的真实工资是多少?...
  9. liteos内核驱动和linux,移植RTOS必备基础知识
  10. c++获取数组长度_灵魂拷问:Java如何获取数组和字符串的长度?length还是length()?...