Scala简述:基于jvm的面向对象的函数式多范式编程语言。
详解:以java虚拟机为目标运行环境面向对象和函数式编程完美结合 Scala可以访问任何java类库 并且 Scala融合了静态类型系统、面向对象、函数式编程等语言特性 使用Scala可以编写更加简洁优雅的代码。
附加:Scala有交互式命令行(REPL,Read Eval Print Loop,读取-求值-输出-循环), 可以在上面快速的试各种语法和代码。这对学习新特性,或者实验新想法非常有用。
附加:什么是函数式编程?
函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。
对象与对象之间的关系是面向对象编程首要考虑的问题,而在函数式编程中,所有的数据都是不可变的,不同的函数之间通过数据流来交换信息,函数作为FP中的一等公民,享有跟数据一样的地位,可以作为参数传递给下一个函数,同时也可以作为返回值。
Scala的应用:Scala语言开发的两个重量级的产品:Spark、Kafka。
Scala的基础语法
命名规范和操作符
命名:区分大小写 - - Scala是区分大小写的,比如标识符Hello和hello在Scala中表示不同的含义
- 类名称 - - 对于所有类名,第一个字母应为大写。如果使用多个单词来形成类的名称,则每个内部单词的第一个字母应该是大写
示例:class MyFirstScalaClass,class Employee*类等。 - 方法名称 - - 所有方法名称应以小写字母开头。如果使用多个单词形成方法的名称,则每个内部单词的第一个字母应为大写。
示例:def myMethodName(),def getName()等。 - 程序文件名 - - 程序文件的名称可以不与对象名称完全匹配。这点与Java有所区别。文件的后缀名应为.scala。
示例:假设HelloWorld是对象名称,文件名称可以是a.scala。 - def main(args:Array [String]) - Scala程序从main()方法开始,这是每个Scala程序的强制性部分。main 方法应该在object中。
标识符:所有Scala组件都需要名称。用于对象,类,变量和方法的名称称为标识符。关键字不能用作标识符,标识符区分大小写。
- 标识符以字母或下划线开头,后面可以有更多的字母,数字或下划线。 $字符是Scala中的保留关键字,不应在标识符中使用
- Scala中的相关关键字
操作符: Scala支持Java中绝大多数操作符 在Scala中,可以使用加(+)、减(-) 、乘(*) 、除(/) 、余数(%)等操作符;
这些操作符就是方法。例如,5 + 3 和 (5).+(3) 是等价的,也就是说:a方法b 等价于 a.方法(b)前者是后者的简写形式,
这里的 + 是方法名,是Int类中的一个方法。
注意:Scala中并没有提供 ++ 和 -- 操作符
当需要递增和递减时,使用i+=1而不是i++;
值和变量
Scala有两种类型的变量:
val:不可变的,在声明时就必须被初始化,而且初始化以后就不能再赋值;
var:可变的,声明的时候需要进行初始化,初始化以后还可以再次对其赋值;
Scala中鼓励使用val,避免使用var
不需要给出值或者变量的类型,scala可以做自动类型推断。必要的时候可指定类型
val a1 = "Hello World 1"
val a2:String = "Hello World 2"
val a3:java.lang.String = "Hello World 3"
a1 = "Hello World 4"
error: reassignment to val
Scala常用类型
scala拥有和java一样的数据类型,和java的数据类型的内存布局、精度完全一致;
Scala的数据类型包括:Byte、Char、Short、Int、Long、Float、Double 和 Boolean;
和Java不同的是,在Scala中,这些类型都是“类”,都是包scala的成员;
Int的全名是scala.Int;对于字符串,Scala用java.lang.String类来表示字符串。
Scala类层次结构
基础类型都继承自AnyVal
引用类型都继承自AnyRef
AnyVal 与 AnyRef 继承自Any
字面量
条件表达式
if else 表达式与Java一样。但Scala表达式有值
val x = 10
val y = if (x>0) 1 else -1
var y = 0
if (x>0) y = 1 else y = -1
val y = if (x>0) 1 else -1
val x = -1
val y = if (x>0) 1
y: AnyVal = ()。()表示无用的占位符
Scala中没有switch语句
REPL是近视眼
if (x>0) 1
else if (x==0) 0 else -1
:paste +代码+ ctrl + d
块表达式 & 输出语句
- 块表达式:块语句是一个包含于{}中的语句序列
- {}块包含一些列表达式,其结果也是一个表达式
- 块中最后一个表达式的值就是块的值
-------------------------------------------------------------
- print println
printf("hello, %s! you are %d years old.\n", "Fred", 42) //支持C风格的字符串输出 - s 插值器。解析字符串中的变量,可调用方法,还能进行计算。调用StringContext中的s方法
val name="James"
println(s"Hello,$name")
- f 插值器。它除s的功能外(不指定格式就和s一样),还能进行格式化输出,在变量后用%指定输出格式
val height = 1.9
val name = "James"
println(f"$name%s is $height%2.2f meters tall")
- raw 插值器。能让字符串原原本本的输出来。
println(raw"a\nb\tc")
for循环
for (i <- 1 to 10)
r = r + i
for (i <- 1 until 10)
r = r + i
for (i <-1 to 3; j<-1 to 3) print((10*i+j) + " " )
// 守卫(guard)表达式(在循环后面加了if条件)
for (i <- 1 to 10 if i%2==0) println(i)
for (i <-1 to 3; j<-1 to 3[;] if i != j) print((10*i+j) + " " )
for推导式:for (i <- 1 to 10) yield i%3
for 循环中的 yield 会把当前的元素记下来(常用这种方式生成新数据集)
while、do 循环
var i = 9
while (i>0) {
i -= 1
println("i is " + i)
}
var i = 0
do {
i += 1
println(i)
} while (i<5)
Range 对象
在执行for循环时,我们经常会用到数值序列,比如,i的值从1循环到5,这时就可以采用Range来实现。Range可以支持创建不同数据类型的数值序列,包括Int、Long、Float、Double、Char、BigInt和BigDecimal等。
1、创建一个从1到5的数值序列,包含区间终点5,步长为1
(1 to 5)
2、创建一个从1到5的数值序列,不包含区间终点5,步长为1
(1until 5)
3、创建一个从1到10的数值序列,包含区间终点10,步长为2
(1 to 10 by 2)
4、创建一个Float类型的数值序列,从0.5f到5.9f,步长为0.3f
(0.5f to 5.9f by 0.3f)
5、将数值序列转换为数组或列表(经常用这个方法快速的创建集合类型)
(1 to 10 by 2).toArray
6、创建字符类型的Range
("A" to "Z" by 2)
7、注意这里有坑:
Range(1, 2, 3, 4, 5),表示这是一个Range,里面的值为1,2,3,4,5;
Range(1, 2, 3, 4, 5)编译是通不过的。 Range(1, 2, 3)代表什么意思?等价于(1 until 2 by 3)
8、Range(1, 5) 与 (1 to 5) 是不等价的!Range(1,5)等价于(1 until 5)
函数 :定义函数,需要指定名称、参数、函数体。如果函数有多条语句,使用{}
只要函数不是递归的,不需要指定返回类型。Scala可以进行自动类型推断:
def abs(x: Double) = if (x >= 0 ) x else –x
注意:可以使用return,但这种做法并不常见(推荐不用return)。
对于递归函数,必须指定返回值
def fac(n:Int):Int = if (n <=0) 1 else n*fac(n-1)
默认参数 & 带名参数
// n、left、right 为默认参数
// 递归函数求阶乘
def fac(n:Int=3):Int = if (n<=0) 1 else n * fac(n-1)
def decorate(str:String , left:String="[", right:String="]") = left + str + right
decorate("abcde", "(")
// right 为带名参数传入时 把参数名字带上
decorate("abcde", right="哈哈")
变长参数
def sum(args : Int*) = {
var result = 0
for (arg <- args) result += arg
result
}
val y = sum(1, 2)
val y = sum(1, 2, 3, 4)
懒值
懒值。当val被声明为lazy,它的初始化将被推迟,直到首次对它取值(只有val才能定义为lazy)
// 无论文件是否存在,都不会报错。仅仅做了一个标记
lazy val words = scala.io.Source.fromFile("a.scala").mkString
// 文件不存在时,报错!
val words = scala.io.Source.fromFile("a.scala").mkString
val words = scala.io.Source.fromFile("b.scala").mkString
懒值是开发懒数据结构的基础!
Scala基础语法:数组
数组是一种固定长度的、可变的、可索引的、元素具有相同类型的数据集合;数组是各种高级语言中最常用的数据结构;
Scala提供了参数化类型的通用数组类Array[T],其中T可以是任意的Scala类型,可以通过显式指定类型或者通过隐式推断来实例化一个数组。
val arr=new Array[Int](3)
arr(0)=1
arr(1)=2
arr(2)=3
可以不给出数组类型,Scala会自动根据提供的初始化数据来推断出数组元素类型
val arr=new Array(3)
arr(0)=1
arr(1)=2
arr(2)=3
变长数组
采用Array类型定义的数组属于定长数组,其数组长度在初始化后就不能改变;
如果要定义变长数组,需要使用ArrayBuffer参数类型,其位于包scala.collection.mutable中。变长数组在实际编程中用的比较多。
import scala.collection.mutable.ArrayBuffer
val b = ArrayBuffer[Int]()
b += 1 // 增加一个元素用 +
b += (2,3,4,5) // 增加多个元素也用 +
b ++= Array(6,7,8) // 增加一个数组用 ++
b -= 5 // 删除一个元素5,用-。元素不存在时,不报错
b --= Array(5,6,7,8) // 删除一个数组,用--
// 删除数组b最后5个元素在ArrayBuffer尾端添加、删除元素是一个高效的操作
b.trimEnd(5)
// 在下标2(下标从0开始)之前插入元素6(插入一个元素)
b.insert(2, 6)
// 在下标2(下标从0开始)之前插入元素7, 8, 8(插入多个元素)
b.insert(2, 7, 8, 8)
// 可在任意位置插入或移除元素,但不高效,因为后面所有的元素都必须被平移
// 移除索引为2的元素,返回值为被删除的元素值
b.remove(2)
// 移除以索引=2开始的3个元素,返回值为被删除的元素值
b.remove(2, 3)
定长数组与变长数组的转换:
val nums = b.toArray / b.toBuffer
多维数组,使用函数ofDim来定义二维和三维数组
val myMatrix = Array.ofDim[Int](3,4) //类型实际就是Array[Array[Int]]
val myCube = Array.ofDim[String](3,2,4) //类型实际是Array[Array[Array[Int]]]
可以使用多个圆括号来访问多维数组的元素,例如myMatrix(0)(1)返回第一行第二列的元素
数组操作
遍历数组
val b = (1 to 10).toArray // 将Range转为数组
for(i <- 0 until b.length) // 使用索引遍历数组
println(i + ": " + b(i))
for(i <- b.indices)
println(i + ": " + b(i))
for(i <- 0 until (b.length, 2))
println(i + ": " + b(i))
for(elem <- b) // 最简洁
println(elem)
数组转换
val a = Array(1,2,3,4,5,6,7,8,9,0)
val result = for (elem <- a) yield 2*elem
// 这才是转换具有scala风格的写法!
val result = a.map(2*_)
val result = a.filter(_%3==0)
数组常用方法
Array(1,2,3,4,5,6,7,8,9,10).sum
Array(2,3,4).product
Array(1,2,3,4,5,6,7,8,9,10).max
Array(1,2,3,4,5,6,7,8,9,10).min
Array(10,9,8,7,6,5,4,3,2,1).sorted
// max、min、sorted方法,要求数组元素支持比较操作
// 注意:map 与 foreach 的区别 map有返回值 而foreach 没有返回值
Array(1,2,3,4,5,4,3,2,1).map(_*2)
Array(1,2,3,4,5,4,3,2,1).foreach(_*2)
Array(1,2,3,4,5,4,3,2,1).reduce(_+_)
Array(1,2,3,4,5,4,3,2,1).distinct
Array(1,2,3,4,5,4,3,2,1).length
Array(1,2,3,4,5,4,3,2,1).size
Array(1,2,3,4,5,4,3,2,1).indices
// count计数,需要注意的是count中必须写条件 返回的是满足条件的个数
Array(1,2,3,4,5,4,3,2,1).count(_>3)
Array(1,2,3,4,5,4,3,2,1).count(_%2==0)
// filter 过滤数据,原始数组中的数据保持不变,返回一个新的数组
Array(1,2,3,4,5,4,3,2,1).filter(_>3)
Array(1,2,3,4,5,4,3,2,1).filterNot(_%2==0)
// 在REPL环境中输入数组名称即可打印数组元素,非常方便
// 在IDEA中,print(a) / print(a.toString)都不能打印数组元素
// 使用mkString / toBuffer 是打印数组元素简单高效的方法
Array(10,9,8,7,6,5,4,3,2,1).toString
Array(10,9,8,7,6,5,4,3,2,1).mkString(" & ")
Array(10,9,8,7,6,5,4,3,2,1).mkString("<", " & ", ">")
Array(10,9,8,7,6,5,4,3,2,1).toBuffer
val a= Array(1,2,7,3,4,9,100).filter(_>3)
a.foreach(println)
println(a.toBuffer)
println(a.mkString("<",",",">"))
结果
7
4
9
100
ArrayBuffer(7, 4, 9, 100)
<7,4,9,100>
// take取前4个元素;takeRight取后4个元素
//原始数组中的数据保持不变,返回一个新的数组
Array(1,2,3,4,5,6,7,8,9,10).take(4)
Array(1,2,3,4,5,6,7,8,9,10).takeRight(4)
// takeWhile 从左向右提取列表的元素,直到条件不成立(条件不成立时终止)
Array(1,2,3,4,5,6,1,2,3,4).takeWhile(_<5)
// drop 删除前4个元素;dropRight删除后4个元素;
// dropWhile删除元素,直到条件不成立
Array(1,2,3,4,5,6,7,8,9,10).drop(4)
Array(1,2,3,4,5,6,7,8,9,10).dropRight(4)
Array(1,2,3,4,5,6,1,2,3,4).dropWhile(_<5)
// 从左向右应用条件进行判断,直到条件不成立;最后将列表分为两个列表
Array(1,2,3,4,5,6,7,8,9,10).span(_==3)
// 第一个元素条件不成立,结束
// 返回两个数组:Array(), Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
Array(1,2,3,4,5,6,7,8,9,10).span(_<3)
// 返回两个数组:Array(1, 2), Array(3, 4, 5, 6, 7, 8, 9, 10)
Array(1,2,3,4,5,6,7,8,9,10).splitAt(4)
// 将列表分为前n个,与剩下的部分
Array(1,2,3,4,5,6,7,8,9,10).partition(_==3)
// 将列表分为两部分,第一部分为满足条件的元素,第二部分为不满足条件的元素
// 填充元素。填充到长度为16,元素为2;
// 从数组最后一个元素开始填充,原来的元素不变
Array(1,2,3,4,5,6,7,8,9,10).padTo(16,2)
// 数组切片。取第2到第4个元素(不包括第5个元素)
// 返回结果:Array(2, 3, 4)
Array(0,1,2,3,4,5,6,7,8,9,10).slice(2,5)
// sliding(size: Int, step: Int): Iterator[List[A]]
// 其实质是一个滑动窗口的概念。Size指定了窗口的长度,step指定滑动的步长
// 按照固定size进行分组(分为多个数组),步进为step(默认为1)
// 返回结果为迭代器,返回多个结果
Array(1,2,3,4,5,6,7,8,9,10,11,12).sliding(3).toArray
Array(1,2,3,4,5,6,7,8,9,10,11,12).sliding(3,2).toArray
// 返回结果:Array(Array(1, 2, 3), Array(3, 4, 5), Array(5, 6, 7), Array(7, 8, 9), Array(9, 10, 11), Array(11, 12))
// 将索引号为3的元素更新为4
Array(1,2,3,3).updated(3,4)
val a1 = Array("A","B","C")
val a2 = Array(1,2,3,4)
// 拉链操作(重要);a1,a2的长度不一致时,截取相同的长度
val z1 = a1.zip(a2)
// 拉链操作;a1,a2的长度不一致时,a1用 * 填充,a2用 -1 填充
val z2 = a1.zipAll(a2, "*", -1)
val z3 = a1.zipAll(a2, -1, "*")
// 用数组索引号填充
val z4 = a1.zipWithIndex
// unzip 的逆操作,拆分成2个数组
val (l1,l2) = z4.unzip
val (l1,l2,l3) = Array((1, "one", '1'),(2, "two", '2'),(3, "three", '3')).unzip3
val a1 = Array("A","B","C")
val a2 = Array(1,2,3,4)
val a3 = a1.zip(a2)
println(a3.toBuffer)
val z=a1.zipAll(a2,"*",-1)
println(z.toBuffer)
val z1=a1.zipWithIndex
println(z1.toBuffer)
val res= z1.unzip
println(res._1.toBuffer)
val res2= Array((6, "one", '1'),(2, "two", '2'),(3, "three", '3')).unzip3
println(res2._1.toBuffer)
ArrayBuffer((A,1), (B,2), (C,3))
ArrayBuffer((A,1), (B,2), (C,3), (*,4))
ArrayBuffer((A,0), (B,1), (C,2))
ArrayBuffer(A, B, C)
ArrayBuffer(6, 2, 3)
val a = (1 to 10).toArray
// 以下方法较为常用(重要)
// 取第一个元素
a1.head
// 取最后一个元素
a1.last
//取列表中的n个元素进行组合,返回一个迭代器(内容为:不重复的组合列表)
a.combinations(2).foreach(x=>println(x.toBuffer))
说明:上述的很多方法不仅仅对Array适用,一般情况下对其他集合类型同样适用。
val list = (1 to 10).toList
list.sum list.max list.take(4) list.drop(4)
用于数组的操作符(:+、+:、++)
:+ 方法用于在尾部追加元素;+: 方法用于在头部追加元素;
备注:冒号永远靠近集合类型,加号位置决定元素加在前还是后;
++ 该方法用于连接两个集合(数组、列表等),arr1 ++ arr2;
val a = (1 to 4).toArray
val b = (5 to 8).toArray
// 分别在集合头部、尾部增加元素;连接两个集合
val c = 10 +: a
val d = c :+ 9
val e = a ++ b
说明:这些操作符可用于Seq集合(简单的说就是List、Array等)
Map
映射(Map)是一系列键值对的容器;
在一个映射中,键是唯一的。可以根据键来对值进行快速的检索;
和集合一样,Scala 提供了可变的和不可变的两种版本的映射,分别定义在包 scala.collection.mutable 和 scala.collection.immutable 里;
默认情况下,Scala中使用不可变的映射;
如果要使用可变映射,必须明确地导入scala.collection.mutable.Map
构造Map
// 可使用两种方式定义Map
// Map缺省是不可变的。值不能更改
val a = Map("a" -> 1, "b" -> 2, "c" -> 3)
val a = Map(("a", 1),("b", 2),("c", 3))
a.keys
a.values
获取Map中的值:
a("a")
// 访问不存在的key时,会抛出异常。java.util.NoSuchElementException: key not found: x
a("x")
// 使用get方法,返回一个Option对象,要么是Some(键对应的值),要么是None。没有异常了
a.get("a")
// 获取键对应的值,如果键不存在返回给定的值(这里是0)
a.getOrElse("a",0)
更新Map中的值(要使用可变的Map)
val b = scala.collection.mutable.Map("a" -> 1, "b" -> 2, "c" -> 3)
b("a") = 2
b("d") = 4 // 增加了一个新元素
// 用 + 添加新的元素;用 – 删除元素
b +=("e" -> 1, "f" -> 2)
b -= "a"
// 增加元素
val b = Map("a" -> 1, "b" -> 2, "c" -> 3)
val c = b + ("a" -> 10, "b" -> 20)
迭代Map
val a = Map(("a", 1),("b", 2),("c", 3))
// 逐个访问 value
for(v <- a.values) println(v)
// key 和 value 做了交换
val b = for((k,v) <- a) yield (v,k)
// 下面才是具有scala交换风格的写法,推荐
a.map(x=>(x._2, x._1))
// 拉链操作创建Map
val a = Array(1,2,3)
val b = Array("a","b","c")
val c = a.zip(b)
val c = a.zip(b).toMap
Tuple
Tuple,元组。Map是键值对的集合。对偶是元组的最简单形态;
元组是不同类型的值的集合,元组中的元素可以是不同的数据类型,元组的应用非常广泛
val a = Tuple23(1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3)
//报错,元素的个数上限是22个
val a = (1, 1.2, "ad", 'd')
Tuple的访问形式比较特殊:
a._1 // 元组的下标从1开始
a _2
a._3
a _4
val (a1, a2, a3, a4), a5 = a
val (b1, _, b2, _), b5 = a
元组的遍历
a.productIterator
实例:
val a=(1,2,3,4,5,6)
val b= a.productIterator
while (b.hasNext){
println(b.next())
}