Scala简介
Scala是一门类Java的多范式言语,它整合了面向目标编程和函数式编程的最佳特性。详细来讲
- Scala运转于Java虚拟机(JVM)之上,井且兼容现有的Java程序,相同具有跨平台、可移植性好、便利的废物回收等特性
- Scala是一门朴实的面向目标的言语
- Scala也是一门函数式言语
- Scala也是一门更合适大数据的言语
- Scala对调集类型数据处理有非常好的支撑
- Spark的底层用Scala编写
编程范式
- 编程范式是指核算机编程的根本风格或模范形式。常见的编程范式主要包括指令式编程和西数式编程。面向目标编程就属于指令式编程,比方C++、Java等
- 指令式言语是植根于冯•诺依曼体系的,一个指令式程序便是一个冯•诺依曼机的指令序列,给机器供给一条又一条的指令序列让其原封不动地履行
- 函数式编程,又称泛函编程,它将核算机的核算视为数学上的函数核算
- 函数编程言语最重要的根底是入演算,入演算对函数式编程特别是Lisp言语有着巨大的影响。典型的函数式言语包括Haskell、 Erlang和Lisp等
函数式编程与指令式编程
- 指令式编程触及多线程之间的状况同享,需求锁机制完成并发操控
- 函数式编程不会在多个线程之间同享状况,不需求用锁机制,能够更好并行处理,充分利用多核CPU井行处理才能
Scala的装置和Idea创立scala工程
Scala装置
Scala运转于Java虚拟机(JVM)之上,因而只需装置有相应的Java虚拟机,一切的操作体系都能够运转Scala程序,包括Window、 Linux、 Unix、 Mac Os等。
笔者运用的是1.8版别的JDK,2.13.10版别得scala,详细装置办法见官方文档Scala 2.13.10 | The Scala Programming Language (scala-lang.org)
官方供给了针对不同操作体系的装置包,下面以MacOS体系装置为例
装置办法1,假如是MacOS体系,能够运用Homebrew装置
brew update
brew install scala
装置办法2,下载对应体系的装置包,并运用tar指令解压
tar -zxf ~/scala-2.13.10.tgz
解压完成后获取解压后的目录,装备SCALA_HOME
vim ~/.zprofile # 修正zprofile文件
装备完成后,输入souce ~/.zprofile
使方才的装备收效,收效完成之后在指令行输入scala
,假如如下图所示进入scala解说器,则阐明scala装置成功了
咱们输入一个核算公式让scala帮咱们核算
假如需求退出scala解说器,能够输入:quit
指令
在Idea中创立Scala工程
首先需求装置JetBrains官方供给的scala插件
然后创立scala工程如下图所示,需求留意的是假如是第一次运用idea创立scala工程,或许挑选不到scala SDK,假如挑选不到scala SDK能够点击create,然后挑选Browse,在弹出框中挑选SCALA_HOME
目录,也便是scala装置包的根目录,挑选后idea就会主动识别scala SDK。
创立完成后有能够入下图所示,履行idea创立的main办法,操控台会输出”Hello World!”
scala根本语法
- 字面量(literal):字面量是呈现在源代码中的数据
比方下面所示
val i = 123 //123便是整效字面量
val i= 3.14 //3.14便是浮点数字面量
val i= true //true 便是布尔型字面量
val i= 'A' //A'便是字符字面量
val i="Hel1o" //"HeLLo”便是字符串字面量
-
数值(values):它由
val
声明,它是不行变的,在声明时就有必要被初始化,而且初始化今后就不能再赋值
// scala具备类型揣度,不需求声明类型
val myStr = "Hello world!"
// 也能够显式声明myStr2的类型为String
val myStr2 : String = "Hello World!"
在解说器中履行,能够发现两者是相同的
-
变量(Variables):它由
var
声明,它是可变的,声明的时分需求进行初始化,初始化今后还能够再次对其赋值
变量的声明办法与数值相似,这儿就不再独自举例
-
代码块(Blocks):代码块经过用
{}
围住表达式来表明表达式的组合。代码块的中的最后一个表达式代表这个代码块的成果。
// 下面这个代码块回来成果为3
println({
val x = 1 + 1
x + 1 // 3
})
-
函数(Functions):函数是具有参数而且接受参数的表达式。函数的界说由
=>
分隔,在=>
的左面是参数列表,=>
的右边是包括参数的表达式
例如咱们能够经过下面比方来界说函数
// 匿名函数
(x: Int) => x + 1
// 具名函数
val addOne = (x: Int) => x + 1
println(addOne(1)) // 调用addOne函数,打印成果为2
// 有多个参数的函数
val add = (x: Int, y: Int) => x + y
println(add(1, 2)) // 打印成果为3
// 无参函数
val getTheAnswer = () => 42
println(getTheAnswer()) // 打印成果为42
-
办法(Methods):办法看起来和函数非常相似,它与函数的区别是办法是经过
def
关键字界说,在def
后边是办法名(name),参数列表(parameter list),一个回来值(return type)和办法体(body)
// 办法的界说
def add(x: Int, y: Int): Int = x + y
println(add(1, 2)) // 3
// 多个参数的办法
def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier
println(addThenMultiply(1, 2)(3)) // 9
// 回来值为空的办法,回来值关键字为Unit,相似java中的void
def printName(name : String):Unit = println("name:" + name)
- 类(Classes):能够运用class关键字界说类,后跟其称号和结构函数参数
// 界说class,结构函数参数为prefix和suffix
class Greeter(prefix: String, suffix: String) {
def greet(name: String): Unit =
println(prefix + name + suffix)
}
// 创立目标并调用目标中的办法
val greeter = new Greeter("Hello, ", "!")
greeter.greet("Scala developer") // Hello, Scala developer!
- Case类(Case Class): Case Class是scala中一种非常特别的类,默许情况下,Case Class是不行变的,而且与一般的类不同的是,Case Class之间的比较是值比较,而一般类的比较是引证的比较,因而Case Class在形式匹配中非常有用
形式匹配会在后边的内容中介绍
// 界说Case Class
case class Point(x: Int, y: Int)
// 创立Case Class不需求 new 关键字
val point = Point(1, 2)
val anotherPoint = Point(1, 2)
val yetAnotherPoint = Point(2, 2)
// case class比较
point == anotherPoint // 回来成果是true
point == yetAnotherPoint // 回来成果是false
-
目标(Objects):目标是他们类的单例目标,用
object
关键字界说目标
// 创立object目标
object IdFactory {
private var counter = 0
def create(): Int = {
counter += 1
counter
}
}
// 调用单例目标中的办法
val newId: Int = IdFactory.create()
println(newId) // 1
val newerId: Int = IdFactory.create()
println(newerId) // 2
-
特质(Traits):特征是包括某些字段和办法的笼统数据类型。在Scala承继中,一个类只能承继另一个类,但它能够承继多个特质。特质由
trait
关键字界说,它与Java中的接口相似
咱们能够经过下面办法创立特质
trait Greeter {
// 没有完成的办法
def greet(name: String): Unit
// 具有默许完成的办法
def defaultGreet(name: String): Unit =
println("Hello, " + name + "!")
}
scala中的数据类型
在scala中,一切值都有类型,包括数值类型和函数类型。下面图片展示了类型层次结构的子集。
Any
是一切类型的超类,也被称作是尖端类型。它界说了全局的办法,例如equals
,hashCode
和toString
等,Any
的直接子类是AnyVal
和AnyRef
。
AnyVal
代表的是值类型。它包括9中预界说的值类型而且他们都对错空的,详细包括Byte、Char、Short、Int、Long、Float、Double、Boolean和Unit。其间Unit代表不包括由含义信息的值类型,它相似Java中的void
AnyRef
代表的是引证类型。一切非值类型都是引证类型,咱们自己界说的类也都是AnyRef
的子类,假如Scala在JRE中运用,它相当于是java.lang.Object
类型的转化
在Scala中值类型的转化准寻下面的图,它只能”向上”转化,不支撑”向下”转化,例如Short
能够转化为Int
,可是Int
不能转化为Short
,编译时会报错
scala中的Range
在履行for循环时,咱们常常会用到数值序列,比方,i的值从1循环到5,这时就能够选用Range来完成。
Range能够支撑创立不同数据类型的数值序列,包括Int、Long、Float、Double、Char、 BigInt和Big Decimal等
假如要创立步长是1的序列,能够运用如下办法
1 to 5 // 创立一个从1到5的数值序列,前闭后闭区间,步长为1
1.to(5) // 与上面等价
1 until 5 // 创立一个1到5的数值序列,不包括区间终点5,步长为1
1.until(5) // 与上面等价
1 to 10 by 2 // 创立一个1到10的数值序列,前闭后闭区间,步长为2
在scala解说器中履行成果如下所示
操控结构
if条件表达式
在scala中与Java不同的是,scala中的if表达式的值能够回来一个成果,if代码如下所示
// 标准if-else写法
if (a == b) {
doSomething()
} else {
doSomethingElse()
}
// if去除括号写法
if (a == b) doSomething()
val minValue = if (a < b) a else b // if条件表达式回来成果给变量赋值
for循环
for循环句子格式能够运用for(变量<-表达式)句子块
,其间,变量<-表达式
被称为生成器(generator)
,代码如下所示
// 写法1,运用序列循环遍历
val nums = Seq(1,2,3)
for (n <- nums) println(n)
// 写法2,运用Range循环遍历
for(i <- 1 to 3) println(i)
在解说器中履行成果如下所示
for循环中的**守卫(guard)**表达式,它能够过滤出一些满意条件的成果,它的语法如下所示
for( <identifier> <- <iterator> if <Boolean expression> )
咱们写一个for循环迭代器守卫的比方
// 取出1到5序列的偶数
for (i <-1 to 5 if i%2==0) println(i)
scala解说器中的履行成果如下所示
scala中也支撑多个迭代器,也称为嵌套迭代器(Nested iterators),能够用分号离隔,比方说两层循环能够选用如下代码
// 两层循环遍历,i:1到3 j:4到6
for(x <-1 to 2; y<-4 to 6) print(s"($x,$y) ")
// 也能够写成下面格式
for {x <- 1 to 2
y <- 4 to 6} {
print(s"($x,$y) ")
}
遍历成果如下所示,两种嵌套迭代器的写法成果相同
在多生成器场景,也能够为每个生成器增加一个守卫
// 两层循环遍历,i:1到3中的奇数 j:4到6中的偶数
for(i <-1 to 3 if i%2==1; j<-4 to 6 if j%2==0) printf("i:%d,j:%d\n",i,j)
履行成果如下所示
for推导式,scala的for循环结构能够在每次履行的时分发明一个值,然后将包括了一切生产量的调集作为for循环表达式的成果回来,调集中的类型由生成器中的调集类型确认,推导式代码结构为for(变量<-表达式)yield{句子块}
,例如如下代码
// 取出1到5序列中的偶数,并将偶数*10后放入到数组中
var result = for (i <- 1 to 5 if i % 2 == 0)
yield {
var j = i*10
println(j)
j
}
在scala解说器中履行成果为
While循环和Do/While循环
在scala中while循环和do/while循环语法与Java中相似,代码如下所示
// while循环
var x = 10
while(x>0) x-=1
// do/while循环
do x+=1 while(x<10)
反常处理
Scala不支撑Java中”受查看反常”(checked exception),将一切反常都当作”不受查看反常”(运转时反常),Scala中try-catch
结构捕获反常代码结构如下所示
var reader: FileReader = null
try {
reader = new FileReader("hello.txt") // 事务代码
} catch {
case ex: FileNotFoundException => // 反常处理
println("文件不存在")
case ex: IOException =>
println("IO反常")
} finally {
if (reader!=null) { // finally
reader.close()
}
}
scala中的数据结构
scala中的数据结构有容器(Collection)、列表(List)、调集(Set)、映射(Map)、迭代器(Iterator)、数组(Array)、元组(Tuple)
Scala供给了一套丰富的容器(collection)库,包括列表(List)、数组(Array)、调集(Set)、映射(Map)等
依据容器中元素的安排办法和操作办法,能够区分为有序和无序、可变和不行变等不同的容器类别.
Scala用了三个包来安排容器类,别离是scala.collection
、 scala.collection.mutable
和scala.collection.immutable
下图显现了scala.collection
包中的容器类。这些都是高档笼统类或特质(trait)。例如一切容器类的根本特质是Traverable特质。它为一切容器界说了共用的foreach办法,用于对容器元素进行遍历
列表(List)
-
列表是一种同享相同类型的不行变的目标序列。既然是一个不行变的调集,scala的List界说在scala.collection.immutable包中
-
不同于Java的
java.util.List
,scala的List一旦被界说其值就不能改动,因而声明List时有必要初始化
创立列表的办法如下所示
// 直接创立列表
var strList=List("Hello","World","linshifu")
// 在已有列表的前端增加元素,生成一个新的列表
var newStrList = "Say"::strList
// 运用与操控拼接
var newIntList = 1::2::3::Nil
上面办法调用完成后的成果如下图所示
列表有头部和尾部的概念,运用列表.head
能够获取列表的第一个元素的值,列表.tail
回来的是除第一个元素外的其他值构成的新列表,这体现出列表具有递归链表的值,详细运用办法如下图所示
调集(Set)
调集(set) 是不重复元素的容器(collection)。列表中的元素是按照刺进的先后次序来安排的,可是,调集中的元素并不会记载元素的刺进次序,而是以哈希办法对元素的值进行安排,所以,它答应你快速地找到某个元素
调集包括可变集和不行变集,别离位于
scala.collection.mutable
包和scala.collection.immutable
包,默许情况下创立的是不行变集
调集的运用办法
// 默许是不行变调集
// 创立一个元素
var mySet = Set("Hello","World")
// 创立一个新的调集,加上"linshifu"元素
mySet += "linshifu"
// 假如要声明一个可变调集
import scala.collection.mutable.Set // 导入可变调集
val myMutableSet = Set("Hello","World")
// 在可变调集中增加"linshifu"元素
myMutableSet += "linshifu"
映射(Map)
val greetMap = Map("hello"->"hi","你好"->"hello","hi"->"你好")
// 获取映射中的值,假如key不存在,则会抛出反常
println(greetMap("hello"))
println(greetMap("linshifu"))
// 假如不想抛出反常,能够先判别key是否存在
val greet = if(greetMap.contains("hello")) greetMap("hello") else 0
scala解说器履行成果如下所示
不行变映射无法更新映射中的元素,也无法新增元素,假如要更新映射的元素,就需求先界说一个可变映射,然后能够运用下面的办法增加元素
import scala.collection.mutable.Map
val greetMap = Map("hello"->"hi")
// 更新元素
greetMap("hello")="你好"
// 增加元素
greetMap("hi")="hello"
// 也能够运用+=操作来增加新的元素
greetMap+=("你好"->"hello","hi"->"你好")
// 也能够运用++=从别的一个Map中增加元素
greetMap++=Map("你好"->"hello","hi"->"你好")
// 删去元素
// 运用-=来删去元素
greetMap-="hello"
greetMap-=("hi","你好")
// 运用-=删去列表中的两个key
greetMap--=List("hi","你好")
假如要循环遍历Map的语法结构如下图所示for((k,v)<-映射)句子块
// 遍历上面的greetMap
for((k,v)<- greetMap) printf("key:%s and value:%s\n",k,v)
// 假如只想遍历key
for(k<- greetMap.keys) println(k)
// 假如只想遍历values
for(v<- greetMap.values) println(v)
迭代器(Iterator)
在scala中,迭代器(Iterator)不是一个调集,可是供给了拜访调集的一种办法。迭代器包括两个根本操作:next和hasNext。next能够放回迭代器的下一个元素hasNext用于检测是否还有下一个元素
val iter = Iterator("Hadoop", "Spark", "Scala")
// 运用while循环遍历
while (iter.hasNext) {
println(iter.next())
}
// 运用for循环遍历
val iter2 = Iterator("Hadoop", "Spark", "Scala")
for (elem <- iter2) {
println(elem)
}
Iterable有两个办法发生迭代器:
- grouped
这些迭代器回来的不是单个元素,而是元容器的全部子序列,用下面比方举例
val xs = List(1, 2, 3, 4, 5)
val group1 = xs grouped 3 // 会生成一个包括2个子列表的迭代器,子列表别离是: List(1,2,3),List(4,5)
while (group1.hasNext) {
println(group1.next())
}
println("----------------") // 会生成一个包括5个子列表的迭代器,子列表别离是: List(1),List(2),List(3),List(4),List(5)
val group2 = xs grouped 1
while (group2.hasNext) {
println(group2.next())
}
履行成果为
- sliding
sliding办法生成一个滑动元素的窗口的迭代器,与上面比方相似
val xs = List(1, 2, 3, 4, 5)
val sliding1 = xs sliding 3
while (sliding1.hasNext) {
println(sliding1.next())
}
println("----------------")
val sliding2 = xs sliding 2
while (sliding2.hasNext) {
println(sliding2.next())
}
履行成果如下所示
数组(Array)
数组是一种可变的、可索引的、元素具有相同类型的数据调集,它是各种高档言语中最常用的数据结构。Scala供给了参数化类型的通用数组类Array[T]
,其间T能够是任意的Scala类型,能够经过显式指定类型或许经过隐式揣度来实例化一个数组。
数组的创立、赋值和循环遍历比方如下
// 创立办法1
val intValueArr=new Array[Int](3)//声明一个长度为3的整型数组,每个数组元素初始化为
intValueArr(0)=12//给第1个数组元素赋值为12
intValueArr(1)=45//给第2个数组元素赋值为45
intValueArr(2)=33//给第3个数组元素赋值为33
// 创立办法2
intValueArr = Array(12,45,33)
// 遍历办法1
for(i<-intValueArr.indices) {
println(intValueArr(i))
}
println("----------------")
// 遍历办法2
for (elem <- intValueArr) {
println(elem)
}
履行成果如图所示
假如用到多维数组,运用办法如下
// 创立一个3行4列的二维数组
val myMatrix = Array.ofDim[Int](3,4)
myMatrix(0)(1) // 拜访1行2列的数组元素
// 创立一个三维数组
val myCube = Array.ofDim[String](3,2,4)
元组(Tuple)
元组是不同类型的值的调集。元组是一个不行变类型,元组和列表不同,列表中各个元素有必要是相同类型,而元组能够包括不同类型的元素。元组尤其合适办法中要回来多个类型的值的场景
// 运用()创立元组
val tuple = ("Hello","World",2023,5)
// 拜访元组中的第一个元素,tuple元素序号是从1开端的
println(tuple._1)
元组也支撑形式匹配,scala元组的形式匹配与ES6的解构赋值非常相似,例如
val ingredient = ("Sugar",25)
val (name, quantity) = ingredient
println(name) // Sugar
println(quantity) // 25
有时咱们在运用时会发现很难再元组和case class之间进行挑选,case class有具有命名的函数,这些称号能够提高代码的可读性
面向目标
简略的类界说
// 界说目标
class Counter{
private var value = 0
// 界说一个办法,冒号后是回来值类型,Unit代表不用回来,相似Java中的void
def increment():Unit = {value+=1}
// 界说一个办法,省掉回来值类型
def increment2() = {value+=1}
// 界说一个办法,省掉办法体的大括号
def increment3():Unit = value+=1
// 界说一个办法,回来value,回来值类型是Int
def current(): Int = {value}
}
// 创立目标
var myCounter = new Counter // 或许 new Counter()
// 调用Counter中的办法
myCounter.increment()
假如代码不在Object中的scala代码,没有被封装在目标中,无法编译成JVM字节码
主结构器
Scala的每个类都有主结构器。可是,Scala的主结构器和Java有着明显的不同Scala的主结构器是整个类体,需求在类称号后边罗列出结构器所需的一切参数,这些参数被编译成字段,字段的值便是创立目标时传入的参数的值。
主结构器中的参数不需求再独自界说类的字段,默许会被编译成类的字段
// 默许情况下是无参主结构器
class Pizza {
}
// 界说两个参数的主结构器
class Pizza (var crustSize: Int, var crustType: String){
}
辅佐结构器
scala不支撑为类界说多个结构器,它供给了辅佐结构器来完成相似Java多个结构器的功用
- Scala结构器包合1个主结构器和若干个(0个或多个)辅佐结构器
- 辅佐结构器的称号为this,每个辅佐结构器都有必要调用一个此前己经界说的辅佐结构器或主结构器
val DefaultCrustSize = 12
val DefaultCrustType = "THIN"
class Pizza (var crustSize: Int, var crustType: String) {
// 1个参数的辅佐结构器
def this(crustSize: Int) = {
this(crustSize, DefaultCrustType)
}
// 1个参数的辅佐结构器
def this(crustType: String) = {
this(DefaultCrustSize, crustType)
}
// 无参数辅佐结构器
def this() = {
this(DefaultCrustSize, DefaultCrustType)
}
override def toString = s"A $crustSize inch pizza with a $crustType crust"
}
参数中的默许值
Scala供给了为参数供给默许值的才能,这些默许值可用于答应调用方省掉这些参数。例如下面比方
// 界说一个函数,message的默许是Success,level的默许值是INFO
def log(message: String="Success", level: String = "INFO") = println(s"$level: $message")
// 假如只需一个参数,此刻message的值为haha,level取默许值为INFO
log("haha")
// 假如只想给level传参,message取默许值,能够运用如下办法
log(level = "WARNING")
// 假如不运用默许值,则两个参数都传
log("FAIL!","ERROR")
上面代码调用后成果如下图所示
getter和setter
给类中的字段设置值以及读取值,在Java中是经过getter和setter办法完成的。在Scala中,也供给了getter和setter办法的完成,可是并没有界说成getXxx和setXxx。
value变成private字段后,scala有没有供给getter和setter办法,scala给出的解决方案是经过界说相似getter和setter的办法,别离叫做value
和value_=
,例如下面的比方
**留意:**setter办法中
_=
的中心不能有空格,不然编译会报错
class Counter {
private var privateValue = 0
// 相当于privateValue的getter
def value = privateValue
// 相当于privateValue的setter
def value_= (newValue : Int) {
if (newValue > 0) {
privateValue = newValue
}
}
}
object MyCounter{
def main(args:Array[String]){
val myCounter = new Counter
// 读取value的值
println(myCounter.value)
// set value的值
myCounter.value=3
println(myCounter.value)
}
}
单例目标和伴生目标
单例目标
Scala并没有供给Java那样的静态办法或静态字段,可是,能够选用object关键字完成单例目标,具备和Java静态办法相同的功用。
能够看出单例目标的界说与类的界说很相似,明显的区别是,用object关键字而不是用class关键字
咱们能够经过如下代码界说单例目标
object Person {
private var lastId=0
def newPersonId()={
lastId+=1
lastId
}
}
// 调用单例目标的办法
Person.newPersonId()
伴生目标
在Java中,咱们常常需求用到同时包括实例办法和静态办法的类,在Scala中能够经过伴生目标来完成。当单例目标与某个类具有相同的称号时,它被称为这个类的“伴生目标”。类和它的伴生目标有必要存在于同一个文件中而且能够相互拜访私有成员(字段和办法),示例代码如下所示
// 类
class Person {
private val id = Person.newPersonId() //调用了伴生目标中的办法
private var name = ""
def this(name: String) {
this()
this.name = name
}
def info() {
printf("The id of %s is %d. \n", name, id)
}
}
// 单例目标
object Person {
private var lastId = 0 // 一个人的id
private def newPersonId() = {
lastId += 1
lastId
}
def main(args: Array[String]) {
val person1 = new Person("linShiFu")
val person2 = new Person("xiaoLin")
person1.info()
person2.info()
}
}
履行上面代码的成果如下图所示
从上面成果能够看出,伴生目标中界说的newPersonld(实际上就完成了Java中静态(static)办法的功用
Scala源代码编译后都会变成JVM字节码实际上,在编译上面的源代码文件今后在Scala里边的class和object在Java层面都会被合二为—,class里边的成员成了实例成员,object成员成了static成员
应用程序目标
每个Scala应用程序都有必要从一个目标的main办法开端,例如
object HelloWorld{
def main(args: Array[String]){
println("hello,world")
}
}
目标的apply办法和update办法
咱们常常会用到目标的apply办法和update办法,尽管咱们表面上并没有察觉,可是实际上,在Scala中,apply办法和update办法都会遵从相关的约好被调用,约好如下
apply办法
用括号传递给变量(目标)一个或多个参数时,Scala 会把它转化成对apply办法的调用。
类的apply演示代码如下
class TestApplyClass {
def apply(param:String):String={
println("apply method called,parameter is :"+param)
"Hello World"
}
}
var myObject = new TestApplyClass
// 这儿会调用apply办法
println(myObject("param1"))
单例目标演示代码如下
object TestApplySingleObject {
def apply(param1:String,param2:String):String = {
println("apply method called")
param1+"and"+param2
}
}
val group = TestApplySingleObject("Xiaolin","LinShiFu")
println(group)
下面看一个运用apply办法的比方。因为Scala中的Array目标界说了apply办法,因而,咱们能够选用如下办法生成一个数组
val myStrArr = Array("BigData","Hadoop","Spark")
用括号传递给变量(目标)一个或多个参数时,Scala 会把它转化成对apply办法的调用
update办法
当对带有括号并包括一到若干参数的目标进行赋值时,编译器将调用目标的update办法,在调用时,是把括号里的参数和等号右边的目标一起作为update办法的输入参数来履行调用
val myStrArr = new Array[String](3)
myStrArr(0) = "BigData" // 实际上调用了伴生类Array中的update办法,履行myStrArr.update(0,"BigData")
从上面能够看出,在进行元组赋值的时分,之所以没有选用Java的方括号myStrArr[0]而是选用圆括号的形式,myStrArr(0),是因为存在上述update办法机制
承继
Scala中的承继与Java有明显的不同
- 在子类中重写超类的笼统办法时不需求运用override关键字
- 在子类中重写超类的笼统办法时不需求运用override关键字
- 能够重写超类中的字段
- 只需主结构器能够调用超类的主结构器
笼统类
在Scala中,笼统类的界说需求留意下面几点
- 界说一个笼统类,需求运用关键字abstract
- 界说一个笼统类的笼统办法,不需求abstract关键字,只需求把办法体空着,不写办法体就能够了
- 笼统类中界说的字段,只需没有给出初始化值,就表明是一个笼统字段,可是,笼统字段有必要要声明类型,例如:
val name:String
,便是把name声明为字符串类型,不能省掉类型,不然编译会报错 - 笼统类不能直接被实例化,要经过扩展类的办法或许说是承继的办法
笼统类的界说能够参考如下代码
abstract class Person{ // 笼统类,不能被直接实例化
val country:String // 笼统字段,没有初始值
def speak() // 笼统办法,不需求运用abstract关键字,没有办法体
}
假如需求承继上面的类能够经过下面的办法
class Chinese extends Person {
// 重写超类的字段,有必要加上override关键字,不然编译报错
override val country: String = "china"
// 完成上面笼统办法,能够不加override关键字,也能够加上override关键字
override def speak(): Unit = {
println("speak Chinese")
}
}
特质
前面文章中咱们已经介绍了特质,本小节咱们将详细介绍特质的界说和运用。特质主要用于在类之间同享接口和字段。它们相似于Java 8的接口。类和目标能够承继特质,但特质不能实例化,因而没有参数。特质的界说如下面代码所示
trait Animal{
var age:Int
def barking():Unit
}
上面界说了一个特质,里边包括一个笼统字段age和笼统办法barking(),在特质中的笼统办法不需求运用abstract关键字,特质中没有办法体的办法,默许便是笼统办法。
上面的实例中,特质只包括了笼统字段和笼统办法,相当于完成了相似Java接口的功用。实际上,特质也能够包括详细完成,也便是说,特质中的字段和办法不一定要是笼统的
trait Dog {
def running(){println("The dog is running!")}
}
特质界说好之后,就能够运用extends或with关键字把特质混入,代码如下所示
class MyDog extends Animal with Dog {
override var age: Int = 10
override def barking(): Unit = {println("Wang~")}
}
特性作为泛型类型和笼统办法变得特别有用,例如下面代码,承继Iterator需求类型A,需求完成办法hasNext和next
trait Iterator[A] {
def hasNext: Boolean
def next(): A
}
// 完成Iterator
class IntIterator(to: Int) extends Iterator[Int] {
private var current = 0
override def hasNext: Boolean = current < to
override def next(): Int = {
if (hasNext) {
val t = current
current += 1
t
} else 0
}
}
// 创立IntIterator实例
val iterator = new IntIterator(10)
iterator.next() // 回来 0
iterator.next() // 回来 1
形式匹配
Scala的形式匹配最常用于match句子中。下面是一个简略的整型值的匹配实例
val colorNum = 1
val colorStr = colorNum match {
case 1 => "red"
case 2 => "green"
case 3 => "yellow"
case _ => "Not Allowed"
}
println(colorStr)
上面代码运转成果如下图所示
别的在形式匹配的case句子中,还能够运用变量,例如如下代码
val colorNum = 4
val colorStr = colorNum match {
case 1 => "red"
case 2 => "green"
case 3 => "yellow"
case unexpected => unexpected + " is Not Allowed"
}
println(colorStr)
履行后成果如下所示
也能够对表达式类型进行匹配
for (elem <- List(9, 12.3, "Spark", "Hadoop", 'Hello)) {
val str = elem match {
case i: Int => i + " is an int value."
case d: Double => d + " is a double value."
case "Spark" => "Spark is found."
case s: String => s + " is a string value . "
case _ => "This is an unexpected value."
}
println(str)
}
履行后的成果如下图所示
也能够在形式匹配中增加一些必要的处理逻辑
for (elem <- List(1, 2, 3, 4)) {
elem match {
case _ if (elem % 2 == 0) => println(elem + " is even.")
case _ => println (elem + " is odd.")
}
}
履行后的成果如下图所示
case类
case类是一种特别的类,它经过在class前增加关键字case界说case类,它用于与case表达式进行形式匹配。case有下列优势:
- 编译器主动改动结构函数参数为不行变字段
- 编译器主动给case类增加equals、hashCode、toString办法
- 实例化目标不需求运用new关键字
object Main {
case class Employee(id: Int, employee_name: String) // case class
def main(args: Array[String]): Unit = {
val a = Employee(1, "abc1")
val b = Employee(2, "abc2")
val c = Employee(3, "abc3")
for (emp <- List(a, b, c)) {
emp match {
case Employee(1, "abc1") => println("Hello abc")
case Employee(2, "abc2") => println("Hello xyz")
case Employee(id, employee_name) => println("ID: " + id + ", Employee:" + employee_name)
}
}
}
}
Optional类型
- 标淮类库中的Option类型用case类来表明那种或许存在、也或许不存在的值
- 一般而言,关于每种言语来说,都会有一个关键宇来表明一个目标引证的是 “无”,在Java中运用的是null。Scala交融了西数式编程风格,因而,当预计到变量或许西数回来值或许不会引证任何值的时分,建议你运用Option类型
- Option类包括一个子类Some,当存在能够被引证的值的时分,就能够运用Some来包括这个值,例如Some(“Hadoop”)。而None则被声明为一个目标,而不是一个类,表明没有值
- Option类型还供给了getOrElse办法,这个办法在这个option是Some的实例是回来对应的值,而是在None的实例是回来传入的参数
示例如下
Option[T]实际上是一个容器,咱们能够把它看做是一个调集,只不过这个调集中要么只包括一个元素(被包装在Some)中回来,要么就不存在元素(回来None),既然是一个调集咱们就能够对它运用map、foreach或许filter等办法。
例如接上面比方,履行情况如下所示
函数式编程
函数式编程一些界说
函数的运用办法和其他数据类型的运用办法完全一致了。这时,咱们就能够像界说变量那样去界说一个函数,由此导致的成果是,函数也会和其他变量相同,开端有”值”。
函数的类型与值,咱们拿下面这个函数界说举例
def counter (value:Int):Int = {value+1}
上面这个函数的类型为(Int)=>Int
,它表明入参是Int类型,回来值是Int类型。函数的值便是把函数界说中类型声明的部分去除,剩下的部分便是函数的值,上面函数的值为(value)=>{value+1}
因而咱们能够得出函数的界说办法如下所示
从上面能够看出,在Scala中,函数已经是它”头等公民”,独自剥离出来了”值”的概念,一个函数”值”便是函数字面量。这样咱们只需在某个声明函数的地方声明一个函数类型,在调用的时分传一个对应的函数字面量即可,和运用一般的变量一模相同
参数组
经过前面咱们了解了函数的界说参数化,并用小括号围住参数表,在scala中还供给了别的一种挑选,能够把参数表分化为参数组(parameter groups),每隔参数组用小括号分隔,例如下面比方
def max(x: Int)(y: Int):Int = if (x > y) x else y
匿名函数和闭包
假如不需求给每个函数命名,这是就能够运用匿名函数,例如下面这个
(num: Int) => num + 1
上面这种匿名函数的界说形式,一般被称为”Lambda表达式”,它的形式如下
(参数) => 表达式 // 假如只需一个参数,则能够省掉圆括号
函数的类型揣度
scala中的类型揣度机制,能够主动揣度变量类型,比方比方
val number : Int =10
// 等价于下面句子,而且省掉Int类型声明
val number =10
函数界说也能够运用类型揣度机制,例如下面比方
// 完好的函数界说如下
val incrFunc: Int => Int = (num: Int) => num + 1
// 咱们能够经过函数类型揣度出函数值中的类型,因而能够省掉函数值中的类型,而且因为只需一个参数,因而能够省掉括号
val incrFunc: Int => Int = num => num + 1
// 咱们也能够依据函数值中的类型揣度出函数的类型,因而能够省掉函数的类型
val incrFunc= (num: Int) => num + 1
高阶函数
高阶函数(higher-order function)也是函数,它包括一个函数类型的值作为输入参数或回来值,例如下面比方
def safeStringOp(s:String,f:String=>String)={
if(s!=null) f(s) else s
}
占位符语法
为了让字面量更简练,咱们能够运用下划线作为一个或多个参数的占位符,只需每个参数在字面量内仅呈现一次而且函数的显式类型在字面量之外指定,咱们拿下面的函数举比方
// 原函数为
val incrFunc: Int => Int = (num: Int) => num + 1
// 运用占位符后的函数为
val incrFunc:Int=>Int = _+1
函数作为参数时也能够运用占位符语法,如下所示
// 函数界说
def safeStringOp(s:String,f:String=>String)={
if(s!=null) f(s) else s
}
// 字符反向
safeStringOp("Ready",_.reverse)
占位符也能够用于多个参数场景
def combination(x: Int,y: Int,f: (Int,Int)=>Int) =f(x,y)
combination(10,5,_*_)
上面比方运用两个占位符,他们会按方位替换输入参数(别离是x和y),第一个占位符代表x,入参为10,第二个占位符代表y,入参为5,履行成果如下所示
部分引证函数
在调用函数时,一般要在调用中指定函数的一切参数(除了包括默许函数值的函数)。假如咱们想重用一个函数,而且不用再次输入其间一些参数,就能够运用如下语法
// 界说一个函数
def factorOf(x:Int,y:Int)=y%x==0
// 只保留一个参数,运用通配符代替y,在这儿有必要给通配符一个显式的类型
val multipleOf3 = factorOf(3,_:Int)
// 调用部分引证函数
multipleOf3(9)
运转成果如下图所示
调集结构
与Java相同,Scala有一个高性能的面向目标的类型参数化调集结构,而且Scala调集与Java中的调集比较,还供给了一些高阶操作,比方fold,scan和reduce等,能够用更简练的表达式来管理和处理数据。Scala中还有独自的可变调集和不行变调集类型的层次体系,能够完成很便利地在不行变数据和可变数据之间转化,下面咱们先来介绍不行变调集。
集(Set)
Scala中的Set与Java的Set相似,它是一个包括不重复元素的调集, Set常用的运用办法如下
// 创立set
val sets = Set(1, 2, 3, 4, 4, 2)
println(sets)
// 判别set中是否存在满意某个条件的值
println(sets.exists(_%2==0))
// 判别set中是否包括某个值
println(sets.contains(3))
// 丢弃两个set中的元素,并创立一个新的set
println(sets.drop(2))
履行成果如下所示
映射(Map)
Scala中的映射与Java中的映射相似,下面是Map的创立与遍历的办法
// 创立Map
val maps = Map("A" -> 1, "B" -> 2, "C" -> 3)
// 遍历办法1
for (elem <- maps) {
print(s"(${elem._1}:${elem._2})")
}
// 遍历办法2
println("")
println("============")
maps foreach { kv=>print(s"(${kv._1}:${kv._2})")}
// 遍历办法3
println("")
println("============")
maps foreach {case (k,v) => print(s"($k:$v)") }
履行成果如下所示
获取元素、增加元素和删去元素
var maps = Map("A" -> 1, "B" -> 2, "C" -> 3)
// 获取元素
println(maps("A"))
// 增加元素
maps += ("D" -> 4)
println(maps)
// 删去元素
maps -= "B"
println(maps)
履行成果如下所示
-
++
,--
:前者是Map的合并,后者是删去Map中的元素
val maps1 = Map("A" -> 1, "B" -> 2, "C" -> 3)
val maps2 = Map("A" -> 10, "D" -> 5, "E" -> 6)
// maps1和maps2合并
println(maps1 ++ maps2)
println(maps2 ++ maps1)
// 删去maps1中A,B
println(maps1 -- List("A","B"))
履行成果如下所示,能够发现映射合并++
,假如两个映射存在相同的key,则++
前映射key的值会被++
后映射key的值覆盖
列表(List)
List是一个不行变的单链表,它是有序的调集。它能够作为一个函数调用List来创立一个列表,下面咱们看List常用的办法
- head():获取列表的第一个元素
- tail():获取列表除了第一个元素之外的剩下元素
- List(n):获取列表第n个元素
// 创立列表
val list=List(1,2,3,4)
// 获取第1个元素
println(list.head)
// 获取剩下元素
println(list.tail)
// 获取第二个元素
println(list(2))
履行成果如下所示
- foreach():遍历列表中的元素,运用办法与Java中的语法相似
- map():将列表中的元素转化成另一个元素,并生成一个由转化后元素的列表
-
reduce():将列表规约为一项,入参有两个,第一个元素是规约后的元素,第二个元素是列表中的元素,reduce默许是从列表的的左面开端规约。scala还供给两种从不同放下开端规约的办法
reduceLeft()
和reduceRight()
示例代码如下
val list=List(1,2,3,4)
// foreach遍历数组
list.foreach(item=>println(item))
// map转化数组
println(list.map(item => item * 2))
// reduce 规约数组
println(list.reduce((a,b)=>a+b))
履行成果如下所示
上面办法运用办法与Java中简直相同,下面是Java中的foreach、map和reduce办法的运用办法
List<Integer> list = Arrays.asList(1, 2, 3, 4);
// foreach
list.forEach(item-> System.out.println(item));
// map
list.stream().map(item -> item * 2);
// reduce
list.stream().reduce((a, b) -> a + b);
除了上面表述的遍历列表的办法,在scala中还有别的一种遍历列表的办法
// 创立列表
val list=List(1,2,3,4)
// 遍历数组
for(item <- list) print(item)
Cons操作符
除了上面咱们在比方中创立列表的办法外,Scala还支撑运用cons(construct)操作符来构建列表。运用Nil作为根底(Nil实际上是List[Nothing]
的一个单例实例),并运用右结合的cons操作符::绑定元素,构建新的列表,示例如下
// 构建一个由1,2,3,4构成的列表
val list = 1::2::3::4::Nil
println(list)
// 咱们也能够运用已有的列表,在表头增加元素,生成新的列表
val newList = 5::list
println(newList)
履行后的成果如下所示
cons语法只适用于在列表的左面增加元素,无法在列表的右边增加元素,假如咱们运用这种写法
val newList=list::5
,则编译会报错
咱们还能够运用:::
将两个列表结合,它与::
不同的是::
会将左面的元素作为一个全体追加到右边的列表中,而:::
会将左面的元素当做是列表与右边的列表结合,如下所示
val list1 = 1 :: 2 :: 3 :: Nil
val list2 = 4 :: 5 :: 6 :: Nil
val newList = list1::list2
println(newList)
val newList2 = list1:::list2
println(newList2)
履行上面代码成果如下所示
:::
仅适用于两头都是列表,假如左面是某个元素,右边是列表,如7:::list
,则编译会报错
列表的算术运算
在Scala中还支撑算术运算,或许说是对调集操作的办法,这些操作能够增加、删去、分化、合并以及修正列表的安排、而不改动列表元素(即期内容)本省。因为Scala中List是一个不行变的调集,因而前面所说的”修正”其实是回来一个新的列表。
-
++
:为列表追加另一个调集
假如++
两头是列表,其作用于:::
相同,与:::
不同的是,++
支撑列表(List)与集(Set)追加,然而:::
仅适用于列表与列表追加
val list1 = List(2, 3) ++ List(4, 5)
println(list1)
val list2 = List(2, 3) ++ Set(4, 5)
println(list2)
履行成果如下所示
-
==
:调集是否相同
调集运用==
相当于Java中的equals
,假如调集中的类型值都相同,则回来true
val list1 = List(1, 2, 3, 4)
val list2 = List(1, 2, 3, 4)
val list3 = List(1, 2, 4, 3)
val list4 = List('1',2,3,4)
// 相同
println(list1==list2)
// 元素次序不同
println(list1==list3)
// 元素类型不同
println(list1==list4)
履行成果如下所示
作为比照,下面是Java中的比方
List<Integer> list1 = Arrays.asList(1, 2, 3, 4);
List<Integer> list2 = Arrays.asList(1, 2, 3, 4);
// list1与list2引证是否相同
System.out.println(list1==list2);
// list1与list2是否相同
System.out.println(list1.equals(list2));
履行成果如下所示
-
distinct
:回来不包括重复元素的列表
val distinct = List(1, 2, 2, 3, 3, 3, 4, 5, 5).distinct
println(distinct)
履行成果如下所示
-
drop
:从列表中删去前n个元素
val lists = List(1, 2, 3, 4, 5)
println(lists.drop(2))
履行成果如下所示
-
slice
:回来列表的一部分,从第1个索引到第2个索引(前开后闭区间),这儿的索引序号是从0开端的
val lists = List(1, 2, 3, 4, 5)
println(lists.slice(2,4))
履行成果如下所示
-
fliter
:过滤原列表中的元素,将回来过滤值为true的元素
val lists = List(1, 2, 3, 4, 5)
println(lists.filter(_ > 3)
履行成果如下所示
-
flatten
:将List中的列表元素转化为元素的列表
这个办法与Java流操作中的flatMap
相似
val lists = List(List(1,2,3), List(4, 5))
println(lists.flatten)
履行成果如下所示
-
partition
:依据true/false,将本来列表分组为两个列表构成的元组
val lists = List(1, 2, 3, 4, 5)
println(lists.partition(_ > 3))
履行成果如下所示
-
reverse
:将列表倒置
val lists = List(1, 2, 3, 4, 5)
println(lists.reverse)
履行成果如下所示
-
sortBy
、sorted
、sortWith
:sortBy
是按照某个规矩排序,sorted
是对一个调集进行自然排序,sortWith
基于某个规矩排序,经过comparator
函数,完成自界说排序的逻辑
val lists = List(1,5,3,4,6,2,-1,-4,-3)
println("=====sortBy=====")
println(lists.sortBy(d=>math.abs(d))) // 次序
println(lists.sortBy(d=>math.abs(d)).reverse) // 倒序
println("=====sorted=====")
println(lists.sorted) // 次序
println(lists.sorted.reverse) // 倒序
println("=====sortWith=====")
println(lists.sortWith(_>=_)) // 次序
println(lists.sortWith(_<=_)) // 倒序
履行成果如下所示
-
splitat
:将列表从指定索引方位拆分为两个列表构成的元组
**留意:**索引方位的元素会被放入后边一个元组
val lists = List(1, 2, 3, 4, 5)
println(lists.splitAt(3))
履行成果如下所示
-
take
、takeRight
、takeWhile
:take
获取列表中的前n个元素。takeRight
获取列表后n个元素。takeWhile
从列表左面开端遍历,假如元素不满意则中止遍历,把前面满意条件的元素放入新的列表中
val lists = List(1, 2, 3, 4, 5)
println(lists.take(3))
println(lists.takeRight(3))
// 回来前两个元素
println(lists.takeWhile(_ <= 2))
// 第一个元素不满意条件,将回来空列表
println(lists.takeWhile(_ > 2))
履行成果如下所示
-
startWith
、endWith
:查看列表是否以给定的列表最初或许给定的列表结束
val lists = List(1, 2, 3, 4, 5)
println(lists startsWith List(1, 2))
println(lists startsWith List(1, 3))
println(lists endsWith List(4, 5))
履行成果如下所示
-
forall
:查看列表中是否每个文件都满意参数中的条件
val lists = List(1, 2, 3, 4, 5)
println(lists forall (_ < 6))
println(lists forall (_ > 3))
履行成果如下所示
-
fold
,foldLeft
,flodRight
:这是一个规约列表函数,也称为折叠(fold),它的作用是给定一个起始值和一个规约函数来规约列表,默许从左开端规约,别的Scala还供给了foldLeft
和foldRight
从不同方向规约
val lists = List(1, 2, 3, 4, 5)
println(lists.fold(0)(_ + _))
println(lists.foldLeft(0)(_ + _))
println(lists.foldRight(0)(_ + _))
履行成果如下所示
-
scan
,scalLeft
,scanRight
:规约操作函数,它的作用是去一个起始值和一个规约函数,回来各个累加值的列表
与fold函数不同的是,fold函数回来的是累加值,scan回来的是累加值的列表
val lists = List(1, 2, 3, 4, 5)
println(lists.scan(0)(_ + _))
println(lists.scanLeft(0)(_ + _))
println(lists.scanRight(0)(_ + _))
履行成果如下所示
调集的形式匹配
前面咱们介绍了形式匹配,在调集中也支撑形式匹配,比方下面比方
val lists = List(1, 2, 3, 4, 5)
val msg = lists match {
case item if item contains 3 => "contains 3"
case _ => "match nothing"
}
println(msg)
可变调集
前面咱们介绍的三个常用调集Set,Map,List都是不行变的调集,在创立之后就不能改动。不过能够经过改动引证的办法,完成调集中元素的”增加”,”修正”和”删去”。本节咱们将介绍Scala中的可变调集,可变调集和不行变调集的对应联系如下
不行变调集 | 可变调集 |
---|---|
collection.immutable.List |
collection.mutable.Buffer |
collection.immutable.Set |
collection.mutable.Set |
collection.immutable.Map |
collection.mutable.Map |
下面咱们看Buffer创立和增加元素的比方
// 因为是空buffer,因而要界说一个类型
// 因为Buffer可变,因而能够界说为val
val buffer = mutable.Buffer[Int]()
// 遍历增加元素
for (elem <- 1 to 10) {
buffer+=elem
}
// 打印成果
println(buffer)
履行成果如下所示