一、Scala的层级
1.1类层级
- Scala中,Any是所其他类的超类,在底端定义了一些有趣的类NULL和Nothing,是所有其他类的子类。
- 根类Any有两个子类:AnyVal和AnyRef。其中AnyVal是Scala里每个内建值类的父类
scala> new Int <console>:12: error: class Int is abstract; cannot be instantiated new Int
1.2底层类型
在Scala类层级中,可看到scala.Null和scala.Nothing。它们是用统一的方式处理Scala面对对象类型的某些“边界情况”的特殊类型。
- Null类是null引用对象的类型,它是每个引用类的子类,Null不兼容值类型,例如,不能把null值赋给整数变量:
scala> val i: Int = null <console>:11: error: an expression of type Null is ineligible for implicit conversion val i: Int = null ^
Nothing类型在Scala的类层级的最底层,它是任何其他类型的的子类型。Nothing的一个用处是它标明了不正常的终止。例如Scala的标准库中的Predef对象有一个error方法,如下定义:
def error(message:Sring):Nothing= throw new RuntimeException(message)
error 的返回类型是Nothing,告诉用户方法不是正常返回(是抛出异常)。因为Nothing是任何类的子类,所有可以灵活使用向error这样的方法,例如:
scala> def error(message:String):Nothing = | throw new RuntimeException(message) error: (message: String)Nothing scala> def divide(x:Int,y:Int):Int= | if(y!=0) x/y else error("can't divide by zero") divide: (x: Int, y: Int)Int scala> divide(7,2) res5: Int = 3 scala> divide(7,0) java.lang.RuntimeException: can't divide by zero at .error(<console>:12) at .divide(<console>:13) ... 28 elided scala>
二、函数和闭包
2.1方法
- 定义函数最通用是方法是作为某个对象的成员。这种函数被称为方法,作为例子:
import scala.io.Source object LongLines{ def processFile(filename:String,Int){ val source = Source.fromFile(filename) for(line<-source.getLines) processFile(filename,width,line) } private def processFile(filename:String,Int,line:String){ if(line.length>width) println(filename+":"+line.trim) } } object FileLongLines{ def main(args:Array[String]){ val width=args(0).toInt for(arg<-args.drop(1)) LongLines.processFile(arg,width) } }
2.2本地函数
上节processFile方法的建立演示了函数式编程的设计原则,问题是这些帮助函数的名称可能污染程序的命名空间。
Scala中的解决方法:一种是私有方法。
- 另一种是把函数定义在别的函数内。就好像本地变量那样,这种本地函数仅在包含它的代码块中可见。举例:
def processFile(filename:String,Int){ def processLine(filename:String,Int,line:String){ if(line.length>width) println(filename+":"+line.trim) } val source = Source.fromFile(filename) for(line<-source.getLines) processFile(filename,width,line) }
例子中通过私有方法转化为局部方法,processLine的定义放在processFile定义里,作为本地函数,processLine的范围局限在processFile之内,外部无法访问。
- 带局部processLine方法的LongLines
def processFile(filename:String,Int){ def processLine(line:String){ if(line.length>width) println(filename+":"+line.trim) } val source = Source.fromFile(filename) for(line<-source.getLines) processFile(line) }
这种对外层函数入参的直接使用是Scala里通用的嵌套思想的平常但也很有用
2.3头等函数
Scala的函数是头等函数,不仅可以定义和调用函数,还可以把它们写成匿名的字面量,并把他们作为值传递,
函数字面量被编译进类,并在运行期间实例化为函数值。因此函数字面量和值的区别在于函数字面量存在于原代码,而函数值作为对象存在于运行期。这个区别很像类(源代码)和对象(运行期)之间的关系。
=>指明这个函数把左边的东西(任何整数x)转化为右边的东西。所有,这个函数可以把任何整数x映射为x+1。
函数值是对象,所有如果你愿意,可以将其存入变量。它们也是函数,所有你可以使用通常的括号函数调用它们。
scala> var increase=(x:Int)=>x+1 increase: Int => Int = $$Lambda$1149/268616862@62eb918 scala> increase(4) res7: Int = 5
2.4部分应用函数
scala> val someNumbers=List(-11,-10,-5,0,5,10) someNumbers: List[Int] = List(-11, -10, -5, 0, 5, 10) scala> someNumbers.foreach(x:Int) <console>:13: error: not found: value x someNumbers.foreach(x:Int) ^ <console>:13: error: type mismatch; found : Int required: Int => ? someNumbers.foreach(x:Int) ^ scala> someNumbers.foreach(x:Int)=>println(x) <console>:1: error: ';' expected but '=>' found. someNumbers.foreach(x:Int)=>println(x) ^ scala> someNumbers.foreach((x:Int)=>println(x)) -11 -10 -5 0 5 10 scala> someNumbers.filter((x:Int)=>x>0) res10: List[Int] = List(5, 10) scala> someNumbers.filter((x)=>x>0) res11: List[Int] = List(5, 10) scala> someNumbers.filter(x=>x>0) res12: List[Int] = List(5, 10) scala> someNumbers.filter(_>0) res13: List[Int] = List(5, 10) scala> val f=_+_ <console>:11: error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$plus(x$2)) val f=_+_ ^ <console>:11: error: missing parameter type for expanded function ((x$1: <error>, x$2: <error>) => x$1.$plus(x$2)) val f=_+_ ^ scala> val f=(_:Int)+(_:Int) f: (Int, Int) => Int = $$Lambda$1180/712034517@6d0ef6dc scala> f(2,6) res14: Int = 8
部分应用函数是一种表达式,不需要提供需要的所有参数。代之以提供部分,或者不提供所需参数。
闭包
scala> (x:Int)=>x+more <console>:12: error: not found: value more (x:Int)=>x+more ^ //声明more scala> val more=1 more: Int = 1 scala> val addMore=(x:Int)=>x+more addMore: Int => Int = $$Lambda$1206/2005145495@773eb14c scala> addMore(5) res21: Int = 6
依照这个函数字面量在运行时创建的函数值(对象)被称为闭包
重复函数
Scala中,可以指明函数的最后一个参数是重复的,从而允许客户向函数传入可变长度参数列表。想要标注一个重复参数,可在参数类型之后放一个星号。如:
scala> def echo(args:String*)= | for (arg<-args) println(arg) echo: (args: String*)Unit scala> echo() scala> echo("one") one scala> echo("one","hello!") one hello! 函数内部,重复函数的类型是声明参数类型的数组。因此,echo函数被声明为类型“String*”的args的类型实际上是Array[String]。然而,如果你有一个合适的数组,并尝试把它当作重复参数传入,将得到编译器错误: scala> val arr=Array("What's","up","doc?") arr: Array[String] = Array(What's, up, doc?) scala> echo(arr) <console>:14: error: type mismatch; found : Array[String] required: String echo(arr) ^
要实现这个做法,需要在数组参数后添加一个冒号和一个_*,如下:
scala> echo(arr:_*) What's up doc?