Scala学习日志(1.5)——自适应类型

来源:互联网 发布:亚洲人长相 知乎 编辑:程序博客网 时间:2024/06/04 17:42

码字不易,转发请注明出处:http://blog.csdn.net/qq_28945021/article/details/52087381

自适应类型

在使用scala开发时。程序员们会惊喜的发现在大多数情况下他们再也无需提供冗余的类型信息。这可以节省很多的开发成本。而能令程序员有这点便捷。归功于Scala优秀类型推演。在使用scala时,我们可以很轻松的写下如下代码。

val one :Int= 1 //(Int)val num = 1 //(Int)val word = "Hello word" //(String)val strBuild = new StringBuilder("hello") //(StringBuilder)

可以清晰的看到,除了第一行代码,之后的代码我们并没有指定具体的参数类型。然后Scala却可以完美的进行类型推演。这样的使用,无疑是十分方便的。

1、容器和类型推演

前面说了scala 的类型推演,我们将其用到容器当中也可成立:

var list1 : List[Int] = new ArrayList[Int]var list2 = new ArrayList[Int]

或许对于容器的使用时,便能体会到第一行语法是如此的冗余。尽管Scala的类型推演看起来很不负责任。可能会令容器发生错误的操作。然而这是多余的担心。Scala对于实例化对象的类型是十分警觉的,严禁进行可能引发类型问题的转换。下面的一个小例子可以很简单的展现这一特性:

val list1 =new ArrayList[Int]val list2 =new ArrayList    list2 = list1 //Compilation Error

之所以会出现Error,是因为在list2实例化时,scala实际上是创建了一个ArrayList[Nothing]的实例。所以在试图将list1赋值给list2时便会报类型不匹配。道理很容易理解,Java当中对泛型也有类似的防范。但是这里出现的Nothing是一个全新的东西:其实是所有类的子类。正由于它是所有类的子类,所以任何有意义类型都无法放到list2当中——因为基类实例是不能当做派生类实例的。而Nothing是最底层的子类。
关于Nothing的具体情况会在接下来详述。这里只是强调,在默认情况下,scala要求赋值两边的容器类型相同。

Any类型

前面我们提出了Nothing类型,Any类型正与之相反——是所有类型的超类。观看下图可以很容易理解。
这里写图片描述

Any的直接子类是AnyRef与AnyVal。这两者中,AnyVal是Java中的基本类型Int,Double的基类。而其余的一切类,由AnyRef充当它们的基类。可以近似的将AnyRef理解为Java中的Object(其实AnyRef的确包含Object的所有方法)。AnyRef可以调用Object的所有方法,而Any与AnyVal却并不能,即使在编译成字节码时,scala也仅仅是将他们当成Object的引用处理。(可以说Any与AnyVal是通过类型擦除成了Object)。
这个概念理解了,我们却要引入上面的问题:我们能否使用Any类型来创建一个没有指定类型的容器呢?接下来的例子测试一下:

var list1 = new ArrayList[Int]var list2 = new ArrayList[Any]var ref1 :Int= 1var ref2 :Any= nullref2 = ref1 //OKlist2 = list1   //Compilation Error

依旧是不可以T.T,其实也是情理之中,如果把Any近似看做Java中的Object,在Java中也是不支持这样使用的。由这点和上述的Nothing,可以了解到scala是如何增强类型安全的。(并且也并非是绝对的无法类型不同,实际也可以很灵活。)

Nothing类型的更多情况

Any的存在原因想必不需要过多解释也可以理解。但是想必在文章前面提到Nothing时,大多程序员都是一头雾水——这东西到底是拿来干嘛的!
Scala的类型推演辛勤地工作,确定表达式和函数的类型。但如果其推演出来的类型过于宽泛,其实无助于类型校验。与此同时,假设一个分支返回Int,一个分支抛出异常。那么又该如何推演类型呢?这种情况下,匹配为小的类型肯定比大的好。所以最终匹配的类型一定是小的,并且异常的类型也应该就是这个小的类型。然而问题就是,异常任何时候都有可能抛出,并无法确保任何时候都是这个类型可以匹配。提到这一步,想必有些聪明人已经明白了。前面提到过Nothing是所有类的子类。那么,因此他就可以替换任何东西。且由于其本身是抽象的,因此在运行时,它的实例并不会真实的存在,它纯粹就是推演的助手而已。
Scala用Nothing类型——所有类型的子类——帮助类型推演工作更加平滑

Option类型

在对于Null值的处理上,scala向前更进了一步。比如,在进行模式匹配时,匹配的结果可能是一个对象,也可能是一个List,还可能是一个元组等等,也可能是不存在,静静的返回null。这就会有两方面的问题:首先,我们没有显示的表达出“我就是要匹配空”的意图。其次,没有办法强迫函数调用者对null进行检查。Scala想让这样的意图更清晰的表达出来。确实,有时候就是需要没有结果。Scala以一种类型安全的方式做到了这一点:它使用Option[T]类型。

def commentOnPractice(input: String) = {    if(input == "test") Some("good") else None}for(input <- Set("test","hack")){      val comment = commentOnPractice(input)      println("input" + input + "comment" +      comment.getOrElse("Found no comments"))}

这里,commentOnpractice()也许返回一个注释(String),也许压根没有任何注释,这两点分别用Some[T]和None的实例表示。这两个类都继承自Option[T]类。输出如下:

inputtestcommentgood
inputhackcommentFound no comments

参数化类型的可变性

前面我不止一次的说到容器的类型其实是可以不指定的。我们见识过:

var arr1 = new Array[Int](3)var arr2: Array[Any] = nullarr2 = arr1 //Compilation ERROR

这样的约束其实是好事。不过有些时候,我们的确希望不那么死板,给予我们一定的活动空间。而Scala的确有这样的能力。

将子类实例的容器赋给基类容器的能力称为协变。将超类实例的容器赋给予子类容器的能力称为逆变。当我们需要进行协变时。可以这样:

def test[T <: Any](ts : Array[T])   

这个语法就声明了该方法中的Array可以将T的派生类赋予。这是一种上界语法。

有了上界语法便有下界语法,当我们需要使用逆变时,便可以用到

def test2[S,D >:S](fromPets :Array[5],toPets:Array[D])={ //... }

将目的数据的参数化类型(D)限制为源数组的参数化类型(S)的超类型。换句话说,S(源类型)设置了类型D(目的类型)的下界——它可以是类型S或其超类的任意类型。

这篇博客浅显的说明了下scala的自适应类型的相关知识,希望能帮到有需要的朋友。

1 0
原创粉丝点击