文章目录
  1. 1. 我眼中的Scala
  2. 2. 编程IDE
  3. 3. 变量定义
  4. 4. 函数定义
  5. 5. 变量不变性
  6. 6. 控制结构和函数
    1. 6.1. 加强版的if
    2. 6.2. 带有守卫条件的for
    3. 6.3. 没有continue、break的while
  7. 7. 数组
  8. 8. 类和伴生对象、特质
    1. 8.1.
    2. 8.2. 伴生对象
    3. 8.3. 接口的加强版-特质
  9. 9. 柯里化
  10. 10. 模式匹配和样例类
    1. 10.1. 模式匹配
    2. 10.2. 样例类
  11. 11. Actor
  12. 12. 隐式转换
  13. 13. 还有还有
  14. 14. 参考

       由于实验需要在半年前开始接触Scala,之前也学习/使用过TIOBE榜上Top20中一半左右的编程语言,感觉还是Scala给我印象最深,最近没怎么做相关的开发感觉都开始慢慢淡忘了,上周在技术分享时我对Scala作了一些总结,顺便在这里写下。

Scala注意,本文主要是描述我所了解的Scala相关的基础语法,和Java相同得在这里就不再累赘。

我眼中的Scala

Scala是一种基于JVM的编程语言,集成了面向对象和函数式编程的特性,既能处理脚本化得临时任务,又能处理高并发场景下分布式大数据应用。

       Java就是因为有JVM虚拟机才成就了现在的辉煌,Scala同样是运行在JVM,大致可以看做Java的升级版,由于现在大数据大势所趋,各种大数据框架的出现导致了Scala强势崛起!
       编程初学者最先接触的应该就是”Hello,World”,而WordCount可以看做大数据编程的入门必学技能,所以现在我们在”Hello,World”上实现CharCount:

1
2
3
4
5
6
7
8
9
//Scala Application
val str="Hello,wrold"
val data=str
.split(Array(',',' '))

.flatMap(for(c<-_) yield (c,1)) //好Api
.groupBy(_._1)
.mapValues(_.size)

println(data)

       假如你是用Java程序来写,最方便的莫过于HashMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Java Application
String str="Hello,World";
HashMap<Character,Integer> map=new HashMap<Character,Integer>();
for(Character ch:str.toCharArray())
{
if(!",".equals(ch))
{
if(!map.containsKey(ch))
{
map.put(ch, 0);
}
map.put(ch, map.get(ch)+1);
}
}
System.out.println(map);

其结果为

Map(e -> 1, l -> 3, H -> 1, r -> 1, w -> 1, o -> 2, d -> 1)

从上面的程序中看出Scala的几大特点:

  • 链式操作
  • 变量不变性
  • 丰富的Api
  • 简洁不简单(不需要加分号,返回值时不需要return关键字,还有强大的语法糖)

现在已经有非常多优秀的项目都是使用Scala来编写的:

  • Spark:不用多说,感觉最近Scala的崛起就是因为该项目的横空出世
  • Akka:分布式、高并发、高可伸缩性的消息驱动模型
  • Kafka:一个高吞吐量的分布式消息系统
  • play:一个高性能的Web框架,据说最近版本用Scala重写了
  • 据说Twitter公司好多中间件都是使用Scala来编写的

编程IDE

       目前编写Scala程序有两种比较流行的IDE:Eclipse for Scalaidea community edition,注意,eclipse版感觉功能没idea强,但是速度要比idea快(可能是自己电脑配置差的原因),还有下载idea时要下载社区版,他是免费的。
       当然如果是初学者的话还是建议使用万能sublime text来写,自己添加一个插件就好。Tools->Build System->New Build System…,然后输入:

{
    "cmd": ["scala","$file"],
    "selector": ["source.scala"],
    "shell": "true"
}

这样Scala后缀的程序在Sublime中直接使用Ctrl+B即可运行

变量定义

标准格式:{val|var} 变量名[:类型]=[new]Class

1
2
3
4
val a=5   //推断为Int类型
val b:Int=5 //显示的指定为Int类型
val c=5f //表示Float类型
val str:Option[String] //表示可空类型

函数定义

标准格式:def 方法名(参数列表)[:返回类型]={//方法体}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
////推断返回String
def hello()
{

"Hello"
}

//指定返回类型
def hello():String=
{
"Hello"
}

//简写
def hello():String="Hello"

//传参数,多个用逗号隔开
def hello(word:String):String="Hello"+word

//注意函数返回时不需要写return

变量不变性

Scala编程最为推崇的就是变量不变性:变量一旦定义了之后不再改变

  • 符合大数据的思想,比如分布式文件系统上只能进行增和删除操作,并没有修改操作
  • 在多线程环境下这个变量是安全的

Scala中有两种类型的不变性

  • val vs var

如果用val定义之后这个变量则不能再被其他变量进行赋值,但是可以对其内容进行修改,val定义的变量就没有这个限制。

1
2
3
4
5
6
7
8
9
import scala.collection.mutable.Map  //显示得先导入可变的Map包

val a=5
a=6 //则会报错:reassignment to val
//如果a用var定义则无影响

val map=Map(1->2)
map.put(2,3) //这里是修改了变量的内容
map.foreach(println(_)) //会输出(2,3),(1,2)

  • immutable vs mutable

如果定义的变量属于immutable包,则定义之后该变量的内容再不能再被修改(不显式导入默认为该包下的类型),mutable包下面的类则无此限制。

1
2
3
4
5
val immap=Map(1->2)//默认为immutable包下面的类
immap.put(2,3) //这里是修改了变量的内容 ,但是会报错 value put is not a member of scala.collection.immutable.Map

val map=scala.collection.mutable.Map(1->2) //显式指定包
map.put(2,3) //可以正常执行

val x=scala.collection.mutable.Type()可以理解为指针常量,指向的地址不可以重新赋值,但内容可以改变
var x=scala.collection.immutable.Type()可以理解为常量指针,指向的地址可以变,但内容不可以重新赋值

控制结构和函数

加强版的if

if除了正常的逻辑判断外,还可以直接返回相应的值,并且还支持返回不同类型的值

1
2
3
4
5
val a=if(false) 1 else "2"
a match{
case x:Int=>println("Int") //true时a=1
case x:String=>println("String") //false时a="2"
}

该功能在实际编程中灰常有用,之前写Java的时候都不得不在if外先定义变量,再在里面赋值-_-

带有守卫条件的for

守卫是什么东东?你可以理解为for里面的条件控制
先来看下Scalafor循环(与传统的三层for结构大不相同,不过与传统的foreach有点类似)

1
for(i<-0 until 5) println(i) //可以输出0,1,2,3,4

那如果我需要双重循环呢?

1
for(i<-0 to 5;j<-i+1 to 5) println(i*j) //大致就是5*5下三角的矩阵相乘值

再来看一下守卫

1
for(i<-0 to 5;if i%2==0) println(i) //输出0,2,4

这个守卫表示只有i%2==0的情况下才会进入循环体,这样是不是很方便。

没有continue、break的while

这个我就不吐槽了,Scala作者竟然认为continuebreak这两个这么顺手控制循环的功能是没用的,完全可以在守卫中加入条件判断来控制,这叫我们这帮C系狗情何以堪-_-

数组

数组作为绝大多数编程语言中的一个经典类型,在Scala是不支持[]这种定义的,而是用Array来定义。

标准格式:[val|var] 变量名[:Array[数组类型]]=Array(…)|new Array(..)

1
2
3
4
5
6
7
8
9
10
11
12
13
val array:Array[Int]=new Array[Int](5)
println("array.length="+array.length) //输出长度为5

val array2=Array("Mary","had","a","little","cat") //直接按内容进行初始化
println(array2.mkString("#"))//
println("the index is 2="+array2(3))//取索引为3的值


val array3:Array[(String,Int)]=Array(("xiaoming",23),("xiaohong",22))//使用元祖作为数组内容
array3.foreach(println(_))


val matrix =Array.ofDim[Double](3,4)//定义二维数组

类和伴生对象、特质

定义:class 类名(构造参数列表) extends,with 继承的类或者接口

与其他的面向对眼语言不同,Scala中的类支持直接在类名后跟随构造函数列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person(name:String){

private var age:Int=0;

def this(name:String,age:Int)
{
this(name);//一定要调用上一层的构造函数
this.age=age;
}


def getName=this.name//函数如果无参 可以去掉括号

def getAge=this.age
}

val p1=new Person("tom")
println(p1.getName)
val p2=new Person("tom",25)
println(p2.getAge)

伴生对象

类没有静态方法或者静态字段,需要用Object这个语法结构来达到同样的目的,一个Object一般与对应的类要在同一个文件中(通过Object就可以实现传说中的不用new关键词来实例化对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Student(name:String)
{

def getName()=name;
}

object Student{

//这个伴生对象可以和他的class共享变量
def apply(name:String):Student=
{
new Student(name)
}
}

val stu1=new Student("tom");
println(stu1.getName)
val stu2=Student("peter");//这里其实是调用了Object的apply方法
println(stu2.getName)

啥都不是说了,没有static是有点奇怪,但是这个Object也不错啊,用习惯就好了(^_^)

接口的加强版-特质

当然Scala里面也是没有Interface这种东西的,而是用trait特质来代替,这个特质类似接口,但是他可以自己实现方法(惊呆了,-_-),这样一来Scala就支持传说中的多继承了,关于多继承里面的菱形问题Scala是通过最后一个相同名称的方法来解决的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
trait Logger{
def log(msg:String){}//直接在特质里面实现了方法
}

trait ConsoleLogger extends Logger {
override def log(msg:String)
{

println(msg)//特质之间能直接继承
}
}

//使用width来实现多继承
//class ConsoleLogger extends Logger with Cloneable with Serializable

class Account{
protected var balance:Double=100
def getBalance=balance
}

//关于Scala的多继承就是通过继承特征来实现的 with trait 可以一下下去
class SaveingsAccount extends Account with Logger
{

def withdraw(amount:Double)
{

if(amount>balance)
{
log("amount>balance,can't withdraw")
}else{
log("withdraw"+amount)
balance-=amount;
}


}
}

println("s111111111")
val s=new SaveingsAccount();
s.withdraw(101);
s.withdraw(34);
println("remain"+s.getBalance)

println("\r\ns222222222")
val s2=new SaveingsAccount() with ConsoleLogger;//这个with其实是可以写到SaveingsAccount类上面的,写在这里只是为了说明Scala的灵活,实在是太灵活了。。
s2.withdraw(101);
s2.withdraw(34);
println("remain"+s2.getBalance)

输出的结果为:

s111111111
remain66.0

s222222222
amount>balance,can't withdraw
withdraw34.0
remain66.0

柯里化

可以将函数的参数列表使用多个括号分类来调用(在其他地方没见过吧~^_^)

1
2
3
4
5
6
7
8
9
def mul(x:Int,y:Int)=x*y  //普通青年
def mulOneAtATime(x:Int)=(y:Int)=>x*y //2B青年
def mulSample(x:Int)(y:Int)(z:Int)=x*y*z//文艺青年

val x=5
val y=6
println("mul",mul(x,y))
println("mulOneAtATime",mulOneAtATime(x)(y))
println("mulSample",mulSample(x)(y)(10))

一般我们在一个函数中如果需要传的参数可以分为几个类别,这样使用柯里化可以让整个函数的调用更加清晰一点。(恩,的确也是)

模式匹配和样例类

模式匹配

模式匹配match是一个更好的switch

Scalamatch除了可以匹配确定值以外,他还有:

  • 可以匹配数组、元祖、样例类等
  • 可以在匹配时加入守卫(就是匹配的判断条件啦^_^)
  • 可以在模式匹配之后进行值的返回
  • 不会有意外掉落到下一个分支问题(也就是没有break
  • 如果得不到匹配会跑出异常,所以如果不确保能全部覆盖匹配则再最后用_占位符来匹配所有情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
val ch:Char='-'
val sign=5;
val result =ch match{//有返回值
case '+'=>sign+1
case '-'=>sign-1
case _=>sign
}
println(result)

//进行是否大于5的判断
val flag=5
flag match{
case _ if flag>5=>println(">5")
case _ if flag<5=>println("<5")
case _ =>println(flag)
}

//使用类型来进行匹配
val obj=if(false) 5 else "5"
obj match{
case obj:Int=>println("i am Int")
case obj:String=>println("i am String")
case obj:BigInt=>println("i am bigint")
case _=>println("i am any")
}

//匹配数组
val arr=Array(0,7)
arr match{
case Array(0)=>println("0") //匹配只有一个0的数组
case Array(x,y)=>println(x+"+"+y) //匹配含有两个元素的数组
case Array(0,_*)=>println("start 0") //匹配以0为第一个元素的数组
case _=>println("any")
}

//匹配元组
val tuple=(1,2)
tuple match{
case (_,2)=>println("end2")//匹配以2结尾的元组
case (1,_)=>println("start 1")//匹配以1开头的元组
case _=>println("nothing")
}

PS:功能很强大吧,在Scala的程序里面随处都可以看到match,甚至很多情况下使用match来完成if-else的操作

样例类

样例类是一种特殊的类,它们经过优化以被用于模式匹配

你可以把样例类看做一个结构体,里面定义数据类型,然后在match操作的时候得以方便得传参

1
2
3
4
5
6
7
8
9
10
11
12
abstract class Amount  //定义一个抽象类
case class Dollar(value:Double) extends Amount //定义一个美元得样例类 继承了Amount
case class Currency(value:Double,unit:String) extends Amount //顶一个现金的抽象类 也是继承了Amount

case object Nothing extends Amount

val amt:Amount=Currency(6,"$") //实例化一个样例类 注意没有new
amt match{//使用样例类进行模式匹配
case Dollar(x)=>println(x)
case Currency(value,unit)=>println("oh,i get "+value+unit)//在这里得到了匹配,看到没?可以传参数
case Nothing=>println("nothing")
}

PS:Scala的框架中样例类也用的特别多,比如说Spark

Actor

Actor提供了并发程序中与传统的基于锁结构不同的另一个选择

他有两种发送消息的方式:

  • actor!message:发送消息之后非阻塞的,也没有返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import scala.actors.Actor

class HiActor extends Actor{ //自定义一个actor类,它一定是要继承Actor
def act()//重写该方法
{

while(true)
{
receive {//在这里接收消息,其实这里就是一个模式匹配
case "Hi"=>println("hello!")
case "Goodbye"=>println("bye bye!")
case _ =>println("i don't know")
}
}
}
}

val act=new HiActor
act.start //一定要启动之后才能发送

println("send Hi")
act!"Hi"
println("send Goodbye")
act!"Goodbye"

这样就会输出

send Hi
send Goodbye
hello!
bye bye!
  • actor!?message:发送消息之后会阻塞下面代码的执行,而且还是可以得到返回值的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import scala.actors.Actor

class AccountActor extends Actor
{

private var balance=0.0

def getBalance=balance
def act()
{

while(true)
{
//使用样例类来进行匹配
receive {
case Desposit(amount)=>
println("Desposit "+amount)
balance+=amount
sender!true
case WidthDraw(amount)=>

if(balance<amount)
{
println("the balance less than amount")
sender!false
}else{
println("WidthDraw"+amount)
balance-=amount
sender!true
}
}
}
}
}

case class Desposit(amount:Double) //存钱的样例类
case class WidthDraw(amount:Double) //取钱的样例类

val account=new AccountActor

account.start


/* 如果使用非阻塞的,整个存取钱的系统会不正常
println(account!WidthDraw(100))
println(account!Desposit(100))
println(account!WidthDraw(50))
println("account.getBalance"+account.getBalance)*/


println(account!?WidthDraw(100))//取100 会说钱不够,没法取
println(account!?Desposit(100))//存100
println(account!?WidthDraw(50))//再取50
println("account.getBalance"+account.getBalance)//可以正常取钱

输出的结果为:

the balance less than amount
false
Desposit 100.0
true
WidthDraw50.0
true
account.getBalance50.0

PS:大名鼎鼎的AKKA就是基于Actor来实现的,是分布式、高并发的消息驱动框架(听听就是高大上~^_^)

隐式转换

你知否曾希望某个类有某个方法,而这个类的作者却没有提供你?

所谓隐式转换函数指的是那种以implicit关键字声明的带有单个参数的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//定义一个现金转换类
class Currency(val value:Double){
def $2y=value*6 //美元转人民币
def y2$=value/6 //人民币转美元
}

//普通用法就是需要自己去实例化一个Currency类再去
val money=new Currency(100.0)
println(money.$2y) //执行去转换的方法


//这里使用隐式转换
implicit def double2Currency(value:Double)=new Currency(value)
println(100.$2y)//就可以直接在数字上使用该现金转换的方法了
//A:有同学可能要问了,这不是double转为Currency的嘛?怎么Int类型也能用?
//Q:不错,100是Int类型,但是这里默认是带有Int 转 Double的隐式转换

在使用隐式转换时只需要确保调用之前 已经直接了定义的隐式转换方法即可,而且一般定义转换名称为元类型2目标类型,这样比较好记啊(^_^)

Note:在Scala编写框架时他的功能类往往不会直接暴露出来,而是通过隐式转换的方式来让用调用 (这点可以让你的框架代码结构非常清晰,但是。。。你要看源码实际调用类的时候就很难找到了)

还有还有

  • 元组:这不是元祖月饼-_-,接触过Python的同学应该都知道吧
  • List,Map,etc的集合类
  • 万能占位符_,懒人模式用的,因为有时候懒得想变量
  • lazy 懒加载,在调用的时候才会加载。。明显有好处嘛^_^
  • 用索引取值一般用圆括号(i),定义变量类型、泛型一般用方括号[K,V]
  • 其他的好也好多都不熟悉了 -_-

参考

  • 《快学Scala》
  • 《Scala编程-中文版》
  • Scala

本作品采用[知识共享署名-非商业性使用-相同方式共享 2.5]中国大陆许可协议进行许可,我的博客欢迎复制共享,但在同时,希望保留我的署名权kubiCode,并且,不得用于商业用途。如您有任何疑问或者授权方面的协商,请给我留言

文章目录
  1. 1. 我眼中的Scala
  2. 2. 编程IDE
  3. 3. 变量定义
  4. 4. 函数定义
  5. 5. 变量不变性
  6. 6. 控制结构和函数
    1. 6.1. 加强版的if
    2. 6.2. 带有守卫条件的for
    3. 6.3. 没有continue、break的while
  7. 7. 数组
  8. 8. 类和伴生对象、特质
    1. 8.1.
    2. 8.2. 伴生对象
    3. 8.3. 接口的加强版-特质
  9. 9. 柯里化
  10. 10. 模式匹配和样例类
    1. 10.1. 模式匹配
    2. 10.2. 样例类
  11. 11. Actor
  12. 12. 隐式转换
  13. 13. 还有还有
  14. 14. 参考