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中,所有值都有类型,包括数值类型和函数类型。下面图片展示了类型层次结构的子集。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xp3ULPTF-1685580476558)(null)]

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,编译时会报错

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dbcYef56-1685580476196)(null)]

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( <- if )

我们写一个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 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 list1 = Arrays.asList(1, 2, 3, 4);

List 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.Listcollection.mutable.Buffercollection.immutable.Setcollection.mutable.Setcollection.immutable.Mapcollection.mutable.Map

下面我们看Buffer创建和添加元素的例子

// 由于是空buffer,因此要定义一个类型

// 因为Buffer可变,因此可以定义为val

val buffer = mutable.Buffer[Int]()

// 遍历添加元素

for (elem <- 1 to 10) {

buffer+=elem

}

// 打印结果

println(buffer)

执行结果如下所示

参考文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: