zoukankan      html  css  js  c++  java
  • Scala入门到精通——第二十四节 高级类型 (三)

    作者:摆摆少年梦
    视频地址:http://blog.csdn.net/wsscy2004/article/details/38440247

    本节主要内容

    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 = true
    
    scala> 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;
        }
    
    }

    经过类型擦除后,终于变为:

    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;
        }
    
    }

    经过类型擦除后的类称为原始类型,从这点来看,java中的泛型事实上是一个伪泛型,它仅仅在编译层次进行实现。在生成字码码这部分泛型信息被擦除。

    以下的样例证明也证明了这一点:

    public static void main(String[] args) {
            Person<String> p1=new Person<String>("张", "三");
            Person<Integer> p2=new Person<Integer>(1, 23);
            //以下的代码返回的是true
            System.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攻克了这些问题。它的语法非常easy,通过注解进行类型专门化声明。如:

    //在泛型參数前面加@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:ScalaWorkspaceScalaChapter24incnscalaxtwyadvancedtype>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)
    }

    在上一讲中我们看到了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")
        else
          println("非字符串类型的List")
      }
      print1(List("one", "two")) 
      print1(List(1, 2)) 
      print1(List("one", 2))
    }

    隐式參数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 {}

    加入公众微信号,能够了解很多其它最新Spark、Scala相关技术资讯
    这里写图片描写叙述

  • 相关阅读:
    最新 蓝鲸人java校招面经 (含整理过的面试题大全)
    最新 上海轻轻java校招面经 (含整理过的面试题大全)
    最新 苏州朗动java校招面经 (含整理过的面试题大全)
    最新 医渡云java校招面经 (含整理过的面试题大全)
    变量的自动类型转换和强制类型转换(day01_10)
    java的数据类型(day01_09)
    常用的dos命令操作(day01_03)
    1.镜像-虚拟光驱-光驱
    Spring基于配置文件的方式来配置AOP
    Spring-AOP(切面的优先级&&&重用切点表达式)
  • 原文地址:https://www.cnblogs.com/mfmdaoyou/p/7207500.html
Copyright © 2011-2022 走看看