一、scala开发环境
(一)Scala安装与验证
1、Scala下载
综合后面学习的Kafka、Spark、Flink等对Scala版本的要求,我们这里选择Scala-2.11.12版本来为各位同学进行讲述Scala。
下载地址:Scala 2.11.12 | The Scala Programming Language,内容如下图1-2-1所示。
这里我们下载图1-2-1中括起来的两个版本,一个在Window下面安装,一个在Linux环境中安装。
2、Scala安装要求
(1)JDK
如前所述,Scala的运行需要JRE,所以在安装scala之前,首先需要安装JDK,因为之前课程中已经做过说明了,这里不再赘述。
(2)目录要求
Scala不能安装在带有中文或者空格的目录下面,不然会报错,scala命令找不到。
3、Windows安装Scala
点击下载的安装包scala-2.11.12.msi进行安装,如下图1-2-2所示。
图1-2-2 接受Scala安装协议
选择安装目录,不能带中文或者空格,如图1-2-3所示
依次点击下一步,即可完成安装。
最后验证,打开cmd命令行,输入scala -version,如果出现如下图1-2-4,则证明安装成功。
4、Linux安装Scala
将安装包上传到Linux用户offcn的指定目录/export/software。
解压
[root@node01 ~]$ tar -zxvf software/scala-2.11.12.tgz -C apps/
重命名
[root@node01 ~]$ mv apps/scala-2.11.12/ apps/scala
配置环境变量
修改/etc/profile,添加如下内容,并重新生效文件
export SCALA_HOME=/home/offcn/apps/scala
export PATH=$PATH:$SCALA_HOME/bin
配置文件生效
[root@node01 ~]$ source /etc/profile
验证
命令行输入:scala -version
(二)打包我们的scala代码并准备运行
1、将我们的代码打包,之后,进行运行
双击package之后,就会出现我们打好的jar包,然后选择下面这个就可以运行了
运行我们的代码一共可以有四个命令,两种是打包的时候选择了我们的main程序类的,两种是我们打包时候没有选择main程序类的
其中第一种和第三种,都是选定了我们的main程序类
第二种和第四种都是没有选定我们的main程序类
四种运行方式都可以用于运行我们的jar包
第一种运行方式:我们打包的时候指定了对应的main方法所在的程序类
scala scala day01-1.0-SNAPSHOT-jar-with-dependencies.jar
第二种运行方式:不管打包的时候有没有指定main方法所在的程序类,都可以运行(小)
scala -cp scaladay01-1.0-SNAPSHOT-jar-with-dependencies.jar chapter.scala.demo1.ScalaFirst
第三种方式:我们打包的时候指定了对应的main方法所在的程序类
java -jar scaladay01-1.0-SNAPSHOT-jar-with-dependencies.jar
第四种方式:不管打包的时候有没有指定main方法所在的程序类,都可以运行(大)
java -cp scaladay01-1.0-SNAPSHOT-jar-with-dependencies.jar chapter.scala.demo1.ScalaFirst
我们可以看到我们的scala代码最终也编译成了class文件
2、高级开发工具——IDEA
(1)添加idea的scala插件
要想在idea中开发scala项目,首先就需要添加idea的scala插件
a、在线安装
进入idea settings配置界面,打开plugin选项,如下图1-2-10所示:
点击安装,安装成功之后,重启idea,如图1-2-11所示
在线安装scala插件成功。
b、离线安装
插件下载地址:
Scala - IntelliJ IDEs Plugin | Marketplace
下载和当前idea版本相匹配的插件,这里的idea版本为2019.3.5,选择的插件如下图1-2-12所示:
进入idea settings配置界面,打开plugin选项,选择从磁盘加载插件,如下图1-2-13所示:
从磁盘具体位置选择scala插件,点击OK,然后重启idea
插件安装成功!
(2)创建普通scala项目
点击 Create New Project,进入选择界面,如下图1-2-16所示:
参考
Intellij IDEA在线和离线安装scala插件 - 整合侠 - 博客园
选择scala-->idea-->点击next,如下图1-2-17所示:
配置项目基本信息,如下图1-2-18所示,点击Finish,完成项目创建
创建好项目之后,右键src->New->Scala Class,创建scala的class
配置包名和className,完成scala class的创建,如下图1-2-20所示:
完成HelloWorld入门案例编写和运行,如下图1-2-21所示
(2)创建基于maven的scala项目
点击File->new->Project,创建新的项目,如下图1-2-23所示
选择maven,点击next,如图1-2-24所示
配置maven坐标信息,点击Finish完成创建,如下图1-2-25所示
添加maven坐标,如下图1-2-26所示:
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.11.12</version>
</dependency>
创建scala source目录,并将其添加为Sources Root
此时,是无法创建scala的class的,需要将scala的sdk重新添加到idea项目才可以。
点开File->Project Structrue->Global Libraries,删除sdk,重新添加
此时,便可以进行创建Scala class了,具体操作和创建普通Scala项目操作时一样。
特此说明:今后,在本讲义中涉及到创建的项目都是基于这种maven方式创建的。
(三)Scala编码规范
命名规范基本和java保持一致。
1、文件名
- 首字母大写
- 多个单词之间使用驼峰命名
- 以名词居多
- 尽量不要使用名词的复数形式
2、方法函数命名
- 首字母小写
- 多个单词之间使用驼峰命名
- 以动词+名词
3、文件编码格式
- 文件编码统一为UTF-8
- {}的位置写法和java保持一致
- 每行缩进使用空格,使用4个空格来代替缩进tab,如图1-2-29所示
三、Scala基础语法知识
(一)、Scala语言的特点
- 可拓展
- 面向对象
- 函数式编程
- 兼容Java
- 类库调用
- 互操作
- 语法简洁
- 代码行短
- 类型推断
- 抽象控制
- 静态类型化
- 可检验
- 安全重构
- 支持并发控制
- 强计算能力
- 自定义其他控制结构
-
(二)Scala与Java的关系
1、都是基于JVM虚拟机运行的
Scala编译之后的文件也是.class,都要转换为字节码,然后运行在JVM虚拟机之上。
2、Scala和Java相互调用
在Scala中可以直接调用Java的代码,同时在Java中也可以直接调用Scala的代码
3、Java 8 VS Scala
1)Java 8(lambda)没有出来之前,Java只是面向对象的一门语言,但是Java 8出来以后,Java就是一个面向对象和面向函数的混合语言了。
2)首先我们要对Scala进行精确定位,从某种程度上讲,Scala并不是一个纯粹的面向函数的编程语言,有人认为 Scala是一个带有闭包的静态面向对象语言),更准确地说,Scala是面向函数与面向对象的混合。
3)Scala设计的初衷是面向函数FP,而Java起家是面向对象OO,现在两者都是OO和FP的混合语言,是否可以这么认为:Scala = FP + OO,而Java = OO+ FP?
由于面向对象OO和面向函数FP两种范式是类似横坐标和纵坐标的两者不同坐标方向的思考方式,类似数据库和对象之间的不匹配阻抗关系,两者如果结合得不好恐怕就不会产生1+1>2的效果。
面向对象是最接近人类思维的方式,而面向函数是最接近计算机的思维方式。如果你想让计算机为人的业务建模服务,那么以OO为主;如果你希望让计算机能自己通过算法从大数据中自动建模,那么以FP为主。所以,Java可能还会在企业工程类软件中占主要市场,而Scala则会在科学计算大数据分析等领域抢占Java市场,比如Scala的Spark大有替代Java的Hadoop之趋势。
(三)Scala解释器
Scala解释器读到一个表达式,对它进行求值,将它打印出来,接着再继续读下一个表达式。这个过程被称做读取read--求值eval--打印print--循环loop,即:REPL。
从技术上讲,Scala程序并不是一个解释器。实际发生的是,你输入的内容被快速地编译成字节码,然后这段字节码交由Java虚拟机执行。正因为如此,大多数Scala程序员更倾向于将它称做“REPL”。过程如下图1-3-1所示
(四)Scala变量的定义
Scala的变量定义分为两种方式,分别需要使用关键字val和var。
- val
val a = 3
- var
var a = 3
val和var都可以用来定义变量,唯一的区别就在于,val定义的变量是不可变,而var定义的变量是可变的。不可变的量确实类似常量(被final修饰的变量),但不叫常量,只能叫不可变量的量。
在今后的开发过程中,能使用val就使用val,var尽量不用,因为scala编程的特点,是在创建变量的时候可以省略数据类型,val有助于进行类型的推断。var在操作过程中不利于变量的类型。
操作如下图1-3-2所示
一个完整的变量的定义如下:
Scala声明变量有两种方式,一个用val,一个用var。
val / var 变量名 : 变量类型 = 变量值。
val a:Int = 3
其中val为变量修饰符,a为变量名,Int为a的数据类型,其中需要将变量名和数据类型使用:连接,=右侧为变量的值。
(五)Scala的数据类型
Scala数据类型如下表1-3-1所示。
说明:Scala拥有和Java一样的数据类型,和Java的数据类型的内存布局完全一致,精度也完全一致。在scala中,Any类是所有类的超类,Any有两个子类:AnyVal和AnyRef。如Int、Double等,AnyVal是它们的基类;对应引用类型,AnyRef是它们的基类。
(六)、Scala操作符说明
Scala的操作符和java一模一样,故省略。唯独需要说明的是:
在Scala调用一些方法或者函数的时候,如果方法或者函数是空参的,可以省略掉()。
在Scala中一行表示的结尾不像Java需要使用";",Scala可以省略。
四、Scala流程控制
(一)、分支语句
scala中的if表达式,基本和java中的if表达式一模一样,唯一的区别就是scala中的if、if-else、if-elseif-else有返回值!
scala> val age = 18 age: Int = 18 scala> if(age > 16) "adult" else "child" res17: String = adult scala> val ret = if(age > 16) "adult" else "child" ret: String = adult scala> println("ret = " + ret) ret = adult scala> var result = "" result: String = "" scala> if(age > 16) result = "adult" else result = "child" scala> println("result= " + result) result= adult scala> if(age > 16) "adult" else 123 res21: Any = adult |
记住:scala中任意的表达式,都有返回值。
如果else丢失了,即:
if(x>0) 1
那么有可能if语句没有输出值,但是在Scala中,每个表达式都有值,这个问题的解决方案是引入一个Unit类,写作(),叫做无用占位符,不带else语句的if语句等同于if(x>0) 1 else ()。
scala的返回值可以省略return关键字,表达式的最后一句话,作为表达式的返回值返回。return关键字通常使用在函数中进行逻辑的终止,比如循环。
(二)块表达式
package cn.bigdata.scala
object BlockExpressionDemo {
def main(args: Array[String]) {
val x = 0
//在scala中{}中包含一系列表达式,块中最后一个表达式的值就是块的值
//下面就是一个块表达式
val result = {
if (x < 0){
-1
} else if(x >= 1) {
1
} else {
"error"
}
}
//result的值就是块表达式的结果
println(result)
val res01 = {
1+2
4+5
4>5
}
println(res01)
}
}
注意:前面提到过,scala中的语句终止就是换行,也就是一行一个语句,此时便可以省略";",但是当一行有多条语句的时候,就应该使用";"分隔。在表达式块中同样可以使用换行作为一条语句的分隔。
如果if/循环{}中只有一条语句,是可以省略{},但是如果大家查看过阿里巴巴编程规范中话,其建议哪怕只有一条语句也写上{},会显得结构很清晰。
(二)、循环
1、while和do while循环
(1) while循环
/** * while循环操作: 1+...+10 */ var n = 1; val while1 = while(n <= 10){ n += 1 } println(while1) println(n) //++/--自增/自减运算符在scala中不支持,因为已经被scala集合的对应函数所占据了 |
注意:error: value ++/-- is not a member of Int
scala中不能直接对一个变量执行自增/减,++/--是scala集合的一个函数。
(2) do While循环
/** * do while循环操作: 1+...+10 */ object _02DoWhileLoopDemo { def main(args: Array[String]): Unit = { var sum = 0 var n = 1 do { sum += n n += 1 } while (n <= 10) println("sum = " + sum) } } |
(3) 循环练习
登录用户名密码的游戏:三次机会,从控制台输入用户名密码,如果成功登录,返回登录成功,失败,则反馈错误信息!
/* 从控制台输入: 用老版本的话直接使用: readLine readInt readChar readXxx 在2.11之后,scala将这些api进行封装, 放入到一个工具类:StdIn scala.io.StdIn */ import scala.io.StdIn object _03WhileTest { def main(args: Array[String]): Unit = { val dbName = "fanshuai" val dbPwd = "sorry" var count = 3 do { val name = StdIn.readLine("请输入用户名:") val pwd = StdIn.readLine("请输入密码:") if(name == dbName && pwd == dbPwd) { println("welcome to our classroom, " + name + " !") count = -1 } else { count -= 1 println("天王盖地虎,密码请重试,您还有" + count + "次机会~") } }while(count > 0) } } |
3、for循环
(1)for循环
在java中有2种写法,普通的for循环for(变量;条件;自增或自减){循环体} or for(类型 变量 : 集合) {}。
scala中的for循环该如何定义呢?
scala中并没有像java中普通for循环的语法结构,更像是高级for循环。
for (变量 <- 集合) {
//循环体
}
将上述的while循环使用for循环改写
object _05ForLoopDemo { def main(args: Array[String]): Unit = { /* 将前面的while循环案例适用for循环实现 scala中生成一个集合可以使用整型变量 to/until 数字 会生成一个返回 to: 1 to 5/1.to(5) 包含 scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5) until: 1 until 5/1.until(5) 不包含 scala.collection.immutable.Range = Range(1, 2, 3, 4) */ val range = 1.to(10) var sum = 0 for(n <- range) { sum += n } println("sum = " + sum) } }
|
说明:添加reverse主要就是向进行循环的反转
val range = 1.to(10) var sum = 0 for(n <- range reverse) { println("n=" + n) } |
/* 循环的嵌套 打印矩阵 ***** ***** ***** 3行 * 5列 循环的嵌套中:外循环控制行,内循环控制每一行对应的列 */ object _06LoopNestDemo { def main(args: Array[String]): Unit = { for(i <- 1 to 3) { for(j <- 1 to 5) { print("*") } println } println("----------打印一个三角形-------------") for(i <- 1 to 5) { for(j <- 1 to 5) { if(j <= i) print("*") } println } println("----------改写打印一个三角形-------------") for(i <- 1 to 5) { for(j <- 1 to i) { print("*") } println } println("----------改写打印一个三角形-------------") for(i <- 1 to 5) { for(j <- i to 5) { print("*") } println } |
循环嵌套
println("----------scala for嵌套写法-------------") for(i <- 1 to 5; j <- 1 to i) { print("*") if(i == j) { println } } println("----------scala for嵌套写法-------------") for(i <- 1 to 5; j <- 1 to 5; if j <= i) { print("*") if(i == j) { println } } } } |
练习题:9*9乘法口诀表。
object _07LoopTest { def main(args: Array[String]): Unit = { for(i <- 1 to 9; j <- 1 to i) { // print(j + "*" + i + "=" + (i * j) + "\t") print(s"${j}*${i}=${i * j}\t") if(i == j) { println } } } } |
(3)循环的终止
在java中终止循环有关键字,continue,break,return,但是在scala中没有前两者continue和break。该如何终止循环呢?
有三种方式来结束循环:
1. 使用return语句来控制循环结束
2. 使用循环结束的条件来进行控制,比如上例中的count >= 0
3. 还可以使用breakable函数体来进行控制
//需要导入包
import scala.util.control.Breaks._
//continue例子
for (i <- 1 to 10) {
breakable{
if (i == 2) break
println(i)
}
}
//break例子
breakable{
for(i<-1 to 10){
if(i==5){
break
}
println(i)
}
}
//retrun
for(i <- 1 to 10){
if(i==2) return
println(i)
}
(三)、异常控制和Lazy懒加载
当val被声明为lazy时,他的初始化将被推迟,直到我们首次对此取值,适用于初始化开销较大的场景。
1) lazy示例:通过lazy关键字的使用与否,来观察执行过程
object LazyDemo {
def init(): String = {
println("init方法执行")
"嘿嘿嘿,我来了~"
}
def main(args: Array[String]): Unit = {
lazy val msg = init()
println("lazy方法没有执行")
println(msg)
}
}
说明:上面的案例中,更进一步的学习了一个关键字lazy(懒加载),被lazy所修饰的变量,只有当第一次被使用的时候才会进行初始化,没有使用之前只是记录了存在,检查语法结构是否正确。可以节省一定的资源空间。
在Java中学习了两种异常的处理方式:throws和try-catch。throws和java中的一模一样,将异常转交给调用者进行处理。try-catch则意味着自己要进行处理,但是编写的方式和Java不一样。
object ExceptionSyllabus { |
五、Scala方法定义
(一)、方法的定义
1、方法定义
和Java中对方法的定义一样,Scala中的方法就是对某一特定功能的封装,当然Scala中除了支持方法以外,还支持函数,这是Java(jdk1.8以前)中所不具备的。
方法对对象进行操作,函数不是。如果二者不做严格的区分的话,二者的定义大同小异,要定义方法或者函数,你需要给出方法或者函数的名称、参数和方法或者函数体,就像图1-5-1:
- 你必须给出所有参数的类型。不过,只要方法不是递归的,你就不需要指定返回类型。Scala编译器可以通过=符号右侧的表达式的类型推断出返回类型。
- “=”并不只是用来分割方法签名和方法体的,它的另一个作用是告诉编译器是否对方法的返回值进行类型推断!如果省去=,则认为方法是没有返回值的!
- 在Scala中一个方法或者函数的最后一句话就是该方法或者函数的返回值,不需要使用return,当然加上也行。
2、方法定义练习
object _09MethodOps { def main(args: Array[String]): Unit = { // show() val ret = sayHi("tom") println(ret) } def show() = { println("abcde") } def sayHi(name:String) = { "Nice to meet you, " + name } } |
说明:方法或者函数可以不使用return语句,但是如果加上return,那返回值类型,必须要加上,否则如图1-5-2所示:
(二)、特殊方法
1、单行方法
所谓单行方法,指的是函数体只有一行的方法。
def sayHello(name:String) = println("Nice to meet you, " + name) |
注意:单行方法,必须要使用"="来做函数的返回值类型推断。
2、无参方法
无参方法,即参数列表为空。
def show() = { println("abcde") } |
注意:定义空参方法或者函数的时候,如果方法或者函数加了(),在调用的时候,()可以省略;但是如果在定义的时候没有加(),在调用的时候,也只能省略,不能加()。
3、递归方法
在特定条件下,调用本身。
/**
* 阶乘求解:5!
* n!= n * (n-1)*..*2*1
* n!= n * (n-1)!
* 1! = 1
*/
def factorial(n:Int):Int = {
if(n == 1)
1
else
n * factorial(n - 1)
}
(三)、方法或函数参数的特点
1、默认参数和带名参数
默认参数,指的是一个参数可以有默认值;带名参数指的是传参的时候可以指定参数名称。
def main(args: Array[String]): Unit = { showAddr("张三", 1688888168, "广西", "CRP")//会覆盖默认值 showAddr("李四", 1381638338, "广东")//使用默认值 //传递参数的时候,可以指定具体的参数名称 showAddr(phone = 1395959599L, name = "zhangsan", province = "福建") } def showAddr(name:String, phone:Long, province:String, country:String = "CHINA"): Unit = { println(s"name=$name, phone=$phone, province=$province, country=$country") } |
Scala的方法或函数参数列表,在定义的时候,可以使用有默认值的参数,在做方法或函数调用的时候可以使用带名的参数,所以有时我们是无法通过方法或函数传递的参数来推断出具体的参数列表顺序。
2、可变参数
所谓可变参数,指得就是,参数个数可以动态变化。
//scala中的可变参数 object _11FunctionDemo { def main(args: Array[String]): Unit = { println(add(1, 2, 3)) println(add(1, 2, 3, 5)) //声明scala中的一个数组 val a = Array(3, 4, 5) println(add(a: _*)) } def add(arr: Int*) = { var sum = 0 for(a <- arr) { sum += a } sum } } |
Scala版本的可变参数
说明:
- scala中的可变参数和java有一点不同,上例中第三个函数和第四个函数,在java中实际上是同一个函数,而在scala中是两个不同的函数。
- 在对函数add(arr:Int*)就不能向java一样,直接传递一个数组,否则报错,类型不匹配。要想给一个可变参数传递数组,就需要将数组中的元素提取出来,再传递,操作方式如下:
val arr = Array(3, 4, 5) ret = add(arr: _*) |
六、Scala函数定义
//要求有返回值
方法:形参 返回值
函数:入参 出参
在函数式编程语言中,函数是“头等公民”,它可以像任何其他数据类型一样被传递和操作
案例:首先定义一个方法,再定义一个函数,然后将函数传递到方法里面
(4)函数也是对象
//第一个Int是入参类型,第一个String是入参类型,第二个String是返回值类型
def add3 = new Function2[Int,String,String] {
def apply(x:Int,y:String):String = x+y;
}
val f5 = new Function0[Int] {
def apply(): Int ={
12
}
}