Scala基础语法
1. Scala概述
1.1. 什么是Scala
Scala 是一种多范式的编程语言,其设计的初衷是要集成面向对象编程和函数式编程的各种特性。Scala 运行于 Java 平台(Java虚拟机),并兼容现有的 Java 程序。 Scala(Scalable Language的简称)语言是一种能够运行于 JVM 和 .Net 平台之上的通用编程语言,既可 用于大规模应用程序开发,也可用于脚本编程,它由由 Martin Odersky 于 2001 开发,2004年开始程 序运行在 JVM 与 .Net 平台之上,由于其简洁、优雅、类型安全的编程模式而受到关注。
Scala官网
注意三者的区别:
面向对象编程
面向过程编程
函数式编程
编程语言之分:
1、汇编语言,脚本语言,机器语言,高级语言
2、静态编程语言和动态编程语言
3、编译型和解释型
4、面向对象和函数式编程
5、强类型语言和弱类型语言
举例:
Python是动态类型语言,是强类型语言,解释型编程语言。
JavaScript是动态类型语言,是弱类型语言,解释型编程语言。
Java是静态类型语言,是强类型语言。
Go是编译型语言,是静态编程语言。
1.2. 为什么要学Scala
第一,基于编程语言自身:
优雅
这是框架设计师第一个要考虑的问题,框架的用户是应用开发程序员,API是否优雅直接影响用户体验。
速度快
Scala语言表达能力强,一行代码抵得上Java多行,开发速度快;Scala是静态编译的,所以和JRuby, Groovy比起来速度会快很多。
能融合到Hadoop生态圈:
Hadoop现在是大数据事实标准,Spark的出现并不是要取代Hadoop,而是要完善Hadoop生态。JVM语言大 部分可能会想到Java,但Java做出来的API太丑,或者想实现一个优雅的API太费劲。
Scala 和 Java 的一个对比:
面向对象编程 面向过程编程 函数式编程
第二,基于活跃度:
- (1)、作为流行的开源大数据内存计算引擎Spark的源码编程语言--Spark有着良好的性能优势
- (2)、Scala将成为未来大数据处理的主流语言
"If I were to pick a language to use today other than Java, it would be Scala." -- James Gosling
-
(3)、最新TIOBE编程语言排行榜Scala曾经进入过前20,现阶段是第28位
2. Scala编译器安装
2.1. 安装JDK
因为Scala是运行在JVM平台上的,所以安装Scala之前要安装JDK
安装JDK
mkdir /root/soft/
mkdir /usr/local/java/
tar -zxvf /root/soft/jdk-8u73-linux-x64.tar.gz -C /usr/local/java/
#配置环境变量
vi /etc/profile
##最后添加内容
export JAVA_HOME=/usr/local/java/jdk1.8.0_73
export PATH=$PATH:$JAVA_HOME/bin
export CLASSPATH=.:/usr/local/java/jdk1.8.0_73/lib/dt.jar:/usr/local/java/jdk1.8.0_73/lib/tools.jar
##重新加载
source /etc/profile
##查看版本
java -version
2.2. Scala的SDK下载
2.3. Windows平台安装Scala
访问Scala官网下载Scala编译器安装包,目前最新版本是2.13.2,但是目 前大多数的框架都是用 2.10.x 或者 2.11.x 或者 2.12.x 编写开发的,如果将来我们要基于 Spark-2.4.5 进行学习,所以这里推荐2.12.x 版本,下载 scala-2.12.11.msi 后点击下一步就可以了,或者你也可以 基于现阶段业界主流使用的版本:scala-2.11.8
可以不用手动配置环境变量:因为会自动配置好
Java 全手动配置环境变量
Python 在安装引导界面可以选择 add path 配置环境变量
Scala 完全不用理会,会自动配置好环境变量
2.4. Linux平台安装Scala
访问官网的下载Scala的地址
https://www.scala-lang.org/download/all.html
https://www.scala-lang.org/download/2.12.11.html
https://downloads.lightbend.com/scala/2.12.11/scala-2.12.11.tgz
下载想要的版本,我们这里下载的是:
scala-2.12.11.tgz
第一步:上传到服务器
put c:/scala-2.12.11.tgz
第二步:解压缩安装到对应目录
[root@bigdata01 ~]# mkdir /usr/local/scala
[root@bigdata01 ~]# tar -zxvf ~/soft/scala-2.12.11.tgz -C /usr/local/scala/
第三步:配置环境变量
vi /etc/profile
添加如下内容:
export SCALA_HOME=/usr/local/scala/scala-2.12.11
export PATH=$PATH:$SCALA_HOME/bin
执行命令使之生效
source /etc/profile
第四步:检测安装是否成功
[root@bigdata01 ~]# scala -version
Scala code runner version 2.12.11 -- Copyright 2002-2020, LAMP/EPFL and Lightbend, Inc.
2.5. Scala集成开发环境Intellgence IDEA安装
目前 Scala 的开发工具主要有两种:Eclipse 和 IDEA,这两个开发工具都有相应的 Scala 插件,如果使 用Eclipse,直接到 Scala 官网下载即可
由于 IDEA 的 Scala 插件比 Eclipse 的更优秀,所以大多数 Scala 程序员都愿意选择 IDEA,可以到社区下载免费版,点击下一步安装即可,安装时如果有网络 可以选择在线安装 Scala插件。这里我们使用离线安装 Scala 插件:
- (1)、安装 IDEA,点击下一步即可。由于我们离线安装插件,所以点击
Skip All and Set Defaul
- (2)、下载 IEDA 的 scala 插件,地址 选择 scala 下载对应 IDEA 版本的 scala插件:
scala-intellij-bin-2019.2.2.zip
- (3)、安装插件:具体步骤如下,在启动 IDEA 以后,以此执行以下步骤
Configure
-->Plugins
-->Install plugin from disk
-->选择Scala插件
-->OK
-->重启IDEA
添加Scala lib
3. Scala基础语法
3.1. Hello Scala
object HelloWordScala {
/**
* def 用来定义方法的关键字
* main 方法名
* args: 形参名称
* :Unit 方法的返回值
*
* def max(a:Int,b :Int):Int = {}
*/
def main(args: Array[String]): Unit = {
println("Hello Word Scala")
}
}
关于HelloWorld程序:Scala 和 Java 的对比不同处:
1、类名和文件名
2、main方法的定义(注意和java的main方法对比理解)
3、分号(可有可无)
4、编译和执行(scalac和scala)
5、类的声明(object和class)
3.2. 变量的定义
object VariableDemo {
def main(args: Array[String]) {
//使用val定义的变量值是不可变的,相当于java里用final修饰的变量
val i = 1
//使用var定义的变量是可变得,在Scala中鼓励使用val
val s = "hello"
//Scala编译器会自动推断变量的类型,必要的时候可以指定类型
//变量名在前,类型在后
val str: String = "你好!世界!"
println(i, s, str)
/**
* val 相当于Java中的final关键字修饰的变量,一旦赋值便不能更改
* 当未指定类型时,Scala会根据实际类型进行类型推断,上面前三种方式结果相同
* lazy关键字修饰的变量,定义时不赋值,真正使用时才赋值
* var关键字修饰的变量,可以被重新赋值
* e也可以是大写E,0.1214e1 = 0.1314*10
* e也可以是大写E,0.1214e2 = 0.1314*10*10
*/
// 定义变量
val hello1 = "Hello Scala"
val hello2: String = "Hello Scala"
val hello3: java.lang.String = "Hello Scala"
lazy val hello4 = "Hello Scala"
// 使用val修饰的变量是不可更改变量。相当于java中的final关键字修饰的变量
// 这个赋值操作是不被允许的: hello5 = "hello spark"
val hello5 = "Hello Scala"
// 使用var修饰的变量可以修改为同类型的其他值, 但是使用val修饰的变量一定不能修改
var hello6 = "hello"
// 这个赋值操作是错误的: hello6 = 4
hello6 = "hi" // 这个操作是允许的
val x1 = 0x29 //十六进制定义整数
val x2 = 41 //十进制定义整数
val x3 = 51 //八进制数定义
println(x1, x2)
val doubleNumber = 3.14159 //Double类型定义,直接输入浮点数,编译器会将其自动推断为Double类型
val floatNumber1 = 3.14159F //定义Float类型浮点数,需要在浮点数后面加F或f
val floatNumber2 = 0.1314e-1 // 科学计数法
println(doubleNumber, floatNumber1, floatNumber2)
// 字符定义,用单引号(')将字符包裹
var letter = 'A'
// 如果需要原样输出字符串中的内容,则用三个双引号"""将字符串包裹起来
val hello = """Hello \n \t \b \\ Scala"""
// 定义布尔变量
var bool = true
println(letter, hello, bool)
}
}
总结:
1、数据类型可以指定,也可以不指定,如果不指定,那么就会进行数据类型的自动推断。
2、如果指定数据类型,数据类型的执行方式是在变量名后面写一个冒号,然后写上数据类型。
3、我们的scala里面变量的修饰符一共有两个,一个是var,一个是val。如果是var修饰的变量,那么这个 变量的值是可以修改的,虽然可以修改,但是不能改成其他类型的值。如果是val修饰的变量,那么这个变量 的值是不可以修改的
懒加载、延迟加载(lazy),两个例子:
scala> import scala.io.Source
import scala.io.Source
scala> lazy val file = Source.fromFile("/home/bigdata/student.txt")
file: scala.io.BufferedSource = <lazy>
scala> file
res2: scala.io.BufferedSource = non-empty iterator
scala> for (f <- file.getLines) println(f)
95002,刘晨,女,19,IS
95017,王风娟,女,18,IS
95018,王一,女,19,IS
95013,冯伟,男,21,CS
95014,王小丽,女,19,CS
95019,邢小丽,女,19,IS
95020,赵钱,男,21,IS
95003,王敏,女,22,MA
95004,张立,男,19,IS
95012,孙花,女,20,CS
95010,孔小涛,男,19,CS
95005,刘刚,男,18,MA
95006,孙庆,男,23,CS
95007,易思玲,女,19,MA
95008,李娜,女,18,CS
95021,周二,男,17,MA
95022,郑明,男,20,MA
95001,李勇,男,20,CS
95011,包小柏,男,18,MA
95009,梦圆圆,女,18,MA
95015,王君,男,18,MA
4. 数据类型
4.1. 数据类型概述
Scala 和 Java 一样,有7种数值类型 Byte、Char、Short、Int、Long、Float 和 Double(无包装类 型)和一个Boolean 类型,再加上常用的String类型。
注意:scala里面没有基本数据类型和包装类型之说。如果大家非要说的话,那么大家都可以认为都有 的类型的类型都是包装类型。
一些代码测试:
/**
* 描述: 数据类型
*
* 要点:
* 1、Any是所有类的父类,包括值类型AnyVal,和引用类型AnyRef
* 2、AnyVal是所有值类型的父类,包括Int,Double,Boolean,Unit等等
* 3、AnyRef是所有引用类型的父类,包括Null
* 4、Null是所有引用类型的子类
* 5、Nothing是所有类的子类
* 6、Unit类型只有一个实例,是(),相当于java中的void,没有任何的实质意义
* 7、Null也只有一个实例,是null,相当于java中的null,能赋值给任何引用类型变量,不能赋值给值类型变量
*/
object TypeDemo {
def main(args: Array[String]): Unit = {
val var_int = 1
val var_double = 3.33
val var_float = 2.4F
val var_char = 'A'
val var_bool = true
println(var_int, var_double, var_float, var_char, var_bool)
val var_16 = 0x29
val var_string = "aa"
val var_string1 = "\"huangbo\""
val var_string2 = """hello\thello\na\t\\"""
println(var_16)
println(var_string)
println(var_string1)
println(var_string2)
}
}
Scala的数据类型的结构图和关系:
总结:
1、Any是所有类的父类,包括值类型AnyVal,和引用类型AnyRef
2、AnyVal是所有值类型的父类,包括Int,Double,Boolean,Unit等等
3、AnyRef是所有引用类型的父类,包括Null
4、Null是所有引用类型的子类
5、Nothing是所有类的子类
6、Unit类型只有一个实例,是(),相当于java中的void,没有任何的实质意义
7、Null也只有一个实例,是null,相当于java中的null,能赋值给任何引用类型变量,不能赋值给值类型 变量
4.2. Scala基本类型操作
算术操作
object Test {
def main(args: Array[String]): Unit ={
var a = 10;
var b = 20;
var c = 25;
var d = 25;
println("a + b = " + (a + b) );
println("a - b = " + (a - b) );
println("a * b = " + (a * b) );
println("b / a = " + (b / a) );
println("b % a = " + (b % a) );
println("c % a = " + (c % a) );
}
}
关系运算
object Test {
def main(args: Array[String]): Unit ={
var a = 10;
var b = 20;
println("a == b = " + (a == b) );
println("a != b = " + (a != b) );
println("a > b = " + (a > b) );
println("a < b = " + (a < b) );
println("b >= a = " + (b >= a) );
println("b <= a = " + (b <= a) );
}
}
逻辑运算
object Test {
def main(args: Array[String]): Unit = {
var a = true;
var b = false;
println("a && b = " + (a&&b) );
println("a || b = " + (a||b) );
println("!(a && b) = " + !(a && b) );
}
}
位运算
object Test {
def main(args: Array[String]): Unit = {
var a = 60; /* 60 = 0011 1100 */
var b = 13; /* 13 = 0000 1101 */
var c = 0;
c = a & b; /* 12 = 0000 1100 */
println("a & b = " + c );
c = a | b; /* 61 = 0011 1101 */
println("a | b = " + c );
c = a ^ b; /* 49 = 0011 0001 */
println("a ^ b = " + c );
c = ~a; /* -61 = 1100 0011 */
println("~a = " + c );
c = a << 2; /* 240 = 1111 0000 */
println("a << 2 = " + c );
c = a >> 2; /* 15 = 1111 */
println("a >> 2 = " + c );
c = a >>> 2; /* 15 = 0000 1111 */
println("a >>> 2 = " + c );
}
}
赋值运算符
object Test {
def main(args: Array[String]): Unit = {
var a = 10;
var b = 20;
var c = 0;
c = a + b;
println("c = a + b = " + c );
c += a ;
println("c += a = " + c );
c -= a ;
println("c -= a = " + c );
c *= a ;
println("c *= a = " + c );
a = 10;
c = 15;
c /= a ;
println("c /= a = " + c );
a = 10;
c = 15;
c %= a ;
println("c %= a = " + c );
c <<= 2 ;
println("c <<= 2 = " + c );
c >>= 2 ;
println("c >>= 2 = " + c );
c >>= a ;
println("c >>= a = " + c );
c &= a ;
println("c &= 2 = " + c );
c ^= a ;
println("c ^= a = " + c );
c |= a ;
println("c |= a = " + c );
}
}
4.3. 编码规范
1、分号:在scala编码中,不强制在代码末尾加分号,但是如果有多句代码写在同一行,那么必须使用分号 进行隔开
2、注释:在scala编程中,注释的方式和Java中注释方式一样,原则:少而精
object HelloWorld {
/* 这是一个 Scala 程序
* 这是一行注释
* 这里演示了多行注释
*/
def main(args: Array[String]): Unit = {
// 输出 Hello World
// 这是一个单行注释
println("Hello, world!")
}
}
3、变量命名:和Java的编码规范一致,采用驼峰法则
4、关键字:关注新关键字:yield, match, object, def, implicit, trait, sealed, var/val
5. 流程控制
5.1. if
Scala的的条件表达式比较简洁,例如:
object IfDemo {
def main(args: Array[String]) {
// if条件分支是可以拥有返回值的。!!返回值为代码段中的最后一个局代码的返回结果
var aa = 1
var result = if (aa > 0) {
println(">")
} else {
println("<")
}
println(result)
val x = 1
//判断x的值,将结果赋给y
val y = if (x > 0) 1 else -1
//打印y的值
println(y)
//支持混合类型表达式
val z = if (x > 1) 1 else "error"
//打印z的值
println(z)
//如果缺失else,相当于if (x > 2) 1 else ()
val m = if (x > 2) 1
println(m)
//在scala中每个表达式都有值,scala中有个Unit类,写做(),相当于Java中的void
val n = if (x > 2) 1 else ()
println(n)
//if和else if
val k = if (x < 0) -1 else if (x > 0) 1 else 0
println(k)
}
}
总结:
1)if条件表达式它是有返回值的,返回值是多个分支的返回结果的共同父类
2)返回值会根据条件表达式的情况会进行自动的数据类型的推断(返回的是多个分支的共同父类)
5.2. 块表达式
object BlockExpresstionDemo {
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 aa = {
1 to 10; if (3 > 2) 3 else 2
}
println(aa)
val bb = {
1 to 10; if (3 > 2) 3 else 2; val cc = 2
}
println(bb)
}
}
总结:
就算是赋值表达式,也是有返回值的。是空,是Unit
scala> val a = {val bb = 2}
a: Unit = ()
scala> val a = {val bb = 2; 2}
a: Int = 2
5.3. for
for的语法结构:
for循环语法结构:for (i <- 表达式/数组/集合)
代码:
object ForDemo {
def main(args: Array[String]) {
//for(i <- 表达式),表达式1 to 10返回一个Range(区间)
//每次循环将区间中的一个值赋给i
for (i <- 1 to 10)
println(i)
//for(i <- 数组)
val arr = Array("a", "b", "c")
for (i <- arr)
println(i)
// 倒序打印
for (str <- arr.reverse) {
println(str)
}
// 使用数组下标的方式进行打印
for (i <- 0 to arr.length - 1) {
println(arr(i))
}
for (i <- 0 until arr.length)
println(arr(i))
println("-----------------")
for (i <- 0 until(arr.length, 2))
println(arr(i))
//高级for循环
//每个生成器都可以带一个条件,注意:if前面没有分号
//守卫条件:if i != j
for (i <- 1 to 3; j <- 1 to 3 if i != j)
println((10 * i + j) + " ")
println()
//for推导式:如果for循环的循环体以yield开始,则该循环会构建出一个集合
//每次迭代生成集合中的一个值
val v = for (i <- 1 to 10) yield i * 10
println(v)
}
}
总结:
1、在scala里面没有运算符,都有的符号其实都是方法。
1 to 10 1.to(10) a.f(b)
2、在scala里面没有 ++ -- 的用法
3、for(i <- 表达式/数组/集合)
4、在for循环里面我们是可以添加if表达式 (守卫条件)
5、有两个特殊表达式需要了解:
To 1 to 3 1 2 3
To 1 to (3,2) 1 3
start to (end, step)
Until 1 until 3 1 2
6、如果在使用for循环的时候,for循环的时候我们需要获取,我们可以是使用yield关键字
5.4. while
代码:
object WhileDemo {
def main(args: Array[String]) {
// Scala中的while的语法和java中的一样
//定义起始条件
var n = 10;
// ( ) 中是终止条件
while (n > 0) {
println(n)
//迭代条件
n -= 1
}
}
}
总结:
1、while使用跟java一模一样
2、注意点:在scala里面不支持 i++ i-- 等操作统一写成 i+=1 i-=1
5.5. break和continue
Scala 里面竟然没有 break 和 continue 关键字,其实不是这样的,Scala 里面推荐使用函数式的风格解 决 break 和 continue 的功能,而不是一个关键字。
break:跳出整个循环, continue:跳出当前循环
import util.control.Breaks._
object Break_ContinueDemo {
def main(args: Array[String]): Unit = {
// break 举例
breakable(
for (i <- 1 to 5) {
if (i == 3) {
break()
}
println(i)
}
)
println("----------------")
// continue 举例
for (i <- 1 to 5) {
breakable {
if (i == 3) {
break
}
println(i)
}
}
}
}
6. 方法和函数
Scala 中的 + - * / % 等操作符的作用与 Java 一样,位操作符 & | ^ >> << 也一样。
只是有一点特别的:这些操作符实际上是方法。例如:
a + b 是如下方法调用的简写:
a.+(b)
"a 方法 b" 可以写成 "a.方法(b)"
6.1. 定义方法
方法的返回值类型可以不写,编译器可以自动推断出来,但是对于递归函数,必须指定返回类型 注意:函数体应该改成叫方法体!如果不写等号,代表没有返回值。
scala> def m2(x:Int, y:Int):Int = x + y m2: (x: Int, y: Int)Int
scala> def m2(x:Int, y:Int){println("aa"); var aa=2; aa} m2: (x: Int, y: Int)Unit
object MethodDemo {
def main(args: Array[String]): Unit = {
println(max(2, 3))
println(sub(4, 3))
println(sub1(4, 3))
println(decorate("Hello", "<<<", ">>>")) // <<<Hello>>>
println(decorate("Hello", "<<<")) // <<<Hello]
println(decorate(left = "<<<", str = "Hello", right = ">>>")) // 指定参数名,<<<Hello>>>
println(decorate("Hello", right = ">>>")) // [Hello>>>
println(sum(1, 2, 3, 4, 5))
println(sum(4, 5, 6, 7, 8, 9))
println(myPrint("huangbo"))
}
/**
* def:关键字,定义一个函数
* max:自定义的方法名
* (x:Int, y:Int):方法名后小扩号中为参数列表
* Int:参数后的Int为方法返回值类型
* {…}:大扩号中为方法体
* =:如果没有等号, 表示该方法的返回值是Unit
*
* Scala函数返回值可以不加return,默认函数体最后一条语句为返回值
* 函数体不指定返回值时,scala会根据实际类型进行类型推断
* Unit关键字表示函数不存在返回值,相当于java中的void关键字
* Scala每行语句结束后的分号可加可不加
*/
def max(x: Int, y: Int): Int = {
if (x > y) {
x
}
else {
y
}
}
// 如果没有return语句,那么方法中的最后一行就是返回值
def sub(a: Int, b: Int) = {
a - b
}
// 方法中的最后一行就是返回值,也可以使用return显示声明这就是返回值。
// 由于方法的定义中声明了,返回值就是Unit,所以sub1方法的返回值始终就是(),就算使用return进行返回也无济于事
def sub1(a: Int, b: Int): Unit = {
return a - b
}
// 如果定义一个方法,不需要返回值,那么可以省略 "="
def myPrint(s: String) {
println(s)
}
def myPrint1(s: String): Unit = {
println(s)
}
// 定义带默认参数的方法
def decorate(str: String, left: String = "[", right: String = "]") = left + str + right
// 定义变长参数方法
def sum(args: Int*): Int = {
var result = 0
for (arg <- args) result += arg
result
}
}
6.2. 定义函数
object FunctionDemo {
//定义一个函数f1,参数是两个Int类型,返回值是一个Int类型
val f1 = (x: Int, y: Int) => x + y
//再定义一个函数f2
val f2 = (m: Int, n: Int) => m * n
val f3 = (x: Int) => x
// 函数其实可以简化(正确方式1)
val f4 = 1 + (_: Int)
// 函数其实可以简化(正确方式2)
val f5: Int => Int = 1 + _
def main(args: Array[String]): Unit = {
println(f1(2, 3))
println(f2(2, 3))
println(f3(3))
println(f4(4))
println(f5(4))
}
}
函数的意义:
表示接受两个Int类型的变量,然后做累加。 经过 scala 的自动类型推断得知,最后返回的结果数据的类型也是 Int。 Function2 中 2 表示这个函数接收的参数个数是2个。
6.3. 方法和函数的区别
1、函数可以作为参数传递给方法,也就是说函数可以作为方法的参数。在函数式编程语言中,函数是 “头等公民”,它可以像任何其他数据类型一样被传递和操作。案例:首先定义一个方法,再定义一个函 数,然后将函数传递到方法里面
2、函数可以作为方法的参数,但是也可以作为函数的参数,例如:
3、方法也可以作为方法的参数。在需要传入函数作为参数的位置上传入一个方法的话,那么这个方法 会被自动的转换为函数作为参数,也可以通过“_”把方法转换为参数
4、方法也可以作为函数的参数。其实,原理就是方法会被自动转换为函数,所以也就是传入一个函数 到一个函数作为参数。
5、综合测试
object MethodAndFunctionDemo {
//方法m1参数要求是一个函数,函数的参数必须是两个Int类型,返回值类型也是Int类型
def m1(f: (Int, Int) => Int): Int = f(2, 6)
// 定义一个需要两个Int类型参数的方法
def m2(x: Int, y: Int): Int = x + y
// 定义一个计算数据不被写死的方法
def m3(f: (Int, Int) => Int, x: Int, y: Int): Int = f(x, y)
// 定义一个函数f1,参数是两个Int类型,返回值是一个Int类型
val f1 = (x: Int, y: Int) => x + y
// 再定义一个函数f2
val f2 = (m: Int, n: Int) => m * n
// 定义一个传入函数的函数
val f3 = (f: (Int, Int) => Int, x: Int, y: Int) => f(x, y)
//main方法
def main(args: Array[String]) {
//调用m1方法,并传入f1函数
val r1 = m1(f1)
println(r1)
//调用m1方法,并传入f2函数
val r2 = m1(f2)
println(r2)
// 调用m3方法, 传入f1函数
val result1 = m3(f1, 2, 4)
println(result1)
// 调用m3方法,传入f2函数
val result2 = m3(f2, 2, 4)
println(result2)
// 调用m3方法, 传入m2方法作为参数
println(m3(m2, 2, 4))
// 调用f3函数, 传入f1函数
println(f3(f1, 3, 4))
}
}
6.4. 将方法转换成函数使用
6.5. Scala函数式编程特点
(1)高阶函数(Higher-order functions)
(2)闭包(closures)
(3)模式匹配(Pattern matching)
(4)单一赋值(Single assignment)
(5)延迟计算(Lazy evaluation)
(6)类型推导(Type inference)
(7)尾部调用优化(Tail call optimization)
7. Scala数组Array
String
StringBuffer
7.1. 定长数组和变长数组
Array
array.toBuffer()
ArrayBuffer
arrayBuffer.toArray()
1、由于Array是不可变(长度不可变)的,初始化之初就有了固定的长度,所以不能直接地对其元素进行删 除操作,也不能多增加元素,只能修改某个位置的元素的值,要实现删除可以通过过滤生成新的Array的方式 来删除不要的元素。所以也就没有add,insert,remove等操作。
2、而ArrayBuffer是可变的,本身提供了很多元素的操作,当然包括增加,删除操作。
3、如果你需要在Array和ArrayBuffer之间转换,那么分别调用toBuffer()和toArray()方法即可
object ArrayDemo {
def main(args: Array[String]) {
//初始化一个长度为8的定长数组,其所有元素均为0
val arr1 = new Array[Int](8)
//直接打印定长数组,内容为数组的hashcode值
println(arr1)
//将数组转换成数组缓冲,就可以看到原数组中的内容了
//toBuffer会将数组转换长数组缓冲
println(arr1.toBuffer)
//注意:如果new,相当于调用了数组的apply方法,直接为数组赋值
//初始化一个长度为1的定长数组
val arr2 = Array[Int](10)
println(arr2.toBuffer)
//定义一个长度为3的定长数组
val arr3 = Array("hadoop", "storm", "spark")
//使用()来访问元素
println(arr3(2))
arr3(2) = "hive"
println(arr3.toBuffer)
println("-----------------------------------------------------------")
//////////////////////////////////////////////////
//变长数组(数组缓冲)
//如果想使用数组缓冲,需要导入import scala.collection.mutable.ArrayBuffer包
val ab = new ArrayBuffer[Int]()
//向数组缓冲的尾部追加一个元素
//+=尾部追加元素
ab += 1
//追加多个元素
ab += (2, 3, 4, 5)
//追加一个数组++=
ab ++= Array(6, 7, 7, 7)
//追加一个数组缓冲
ab ++= ArrayBuffer(8, 9)
// 追加一个List
ab ++= List(10, 11)
//打印数组缓冲ab
println(ab)
//在数组某个位置插入元素用insert
ab.insert(0, -1, 0)
//删除数组某个位置的元素用remove
ab.remove(8, 2)
println(ab)
}
}
7.2. 遍历数组
1、增强for循环
2、使用to可以生成序列,0 to 10 包含0包含10
3、好用的until会生成脚标,0 until 10 包含0不包含10
object ArrayForDemo {
def main(args: Array[String]) {
//初始化一个数组
val arr = Array(1, 2, 3, 4, 5, 6, 7, 8)
//增强for循环
for (i <- arr) {
println(i)
}
//使用to可以生成一个序列作为脚标
for (i <- (0 to arr.length - 1).reverse)
println(arr(i))
//好用的until会生成一个Range,reverse是将前面生成的Range反转
for (i <- (0 until arr.length).reverse)
println(arr(i))
//步长为2
for (i <- (0 until(arr.length, 2)).reverse)
println(arr(i))
}
}
7.3. 数组转换
yield关键字将原始的数组进行转换会产生一个新的数组,原始的数组不变
object ArrayYieldDemo {
def main(args: Array[String]) {
//定义一个数组
val arr = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
//将偶数取出乘以10后再生成一个新的数组
val res = for (e <- arr if e % 2 == 0) yield e * 10
println(res.toBuffer)
//更高级的写法,用着更爽
//filter是过滤,接收一个返回值为boolean的函数
//map相当于将数组中的每一个元素取出来,应用传进去的函数
val r = arr.filter(_ % 2 == 0).map(_ * 10)
println(r.toBuffer)
}
}
7.4. 数组常用算法
在Scala中,数组上的某些方法对数组进行相应的操作非常方便!
object ArrayTestDemo {
def main(args: Array[String]): Unit = {
val array = Array(4, 2, 7, 8, 1, 2, 2, 8, 8, 9)
val ab: ArrayBuffer[Int] = new ArrayBuffer[Int]()
ab += (4, 3, 5, 6, 2, 7, 1, 8)
val result11 = array.reduce((x: Int, y: Int) => x + y)
println(result11)
println(array.length)
println(array.max)
println(array.min)
println(array.sum)
println(array.mkString("-"))
println(array.mkString("<", ",", ">"))
println(array.filter((x: Int) => if (x % 2 == 0) true else false).toBuffer)
ab.trimStart(2)
println(ab)
ab.trimEnd(2)
println(ab)
println("-----------------1-----------------")
/**
* array的map和reduce操作
*/
val newArray: Array[Int] = array.map((x: Int) => x * 2)
println(newArray.toBuffer)
val newArray2: Int = array.reduce((x: Int, y: Int) => x + y)
println(newArray2)
println("-----------------2----------------")
/**
* fold操作
*/
array.foldLeft[Int](0)((init, value) => {
println(init, value);
init + value
})
println("------------------3----------------")
array.foldRight[Int](0)((init, value) => {
println(init, value);
init + value
})
val result: Int = array.fold(0)((x: Int, y: Int) => x + y)
println(result)
/**
* array求平均值
*/
val result2: (Int, Int) = array.map((x: Int) => (x, 1))
.reduce((x: (Int, Int), y: (Int, Int)) => (x._1 + y._1, x._2 + y._2))
println(result2._1 / result2._2.toDouble)
println("-----------------4-----------------")
/**
* 排序
*/
println(array.sorted.toBuffer)
println(array.sortBy(x => x).reverse.toBuffer)
println(array.sortWith((x: Int, y: Int) => x > y).toBuffer)
Sorting.quickSort(array)
println(array.toBuffer)
}
}
7.5. 多维数组
8. Scala集合相关
8.1. Scala集合-概述
Scala的集合有三大类:序列Seq、集合Set、映射Map,所有的集合都扩展自Iterable特质 在Scala中集合有可变(mutable)和不可变(immutable)两种类型,immutable类型的集合初始化 后就不能改变了(注意与val修饰的变量进行区别) 官网解释
Scala collections systematically distinguish between mutable and immutable collections. A mutable collection can be updated or extended in place. This means you can change, add, or remove elements of a collection as a side effect. Immutable collections, by contrast, never change. You have still operations that simulate additions, removals, or updates, but those operations will in each case return a new collection and leave the old collection unchanged.
大致意思是:Scala中的集合分为两种,一种是可变的集合,另一种是不可变的集合 可变的集合可以更新或修改,添加、删除、修改元素将作用于原集合 不可变集合一量被创建,便不能被改变,添加、删除、更新操作返回的是新的集合,老集合保持不变
scala集合类的层次结构:
scala.collection包中的集合类层次结构如下图:
These are all high-level abstract classes or traits, which generally have mutable as well as immutable implementations.
scala.collection.immutable包中的类层次结构:
scala.collection.mutable包中的类层次结构:
可变集合与不可变集合对应关系:
List 序列 类似于Java中的List,元素有序,可以重复
Set 集合 类似于Java中的Set,元素无序,不重复
Map 映射 类似于Java中的Map,元素是key-value形式
Tuple 元组 类似于Java中的一个 自定义POJO的简写
val和var:表明定义的变量(引用)是否能被修改而指向其他内容 immutable和mutable:表明的是内存中开辟出来的这块空间里的内容能否被修改,如果针对immutable变 量进行修改,其实是开辟了一块新的内存空间,产生了一个新的变量,而原来的变量依然没有改变
8.2. Scala序列--List
不可变的序列:import scala.collection.immutable._
在Scala中列表要么为空(Nil表示空列表)要么是一个head元素加上一个tail列表。
9 :: List(5, 2) :: 操作符是将给定的头和尾创建一个新的列表
注意::: 操作符是右结合的,如9 :: 5 :: 2 :: Nil相当于 9 :: (5 :: (2 :: Nil))
object ImmutableListDemo {
def main(args: Array[String]) {
//创建一个不可变的集合
val list1 = List(1, 2, 3)
//将0插入到lst1的前面生成一个新的List
val list2 = 0 :: list1
val list3 = list1.::(0)
val list4 = 0 +: list1
val list5 = list1.+:(0)
println(list2, list3, list4, list5)
//将一个元素添加到lst1的后面产生一个新的集合
val list6 = list1 :+ 3
println(list6)
val list0 = List(4, 5, 6)
//将2个list合并成一个新的List
val list7 = list1 ++ list0
//将lst0插入到lst1前面生成一个新的集合
val list8 = list1 ++: list0
//将lst0插入到lst1前面生成一个新的集合
val list9 = list1.:::(list0)
println(list7)
println(list8)
println(list9)
}
}
List的常用方法
//采用::及Nil进行列表构建
scala> val nums = 1 :: (2 :: (3 :: (4 :: Nil)))
nums: List[Int] = List(1, 2, 3, 4)
//由于::操作符的优先级是从右往左的,因此上一条语句等同于下面这条语句
scala> val nums=1::2::3::4::Nil
nums: List[Int] = List(1, 2, 3, 4)
//判断是否为空
scala> nums.isEmpty
res108: Boolean = false
//取第一个无素
scala> nums.head
res109: Int = 1
//取除第一个元素外剩余的元素,返回的是列表
scala> nums.tail
res114: List[Int] = List(2, 3, 4)
//取列表第二个元素
scala> nums.tail.head
res115: Int = 2
//List连接操作
scala> List(1,2,3):::List(4,5,6)
res116: List[Int] = List(1, 2, 3, 4, 5, 6)
//取除最后一个元素外的元素,返回的是列表
scala> nums.init
res117: List[Int] = List(1, 2, 3)
//取列表最后一个元素
scala> nums.last
res118: Int = 4
//列表元素倒置
scala> nums.reverse
res119: List[Int] = List(4, 3, 2, 1)
//一些好玩的方法调用
scala> nums.reverse.reverse==nums
res120: Boolean = true
scala> nums.reverse.init
res121: List[Int] = List(4, 3, 2)
scala> nums.tail.reverse
res122: List[Int] = List(4, 3, 2)
//丢弃前n个元素
scala> nums drop 3
res123: List[Int] = List(4)
scala> nums drop 1
res124: List[Int] = List(2, 3, 4)
//获取前n个元素
scala> nums take 1
res125: List[Int] = List(1)
scala> nums.take(3)
res126: List[Int] = List(1, 2, 3)
//将列表进行分割
scala> nums.splitAt(2)
res127: (List[Int], List[Int]) = (List(1, 2),List(3, 4))
//前一个操作与下列语句等同
scala> (nums.take(2),nums.drop(2))
res128: (List[Int], List[Int]) = (List(1, 2),List(3, 4))
//Zip操作,返回的是List类型的元组(Tuple)
scala> val nums=List(1,2,3,4)
nums: List[Int] = List(1, 2, 3, 4)
scala> val chars=List('1','2','3','4')
chars: List[Char] = List(1, 2, 3, 4)
scala> nums zip chars
res130: List[(Int, Char)] = List((1,1), (2,2), (3,3), (4,4))
//List toString方法
scala> nums.toString
res131: String = List(1, 2, 3, 4)
//List mkString方法
scala> nums.mkString res132: String = 1234
//转换成数组
scala> nums.toArray
res134: Array[Int] = Array(1, 2, 3, 4)
可变的序列:import scala.collection.mutable._
object MutableList Demoextends App {
//构建一个可变列表,初始有3个元素1,2,3
val list0 = ListBuffer[Int](1, 2, 3)
//创建一个空的可变列表
val list1 = new ListBuffer[Int]
//向list1中追加元素,注意:没有生成新的集合
list1 += 4
list1.append(5)
println(list1)
//将list1中的元素最近到list0中, 注意:没有生成新的集合
list0 ++= list1
//将list0和list1合并成一个新的ListBuffer 注意:生成了一个集合
val list2 = list0 ++ list1
//将元素追加到list0的后面生成一个新的集合
val list3 = list0 :+ 5
}
8.3. Scala集合--Set
不可变的Set
import scala.collection.immutable.HashSet
object ImmutableSetDemo extends App {
val set1 = new HashSet[Int]()
//将元素和set1合并生成一个新的set,原有set不变
val set2 = set1 + 4
//set中元素不能重复
val set3 = set1 ++ Set(5, 6, 7)
val set0 = Set(1, 3, 4) ++ set1
println(set0.getClass)
}
可变的Set
import scala.collection.mutable
object MutableSetDemo extends App {
//创建一个可变的HashSet
val set1 = new mutable.HashSet[Int]()
//向HashSet中添加元素
set1 += 2
//add等价于+=
set1.add(4)
set1 ++= Set(1, 3, 5)
println(set1)
//删除一个元素
set1 -= 5
set1.remove(2)
println(set1)
}
怎么求集合的交集并集差集呢?
object SetTestDemo {
def main(args: Array[String]): Unit = {
val set1: Set[Int] = Set(1, 2, 3, 4, 5)
val set2: Set[Int] = Set(3, 4, 5, 6, 7)
println(set1.union(set2))
println(set1.intersect(set2))
println(set1.diff(set2), set2.diff(set1))
}
}
8.4. Scala集合--Map
object MutableMapDemo extends App {
val map1 = new mutable.HashMap[String, Int]()
//向map中添加数据
map1("spark") = 1
map1 += (("hadoop", 2))
map1.put("storm", 3)
println(map1)
//从map中移除元素
map1 -= "spark"
map1.remove("hadoop")
println(map1)
println("----------------------1--------------------------------------------")
// 访问map1中的key-value
println(map1("storm"))
// println(map1("spark"))
val result = if (map1.contains("spark")) map1("spark") else 0
println(result)
println(map1.get("storm"))
println(map1.getOrElse("storm", 0))
println(map1.getOrElse("spark", 0))
println("----------------------2--------------------------------------------")
// 遍历
val map2 = Map("a" -> 1, "b" -> 2, "c" -> 3)
for (kv <- map2) {
println(kv._1, kv._2)
}
for ((k, v) <- map2) {
println(k, v)
}
println("----------------------3--------------------------------------------")
}
8.5. Scala元组--Tuple
映射是K/V对偶的集合,对偶是元组的最简单形式,元组可以装着多个不同类型的值。
8.5.1. 创建元组
8.5.2. 获取元组中的值
8.5.3. 将对偶的元组转成集合
其实对于二元组集合,完全可以认为是一个map集合,所以二元组集合可以和map进行相互转换。
8.5.4. 元组拉链操作
zip命令可以将多个值绑定在一起
注意:如果两个数组的元素个数不一致,拉链操作后生成的数组的长度为较小的那个数组的元素个数 相反操作:unzip
object TupleDemo {
def main(args: Array[String]): Unit = {
// 声明一个Tuple
val t = ("huangbo", 22, 55.55, true)
// 声明一个Tuple
val t1, (a, b, c, d) = ("huangbo", 22, 55.55, true)
// 打印输出一个Tuple
println(t1)
println(a, b, c, d)
// 访问Tuple对应位置上的元素
val aa = t1._1
println(aa, t1._2, t1._3)
// 将元素是 二元组 的集合转换成Map
var array = Array(("A", 1), ("B", 2))
val map1 = array.toMap
println(map1)
// 可以将一个map转成一个 二元组 的数组
val tuple1 = map1.toArray
println(tuple1.toBuffer)
// 如果只需要使用元组中的部分元素呢。
val (aa1, _, bb1, _) = t1
println(aa1, bb1)
// List的拉链操作
val alist = List("a", "b", "c")
val blist = List(1, 2, 3)
val result33: Map[String, Int] = alist.zip(blist).toMap
println(result33)
}
}
8.6. Scala映射—Map
在Scala中,把哈希表这种数据结构叫做映射,在Java中也叫做映射
在Python中,把哈希表这种数据结构叫做字典
不管叫什么,存储的都是key-value对形式的键值对
8.6.1. 构建Map
scala> val scores = Map("huangbo" -> 85, "xuzheng" -> 99, "huanglei" -> 90)
scores: scala.collection.immutable.Map[String,Int] = Map(huangbo -> 85, xuzheng -> 99, huanglei -> 90)
scala> val scores = Map(("huangbo",85), ("xuzheng",99), ("huanglei",90))
scores: scala.collection.immutable.Map[String,Int] = Map(huangbo -> 85, xuzheng -> 99, huanglei -> 90)
8.6.2. 获取和修改Map中的值
scala> val scores = Map("huangbo" -> 85, "xuzheng" -> 99, "huanglei" -> 90)
scores: scala.collection.immutable.Map[String,Int] = Map(huangbo -> 85, xuzheng -> 99, huanglei -> 90)
scala> scores("huangbo")
res6: Int = 85
scala> scores.getOrElse("huangbo", 100)
res7: Int = 85
scala> scores.getOrElse("huangbo11", 100)
res8: Int = 100
注意:通常我们在创建一个集合是会用val这个关键字修饰一个变量(相当于java中的final),那么就意 味着该变量的引用不可变,该引用中的内容是不是可变,取决于这个引用指向的集合的类型。
常见的map操作:
scala> val studentInfo=Map("john" -> 21, "stephen" -> 22,"lucy" -> 20)
studentInfo: scala.collection.immutable.Map[String,Int] = Map(john -> 21, stephen -> 22, lucy -> 20)
scala> studentInfo.clear()
<console>:15: error: value clear is not a member of scala.collection.immutable.Map[String,Int]
8.6.3. Option, None, Some类型
studentInfo.clear()
^
scala> val studentInfoMutable=scala.collection.mutable.Map("john" -> 21, "stephen" -> 22,"lucy" -> 20)
studentInfoMutable: scala.collection.mutable.Map[String,Int] = Map(john -> 21, lucy -> 20, stephen -> 22)
scala> studentInfoMutable.clear()
scala> studentInfoMutable res11: scala.collection.mutable.Map[String,Int] = Map()
scala> for( i <- studentInfoMutable ) println(i)
scala> val studentInfoMutable=scala.collection.mutable.Map("john" -> 21, "stephen" -> 22,"lucy" -> 20)
studentInfoMutable: scala.collection.mutable.Map[String,Int] = Map(john -> 21, lucy -> 20, stephen -> 22)
scala> for( i <- studentInfoMutable )
println(i) (john,21) (lucy,20) (stephen,22)
scala> studentInfoMutable.foreach(e=> println(e._1+":"+e._2))
john:21
lucy:20
stephen:22
scala> val xMap=new scala.collection.mutable.HashMap[String,Int]()
xMap: scala.collection.mutable.HashMap[String,Int] = Map()
scala> xMap.put("spark",1)
res15: Option[Int] = None
scala> xMap.put("spark",1)
res16: Option[Int] = Some(1)
scala> xMap.contains("spark")
res17: Boolean = true
scala> val xMap=scala.collection.mutable.Map(("spark",1),("hive",1))
xMap: scala.collection.mutable.Map[String,Int] = Map(spark -> 1, hive -> 1)
scala> "spark" -> 1
res18: (String, Int) = (spark,1)
scala> xMap.get("spark")
res19: Option[Int] = Some(1)
scala> xMap.get("SparkSQL")
res20: Option[Int] = None
8.6.3. Option, None, Some类型
Option、None、Some是scala中定义的类型,它们在scala语言中十分常用,因此这三个类型非学重要。
None、Some是Option的子类,它主要解决值为null的问题,在java语言中,对于定义好的HashMap,如 果get方法中传入的键不存在,方法会返回null,在编写代码的时候对于null的这种情况通常需要特殊处 理,然而在实际中经常会忘记,因此它很容易引起 NullPointerException异常。
在Scala语言中通过Option、None、Some这三个类来避免这样的问题,这样做有几个好处,首先是代码可读 性更强,当看到Option时,我们自然而然就知道它的值是可选的,然后变量是Option,比如 Option[String]的时候,直接使用String的话,编译直接通不过。
几种常见“空”的概念
Nothing : 所有类的子类
None: 从map中使用get方法获取值,如果没有获取到的就是None
Nil: 表示一个空序列
Null: 所有引用类的子类
Unit: 抽象了void关键字,用来表示一个方法没有返回值得抽象
9. 编程练习
9.1. 99乘法表
object Table99Demo {
def main(args: Array[String]): Unit = {
// 传统方式
for (i <- 1 to 9) {
for (j <- 1 to i) {
printf("%d*%d=%2d\t", i, j, (i * j))
}
println()
}
println("-------------------------------------")
// 新方式1
for (i <- 1 to 9; j <- 1 to i) {
printf("%d*%d=%2d\t", i, j, (i * j)); if (i == j) println()
}
// 新方式2
for (i <- 1 to 9; j <- 1 to i) {
print(s"$i*$j=${i * j}${if (i == j) "\n" else "\t"}")
}
}
}
9.2. Scala版本的WordCount
现在有一个数组变量读入了一段话形成了一个数组,求出这段话中的每个单词的出现次数,简单来说, 就是单词统计:
object WordCountDemo {
def main(args: Array[String]): Unit = {
val array = Array("hello huangbo", "hello xuzheng", "hello wangbaoqiang")
/**
* 第一种方式
*/
val result: List[(String, Int)] = array.flatMap(_.split(" "))
.map((_, 1))
.groupBy(t => t._1)
.map(t => (t._1, t._2.length))
.toList
.sortBy(t => t._2)
.reverse
for (t <- result) {
println(t)
}
println("------------------------------1------------------------------------")
/**
* 第二种方式
*/
val result2: Map[String, Int] = array.flatMap(_.split(" "))
.map(x => (x, 1))
.groupBy(x => x._1)
.mapValues(x => x.length)
for ((k, v) <- result2) {
println(k, v)
}
println("------------------------------2------------------------------------")
/**
* 第三种方式
*/
val result3: Map[String, Int] = array.flatMap(_.split(" "))
.map(x => (x, 1))
.groupBy(x => x._1)
.mapValues(x => x.foldLeft(0)((x, y) => x + y._2))
for ((k, v) <- result3) {
println(k, v)
}
}
}
结果:
9.3. Scala版本的插入排序InsertSort
import scala.util.control.Breaks
object InsertSortDemo {
def main(args: Array[String]): Unit = {
val array = Array(4, 12, 6, 3, 8, 9, 5)
val ab = array.toBuffer
// 创建Breaks对象
val forLoop = new Breaks
for (i <- 1 until ab.length) {
val value_i = ab(i)
// 把需要可能break的代码放在 breakable 中执行
forLoop.breakable {
for (j <- 0 to i - 1) {
val value_j = ab(j)
if (value_j > value_i) {
ab.remove(i, 1)
ab.insert(j, value_i)
// 使用break进行跳出
forLoop.break()
}
}
}
}
println(ab)
}
}
另一种实现方式:
def isort(xs: List[Int]): List[Int] = if (xs.isEmpty) Nil else insert(xs.head, isort(xs.tail))
def insert(x: Int, xs: List[Int]): List[Int] = if (xs.isEmpty || x <= xs.head) x :: xs else xs.head :: insert(x, xs.tail)