官方网址:http://www.scala-lang.org
官网对scala的介绍:Scala既是面向对象的语言,也是面向函数的语言。
scala可以为你在做大量代码重用和扩展是提供优雅的层次结构,并可以通过高阶函数来实现这样的目标。
Scala创始人 Martin Odersky 马丁·奥德斯基
Martin是EPFL(瑞士领先的技术大学)Martin是EPFL(瑞士领先的技术大学,洛桑联邦理工学院)编程研究组的教授。他在整个职业生涯中一直不断追求着一个目标:让写程序这样一个基础工作变得高效、简单、且令人愉悦。
他可能比世界上任何一个人写过更多的Java和Scala代码。他编写了javac,这是目前大部分Java程序员所使用的编译器。他也编写了Scala编译器scalac,可谓是Scala社区飞速发展的基石。他著有《Programming in Scala》一书,是最畅销的Scala书籍。他曾经就职于IBM研究院、耶鲁大学、卡尔斯鲁厄大学以及南澳大利亚大学。在此之前,他在瑞士苏黎世联邦理工学院追随Pascal创始人Niklaus Wirth学习,并于1989年获得博士学位。
Scala并不适于编程的初级课程。相反,它是为专业程序员定制的强力语言。
1、它是一门现代编程语言,作者是Martin Odersky(javac之父),受到Java、Ruby、Smalltalk、ML、Haskell、Erlang等语言的影响。
2、它即是一门面向对象(OOP)语言,每个变量都是一个对象,每个“操作符”都是方法。scala语言在面向对象的方面,要比java更彻底。
它同时也是一门函数式编程(FP)语言,可以将函数作为参数传递。你可以用OOP、FP,或者两者结合的方式编写代码。
3、Scala代码通过scalac编译成.class文件,然后在JVM上运行,可以无缝使用已有的丰富的Java类库。即Scala的代码会编译成字节码,运行在Java虚拟机(JVM)上。
4、接触语言的第一天你就能编出有趣的程序,但是这门语言很深奥,随着学习的深入,你会发现更新、更好的编写代码的方式。Scala会改变你对编程的看法。针对同一任务,可以有很多种不同的实现方式,并且可读性以及性能都有不一样的体现。
语句 说明 示例
if……else if……else 判断 //一个简单的示例
var var1=10;
if是具有返回值的,if判断后,将执行代码的最后一个表达式的值返回作为整个if执行后的结果。 if(var1<100){
println("小了")
}else{
println("大了")
}
//根据scala函数式编程风格,建议做如下更改 //尽量使用常量 val val1=10; //if……else最后一行的值是返回值,可省略return val result= if(val1<100){ "小了"; }else{ "大了"; } print(result) //也可以简化成下面的形式 val val2=10; println(if(val2<100)"小了"else"大了");
while 和java中用法相同 //一个简单的例子
while无法控制返回值,或者说返回值是Unit,写成() val val1=List(1,2,3,4);
在scala中while比较少使用 var index=0;
while(index<val1.size){
println(val1(index));
index+=1;
}
for scala中的for要比java的for强大,使用很频繁,需要熟练掌握。 //生成一个1~100的区间,区间类型是range
for 同while一样,无法控制返回值,或者说返回值是Unit,写成() val val1=1 to 100;
但是:for()可以和yield{ }配合使用,将for()每次循环结果存到一个Vector( )中。 //循环遍历并打印 for(num <-val1){ println(num) } //支持条件过滤 for(num<-val1;if num>50){ println(num) } //支持多条件过滤 for(num<-val1;if num>50;if num%2==0;if num<90) println(num) //也可以写成下面的形式 for(num<- 1 to 100;if num>50&&num<90&&num%2==0){ println(num) } //实现9*9乘法表 for(a<-1 to 9){ for(b<-1 to a){ print(b+"*"+a+"="+b*a+"\t"); } println(); } //可以简化为下面的写法 //这种形式成为流间变量定义 for(a<-1 to 9;b<-1 to a; val s=if(b==a)"\r\n"else"\t"){ print(b+"*"+a+"="+b*a+s) } //引入yield函数,这类循环叫做for推导式 //此外,如果yield 只有一行返回值,则{}可以省略 val result=for(a<-1 to 9;b<-1 to a; val flag=if(b==a)"\r\n"else"\t")yield {b+"*"+a+"="+b*a+flag} println(result)
//一个有趣的案例
var v1=Array("apple","banana","orange")
for(i<-0 to v1.length-1){
println(s"$i is ${v1(i)}") }
> 0 is apple 1 is banana 2 is orange //用s函数改造99乘法表 for(a<-1 to 9;b<-1 to a; val s=if(b==a)"\r\n"else"\t"){ print(s"$b*$a=${b*a}$s") //遍历一个Map var v2=Map("key1"->"rose","key2"->"tom","key3"->"jary") for((k,v)<-v2){ print(s"$k:$v") }
try catch finally scala中继承了java的异常机制 import java.lang
try {
throw new RuntimeException("error");
}catch {
case t: NullPointerException => t.printStackTrace();("空指针异常");
case t: Exception=>t.printStackTrace();println("其他异常");
}finally {
println("资源释放")
}
match scala中的match类似于其他语言的switch //一个简单的例子
//match匹配到case后,执行case对应的内容,然后退出match,于java不同的是,不需要写break
val val1="bbb";
val1 match {
case "aaa" =>println("1");
case "bbb" =>println("2");
case _ =>println("3");
}
//此外,match可以带有返回值 val result=val1 match{ case "aaa" =>1 case "bbb" =>2 case _ =>3 }
break scala中没有break和continue语句,需要通过另外的形式来实现 import util.control.Breaks._
continue
object Demo12 {
def main(args: Array[String]): Unit = {
//实现break breakable( for(i <- 1 to 10){ if(i==8){ break(); }else{ println(i); } } ) //实现continue for(i<-1 to 10){ breakable( if(i==8){ break; }else{ println(i); } ) } } }
函数的声明
scala 函数通过 def 关键字定义,def前面可以具有修饰符,可以通过private、protected来控制其访问权限。
注意:没有public,不写默认就是public的。 此外也可跟上override,final等关键字修饰。
函数的返回值
1)函数体中return关键字往往可以省略掉,一旦省略掉,函数将会返回整个函数体中最后一行表达式的值,这也要求整个函数体的最后一行必须是正确类型的值的表达式。
2)大部分时候scala都可以通过 =符号 来自动推断出返回值的类型,所以通常返回值类型声明可以省略。
但是注意:如果因为省略了返回值类型造成歧义,则一定要写上返回值声明。
3)如果函数体只有一行内容,则包裹函数体的大括号可以省略
4)如果返回值类型是UNIT,则另一种写法是可以去掉返回值类型和等号,把方法体写在花括号内,而这时方法内无论返回什么,返回值都是 UNIT。
格式:[private/protected] def 函数名(参数列表):返回值声明 = {函数体}
示例:
//方法的返回值为空
def f1():Unit={
println("hello scala");
}
//等价于f1()方法,注意:如果函数没有=号,无论函数体里的返回值是什么,函数的返回值都是Unit
def f2(){
println("hello scala");
}
//定义方法参数类型,返回值类型,及返回值
def f3(a:Int,b:Int):Int={
a+b;
}
//scala可自行推断返回值类型,所以可省略返回值类型
def f4(a:Int,b:Int)={
a+b;
}
//如果函数体只一行内容,可以省了花括号
def f5(a:Int,b:Int)=a+b
//注意下面这种形式,因为没有=号,所以函数的返回值是Unit,即()
def f6(a:Int,b:Int){
a+b
}
默认参数
代码示意:
object Demo21 {
def f1(a:String,b:String="[",c:String="]")={
b+a+c
}
def main(args: Array[String]): Unit = {
print(f1("hello"))//将打印:[hello]
}
}
函数的种类
1.成员函数
2.本地函数(内嵌在函数内的函数)
3.函数值(匿名函数)
4.高阶函数
成员函数: 函数被使用在类的内部,作为类的一份子,称为类的成员函数
示例:
object Demo15 {
def main(args: Array[String]): Unit = {
val p=new Student();
p.eat();
p.study();
}
// eat() 和study()属于 类Student的成员函数
class Student{
def eat(){
println("吃饭")
}
def study(){ println("学习") }
}
}
本地函数:函数内嵌的函数称为本地函数,这样的函数外界无法访问
示例:
object Demo16 {
def main(args: Array[String]): Unit = {
val p=new Student();
p.eat("肉");
}
class Student{
def eat(food:String){ //cook函数内嵌在eat函数里,这样的函数称之为本地函数 def cook(food:String):String={ "做熟了的"+food; } println("吃"+cook(food)); }
}
}
函数值 - 匿名函数:
示例:
def f1(a:Int,b:Int):Int={a+b}; //等价于上式的函数 (a:Int,b:Int)=>{a+b}; //如果函数体只有一行代码,可以省去大括号 (a:Int,b:Int)=>a+b; //如果函数参数列表只有一个参数,小括号有可以省略 a:Int=>a+1; //如果函数的参数类型可以被推测,则可以省略类型值 //(a,b)=>a+b def f1(a:Int,b:Int):Int={a+b}; //可以将f1()函数赋值给f2常量 val f2=f1(_,_); //可以将f1()函数赋值给f3变量,f3可以更改此函数 var f3=f1(_,_); f3=(a:Int,b:Int)=>a*b; //也可以这样写 val f4=(c:Int,d:Int)=>{c+d} //注意,下面的写法是将f1的函数值复制给f5,而不是函数赋值给f5 val f5=f1(2,3)
高阶函数:函数可以作为方法的参数进行传递和调用
示例1:
object Demo01 {
//定义了compute函数,a,b和f函数 三个参数。其中f函数未做实现。
//我们的目的是对传入的a和b参数利用 f函数做运算,但是具体是什么运算需要由用户自己来指定。
def compute(a:Int,b:Int,f:(Int,Int)=>Int):Int={
f(a,b)
}
def main(args: Array[String]): Unit = {
val f1=(a:Int,b:Int)=>{a+b}
val f2=(a:Int,b:Int)=>{a*b}
val f3=(a:Int,b:Int)=>{a-b}
val f4=(a:Int,b:Int)=>{a/b}
//由下式可以看出,scala中,函数可以当做参数进行传递和调用 val result=compute(2,3,f1) //下式等价于上式 //val result=compute(2,3,(a,b)=>{a+b})
}
}
示例2:
object Demo02 {
//定义了一个函数,作用是将用户处理后的字符串结果进行打印输出
def handleString(a:String,f:(String)=>String){
println("处理完后的字符串为:"+ f(a))
}
def main(args: Array[String]): Unit = {
val a="hello scala";
handleString(a,(a)=>{a}); handleString(a,(a)=>{a.substring(6)}); handleString(a,(a)=>{a.concat(" 1706")})
}
}
占位符:占位符指的是scala中的下划线_ ,可以用它当作一个或多个参数来使用。
使用_占位符的前提要求:每个参数在函数仅出现一次。
使用下划线时,如果类型可以自动推断出,则不用声明类型。如果无法自动推断类型,则在下划线后自己来显示声明类型即可。
示例1:
object Demo03 {
def compute(a:Int,b:Int,f:(Int,Int)=>Int):Int={
f(a,b)
}
def handleString(a:String,f:(String)=>String){
println("处理完后的字符串为:"+ f(a))
}
def main(args: Array[String]): Unit = {
val message="hello scala";
//这样用占位符会报错 //handleString(message,(_)=>{_.substring(6)}) //应改为下面的写法 handleString(message,{_.substring(6)}) //如果函数体只有一行代码,则还可以将大括号去掉 handleString(message,_.substring(6)) //compute的代码可简化如下 compute(2,3,_+_) compute(2,3,_*_) compute(2,3,_-_) // 此外 // val f1=(a:Int,b:Int)=>{a+b} // 等价于下式: // val f1=(_:Int)+(_:Int) // compute(2, 3, f1) //再来看一个例子 val list=List(1,3,5,7,9) list.foreach { x =>print(x) } list.foreach { _ =>print(_) } list.foreach { print(_) } list.foreach { x => x*2 } list.foreach { _*2 }
}
}
示例1:
object Demo07 {
//从1开始做加法,只加偶数,当加和累计超过50时,结束递归
//示意:2+4+6……
def f1(num:Int,sum:Int):Int={
if(sum>50) return sum;
if(num%2==0){f1(num+1,sum+num)}
else {f1(num+1,sum)}
}
def main(args: Array[String]): Unit = {
//num是用户传入的初始值=1,sum是最后求得的和,初始值是0
val result=f1(1,0)
println(result)
}
}
示例2,用递归方式实现斐波那契数列
//0 1 1 2 3 5 8 13
def f2(n:Int):Int={
if(n==0) return 0
if(n==1) return 1
else f2(n-1)+f2(n-2)
}
f2(6)
示例3——2 3 4 9 16 81 ?
// n的取值:f(0) f(1) f(2) f(3) f(4) f(5)
//当n为偶数时,f(n)=f(n-2)f(n-2)
//当n为奇数是,f(n)=f(n-2)f(n-2)
def f3(n:Int):Int={
if(n==0) return 2
if(n==1) return 3
else f3(n-2)*f3(n-2)
}
示例4——函数值:2 3 4 9 8 27 16 ?
//当n为偶数时,f(n)=2f(n-2)
//当n为奇数是,f(n)=3f(n-2)
def f4(n:Int):Int={
if(n==0) return 2
if(n==1) return 3
if(n%2==0)2*f4(n-2) else 3*f4(n-2)
}
示例5——求 1~n的数字之和
//1 3 6 10 15
//f(0) f(1) f(2) f(3) f(4)
//f(n)=f(n-1)+n+1
def f5(n:Int):Int={
if(n==0) return 1
else f5(n-1)+n+1
}
示例6—给定一个初始值n,并设定sum的初始值为0,当求和sum>12时结束递归
//0 1 3 6 10 15
//f(0,0)——n=0,sum=0
//f(1,1)——n=1,sum=1
//f(2,3)——n=2,sum=3
//f(3,6)——n=3,sum=6
//f(n+1,sum+n)
def f6(n:Int,sum:Int):Int={
if(sum>12)return sum
else f6(n+1,sum+n)
} //> f6: (n: Int, sum: Int)Int
f6(0,0) //> res3: Int = 15
示例7:
//给定一个scope范围,计算0~scope范围的整数之和,
//当和>12或者达到scope边界时,结束递归
def f7(n:Int,sum:Int,scope:Int):Int={
if(sum>12)return sum
if(n-1==scope)return sum
else f7(n+1,sum+n,scope)
} //> f7: (n: Int, sum: Int, scope: Int)Int
f7(0,0,4) //> res4: Int = 10
scala中,如果在递归时,保证函数体的最后一行为递归调用,则称这样的递归为尾递归。
scala会针对尾递归做优化处理,所以建议在写递归时写成尾递归形式
编程范式
函数式编程是一种编程范式,我们常见的编程范式有:
1)命令式编程(Imperative programming),常见的面向对象编程是也是一种命令式编程。比如java,c等。
命令式编程是完全依托于冯诺依曼体系机来实现的,即代码最后会转变为一条条的指令去执行,所以指令式编程的时间复杂度是和指令数相关的。根据摩尔定律,冯诺依曼体系机的性能可能本世纪30年代就不再提高,即当冯诺依曼体系机被淘汰时,指令式编程可能也会被淘汰。现在有一个名字就是:冯诺依曼瓶颈,即内存的速度跟不上cpu处理速度。IBM TrueNorth。
2)函数式编程,从理论上来说,函数编程是不依托于指令架构的,因为函数式编程是建立主体和主体之间的映射关系。但是目前从实际情况来开,函数式编程还是需要转变为指令去运行的。
函数式编程语言:HasKell,K#,Lisp
3)逻辑式编程,最常用的逻辑编程语言是Prolog,另外有较适用于大型方案的Mercury。
函数式编程
在函数式语言中,函数作为一等公民,可以在任何地方定义,在函数内或函数外,可以作为函数的参数和返回值,可以对函数进行组合。这个概念就是——高阶函数,高阶函数就是参数为函数或返回值为函数的函数。有了高阶函数,就可以将复用的粒度降低到函数级别,相对于面向对象语言,复用的粒度更低。
此外,函数式语言通常提供非常强大的集合类(Collection),提供很多高阶函数,因此使用非常方便。
函数式编程语言还提供惰性求值(Lazy evaluation,也称作call-by-need),是在将表达式赋值给变量(或称作绑定)时并不计算表达式的值,而在变量第一次被使用时才进行计算。这样就可以通过避免不必要的求值提升性能。
纯函数式编程语言中的变量的值是不可变的(immutable),也就是说不允许像命令式编程语言中那样多次给一个变量赋值。比如说在命令式编程语言我们写“x = x + 1”,这依赖可变状态的事实,拿给程序员看说是对的,但拿给数学家看,却被认为这个等式为假。
懒值 lazy 当val被声明为lazy时,它的初始化将被推迟,直到我们首次对它取值。
懒值对于开销较大的初始化语句而言十分有用
scala的柯里化的作用是结合scala的高阶函数,从而允许用户自建立控制结构。
柯里化(Currying)技术 Christopher Strachey 以逻辑学家 Haskell Curry 命名的(尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的)。它是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。
案例1:
object Demo08 {
def main(args: Array[String]): Unit = {
//首先我们定义一个函数:
def f1(a:Int,b:Int):Int={a+b}
//现在我们把这个函数变一下形: def f2(a:Int)(b:Int)={a+b} //那么我们应用的时候,应该是这样用:f2(2)(3),最后结果都一样是5,这种方式(过程)就叫柯里化。 val r1=f2(2)(3) //柯里化实质上会演变成这样一个函数: //接收一个参数a,返回一个匿名函数, //该匿名函数又接收一个参数b,函数体为a+b def f3(a:Int)=(b:Int)=>a+b val f4=f3(2) //请思考 f4(3)的值是多少?答案是:5 f4(3)
}
}
示例2:
object Demo09 {
def f1(a:Int,b:Int,c:Int):Int={a+b+c}
def f2(a:Int)(b:Int)(c:Int):Int={a+b+c}
def f3(a:Int)(b:Int,c:Int):Int={a+b+c}
def f4(a:Int,b:Int)(c:Int):Int={a+b+c}
def main(args: Array[String]): Unit = {
//f1和f2的函数的体现的是传入三个数,马上得到结果
f1(1,2,3)
f2(1)(2)(3)
//f3函数则可以体现:延迟执行的思想以及固定易变因素的思想 val r1=f3(1)(2,3)
}
}
示例3:
object Demo10 {
def f1(a:Int,b:Int,f:(Int,Int)=>Int):Int={f(a,b)}
def f2(a:Int)(b:Int)(f:(Int,Int)=>Int):Int={f(a,b)}
def f3(a:Int,b:Int)(f:(Int,Int)=>Int):Int={f(a,b)}
def f4(a:Int)(b:Int,f:(Int,Int)=>Int):Int={f(a,b)}
def main(args: Array[String]): Unit = {
//调用f3
f3(2,3){(a:Int,b:Int)=>a+b}
//可简化为下面的形式,我们发现这和scala中很多函数的形式很相近,比如:for(x<-1 to 10){print()}
//所以,柯里化的另外一个作用是让用户灵活的自义定自建控制结构
f3(2,3){+}
f3(2,3){*_}
// 延迟处理思想
def f3(a:Int)=(b:Int)=>a+b
val f4=f3(2)
f4(3)
}
}
总结柯里化的作用
柯里化技术在提高适用性、延迟执行或者固定易变因素等方面有着重要重要的作用,加上scala语言本身就是推崇简洁编码,使得同样功能的函数在定义与转换的时候会更加灵活多样。另外在Spark的源码中有大量运用scala柯里化技术的情况,需要掌握好该技术才能看得懂相关的源代码。
在scala柯里化中,闭包也发挥着重要的作用。所谓的闭包就是变量出了函数的定义域外在其他代码块还能其作用,这样的情况称之为闭包。就上述讨论的案例而言,如果没有闭包作用,那么转换后函数其实返回的匿名函数是无法在与第一个参数a相关结合的,自然也就无法保证其所实现的功能是跟原来一致的。