Scala
- Scala介绍
- Scala安装
- 1. windows下安装, 环境配置
- 2. Scala-IDEA
- 3. IntelliJ IDEA中安装Scala插件
- 4. IntelliJ IDEA-2017.3版本中创建Scala项目
- Scala基础
- 1. 数据类型
- 2. 常量&变量
- 3. 类&对象
- 4. 判断语句
- 5.循环语句
- 5.1 to和until的使用
- 5.2 for循环
- 5.3 while和do...while循环
- Scala函数
- 1. 函数定义
- 2. 递归函数
- 3. 带默认值的函数
- 4. 可变参数列表的函数
- 5. 匿名函数
- 6. 嵌套函数
- 7. 偏应用函数
- 8. 高阶函数
- 9. 柯里化函数
Scala介绍
Scala是一门多范式的编程语言, 它与Java极其相似, 都是运行在JVM上的编程语言. Scala设计的初衷是将面向对象编程和函数式编程的各种特性集成起来。
Scala是一门编译型语言, 同时也是一门弱类型语言, 它的语法简单, 但是灵活, 极大的灵活性使得初学者对其难以驾驭. 大数据中Spark底层是由Scala语言实现, 因此想要学习Spark, 要先入门Scala.
Scala程序运行时, 会先编译成Java字节码文件, 然后该文件再在JVM上运行.
Scala六个特征
- 可以和Java无缝混编
- 支持类型推测(自动推测类型)
- 并发和分布式(Actor, 类似Java中的Thread)
- 结合Java中interface实现和abstract继承的新特性trait
- 模式匹配match(类似Java中的switch, 但支持的类型更加丰富)
- 高阶函数(面向函数编程)
- 函数参数是函数
- 函数返回是函数
Scala安装
1. windows下安装, 环境配置
官网下载scala2.10.4的压缩文件:https://www.scala-lang.org/download/2.10.4.html
解压并配置环境变量(和配置jdk一样)
新建SCALA_HOME
上个步骤完成后,编辑Path变量,在后面追加如下:
%SCALA_HOME%\bin;
打开cmd,输入:scala - version 看是否显示版本号,确定是否安装成功
可通过scala命令进入Scala Shell.
2. Scala-IDEA
开发Scala程序, 可以去下载一个支持Scala的eclipse. 不推荐在现有的eclipse中安装Scala插件的形式.
下载网址:http://scala-ide.org/download/sdk.html
3. IntelliJ IDEA中安装Scala插件
打开idea,close项目后,点击Configure->Plugins
搜索scala,点击Install安装
或者直接在settings的plugins中搜索scala
由于笔者之前以及安装过, 因此提示的是卸载(Uninstall), 如果之前未安装过会是一个绿色的√, 文字未Install.
4. IntelliJ IDEA-2017.3版本中创建Scala项目
创建Scala项目
设置项目名, 存放位置, JDK以及ScalaSDK
在src下新建包, 右键新建Scala Class
注意这里选择Object类型
Scala基础
1. 数据类型
Scala中与Java相似的数据类型
数据类型 | 描述 | 范围 |
---|---|---|
Byte | 8bit有符号数字 | -128~127 |
Short | 16bit有符号数字 | -32768~32767 |
Int | 32bit有符号数字 | -2147483648~2147483647 |
Long | 64bit有符号数字 | -263~263-1 |
Float | 32位IEEE754标准的单精度浮点数 | 1.4E-45~3.4028235E38 |
Double | 64位IEEE754标准的双精度浮点数 | 4.9E-324~1.7976931348623157E308 |
Char | 16位Unicode字符 | U+0000~U+FFFF |
String | 字符串 | - |
Boolean | 布尔类型 | true/false |
Null | 空值或空引用, 是AnyRef的子类 | - |
Scala独有的数据类型
数据类型 | 描述 |
---|---|
Unit | 表示无值, 同void |
Any | 所有类型的超类, 任何实例都属于Any. Any的父类还是Object |
AnyRef | 所有引用类型的超类 |
AnyVal | 所有值类型的超类 |
Nothing | 表示没有值, 所有其他类型的子类型, 该类型的对象不会报空指针异常 |
None | Option的子类,用于安全的函数返回值 |
some | Option的子类, 用于存储元组数据(“hello”,“scala”), 类似于map,由一对小括号包括 |
2. 常量&变量
定义变量: var a: Int = 1
定义常量: val b: Int = 1
由于Scala支持类型推测, 因此常量或变量的类型一般省略, 上述定义可简写为:
var a = 1
val b = 1
注意:
val修饰的常量一旦定义, 则其值不可修改. 如果修改, 会报error: reassignment to val.
Scala中语句之后可以不加分号’;’ , 自动根据换行符区分. 但是, 如果多条语句在一行, 必须加分号隔开.
3. 类&对象
创建类
class Person {
val name = "qibao"
val age = 22
def tell() = {
"My name is " + name + ". My age is " + age
}
}
val 声明两个常量, 不可修改.
def 声明函数, 具体格式之后再说.
创建对象
object OnePerson {
def main(args: Array[String]): Unit = {
val person = new Person()
println(person.name + ":" + person.tell())
}
}
注意:
- 命名规则与Java保持一致即可, 包括类首字母都是大写, 方法首字母小写, 符合驼峰式命名法.
- Scala中class默认可传参, 默认的传参数就是默认的构造函数. 重载构造函数时, 必须调用默认构造函数.
//定义类时传参
class Person(var name: String, var age: Int) {
var fcp = 0.0
//重载构造器, 参数不能有修饰符
def this(name: String, age:Int, facePower:Double) = {
//调用默认构造器
this(name,age)
fcp = facePower
}
def tell() = println(name + ":" + age + ":" + fcp)
}
- 在class修饰的类中, 如果参数用var来修饰, 那么可以之间通过对象名.属性来调用; 如果参数用val修饰, 则不能通过对象名.属性来修改; 如果参数没有修饰符, 则该属性为私有, 无法通过对象来调用.
object ScalaBase {
def main(args: Array[String]): Unit = {
var p1 = new Person("qb", 22, '男')
p1.name = "qibao"
// p1.age = 23 val定义无法修改
println(p1.age)
// println(p1.sex) 没有修饰符,无法访问
}
}
class Person(var name: String, val age: Int, sex: Char) {
def tell() = println(name + ":" + age)
}
- Scala中object修饰的单例对象, 不可传递参数. 在Scala中没有static这一关键字, 所有静态属性和方法都需要放在object修饰的对象中.
- 创建object修饰的对象时, 不用new这一关键字; 创建class修饰的对象时, 使用new, 且在new的时候, 除了class中的方法不执行外, 其余的都执行.
- 如果在同一个文件中, object对象和class类的名称相同, 则这个对象就是这个类的伴生对象, 这个类就是这个对象的伴生类. 伴生对象和伴生类之间可以互相访问私有变量.
- Scala中伴生对象+伴生类 = Java中某个类
- apply方法的使用: apply相当于Java中的静态代码块, 创建对象时自动调用. 因此它要在object修饰的伴生对象中定义.
class Person(var name: String, var age: Int) {
def tell() = println(name + ":" + age)
}
object Person {
def apply(name: String, age: Int) = new Person(name, age)
}
定义之后再创建Person对象时, 可以省略new关键字
object ScalaBase {
def main(args: Array[String]): Unit = {
var p1 = new Person("qb", 22)
var p2 = Person("qb", 23)
}
}
4. 判断语句
判断语句与Java中的用法完全一致, 两种形式 if-else和if-else if -else.
val age =18
if (age < 18 ){
println("no allow")
}else if (18 <= age && age <= 20){
println("allow with other")
}else{
println("allow self")
}
5.循环语句
5.1 to和until的使用
这俩个方法都会产生一个集合, 其中until前闭后开, to前后均闭.
比如说,1 until 10 返回1到9的数组, 1 to 10 返回1到10的数组.
println(1 to 5 )//打印 1, 2, 3, 4, 5
println(1.to(5))//与上面等价,打印 1, 2, 3, 4, 5
println(1 until 5 ) //不包含最后一个数,打印 1,2,3,4
println(1.until(5))//与上面等价
除按顺序打印外, 还可设置步长.
println(1 to (10 ,2))//步长为2,从1开始打印 ,1,3,5,7,9
println(1.to(10, 2))
println(1 until (10 ,3 ))//步长为3,从1开始打印,打印1,4,7
5.2 for循环
- 通过索引
var list = List("hello", "scala", "spark")
for (index <- 0 until list.length) println(list(index))
- 增强for循环
for (elem <- list) println(elem)
在用Scala写for循环时, 不需要指定循环变量的类型, Scala会自动推测出其类型, 同时给循环变量赋值使用"<-". Scala习惯来讲, 如果判断或循环语句只有一行, 一般把它们放在一行.
- 多层for循环
当有多层循环时, 可用分号’;'将循环变量隔开.
//打印九九乘法表
for (i <- 1 to 9; j <- 1 to 9) {
if (j <= i) print(i + "*" + j + "=" + i * j + "\t")
if (j == i) println()
}
- for循环中添加条件判断
Scala的for循环, 还可以在循环变量设置的( ) 中添加条件判断, 只需用分号’;'将它们隔开即可.
for (i <- 1 to 100; if i % 2 == 0) println(i)
for (i <- 1 to 10 ; if i % 2 == 0 ;if i == 4 ) println(i)
- yield关键词自动封装集合
在for循环中使用yield关键词后, 能够将符合要求的元素自动封装到一个集合中.
//将1-20之间的偶数封装到rest集合中
val rest = for (i <- 1 to 20; if i % 2 == 0) yield i
for (elem <- rest) println(elem)
5.3 while和do…while循环
Scala中while和do-while循环的用法与Java中也是完全一致的, 唯一一点需要注意的是Scala语法中没有break语句, 因此想要跳出循环可以自定义一个布尔类型的标识符.
var flag = true
var index = 0
while (flag && index < 50){
index += 1
if (index == 20) flag = false
println(index)
}
通过上述几个实例, 可以看出Scala语法的灵活性以及在写Scala代码时遵循的一些简写习惯, 之后在函数板块会有更多简写规则, 更能体现Scala的灵活.
Scala函数
1. 函数定义
- 函数定义用def关键词.
- 可有参也可无参,传参时需指定参数类型.
- 函数可写返回值类型也可不写,Scala会自动推断. 但有时候不能省必须写,比如在递归函数或函数的返回值是函数类型的时候.
- Scala中函数有返回值时,return可写可不写. 如果不写, 函数会把最后一行当做结果返回。当写return时,必须要写函数的返回值.
- 如果函数体只有一行,可以将方法名和函数体写在一行.
- 传递给方法的参数可以在方法中使用,并且scala规定方法传过来的参数为val的,不是var的。
- 如果去掉方法体前面的等号,那么这个方法返回类型必定是Unit的。这种说法无论方法体里面什么逻辑都成立,scala可以把任意类型转换为Unit.假设,里面的逻辑最后返回了一个string,那么这个返回值会被转换成Unit,并且值会被丢弃。
def fun1(a: Int, b: Int): Int = {
return a + b;
}
def fun2(a: Int, b: Int) = {
a + b
}
def fun3(a: Int, b: Int) = a + b
从f1–>f3, 由Java编码思想向Scala编码思想的转变. Scala追求的是一种高效, 快速的编码习惯.
2. 递归函数
递归函数: 函数自身调用自身
//求一个数的阶乘
def fun4(n: Int): Int = {
if (n == 1 || n == 0) 1
else n * fun4(n - 1)
}
需要注意的是, 递归函数在最后一次返回之前返回的是一个函数, 此时Scala无法推测其类型. 因此, 在Scala中写递归函数时, 必须明确返回值类型, 否则会报Type mismatch的错.
3. 带默认值的函数
默认值函数: 函数在定义时, 指定某个或多个参数的值.
object ScalaFun1 {
def main(args: Array[String]): Unit = {
println(fun5());
println(fun5(1));
println(fun5(b=2));
println(fun5(1,2));
println(fun5_1(1));
println(fun5_2(b = 2));
}
//默认值函数
def fun5(a: Int = 10, b: Int = 20) = a + b
def fun5_1(a: Int, b: Int = 20) = a + b
def fun5_2(a: Int = 10, b: Int) = a + b
}
注意: 当参数有默认值时, 可以不指定, 也可指定. 但如果想要给不是第一个位置的参数指定值时, 需要明确参数的名称.
4. 可变参数列表的函数
可变参数列表函数: 函数参数可以是一个也可以是多个. Java中使用…来指定可变参数列表方法, Scala中使用*.
def fun6(args: Double*) = {
var sum = 0.0
for (arg <- args) sum += arg
sum
}
注意: Scala中+=, -=前后的数值类型必须相同, Scala不会隐式类型转换; +,- 可以前后类型不相同. 此外, 函数最后一行作为返回值, for的返回是Unit(空), 因此需要另起一行返回sum.
补充一点: Scala中没有++, --运算符.
5. 匿名函数
匿名函数: 没有名字的函数. 既然没有名字就无法调用, 因此可以将匿名函数赋给一个变量.
def main(args: Array[String]): Unit = {
println(fun7(1, 2));
}
val fun7 = (a: Int, b: Int) => a + b
注意:
- 匿名函数可以有参, 也可无参
- 匿名函数的参数和方法体要用 => 来连接
- 匿名函数不能显示的声明返回值类型
6. 嵌套函数
匿名函数: 函数体内又定义了一个函数.
def fun8 (n:Int) = {
def fun9(a:Int,b:Int):Int ={
if(a==1) b
else fun9(a-1,a*b)
}
fun9(n,1)
}
7. 偏应用函数
偏应用函数: 偏应用函数是一种表达式, 它实际是调用了其他函数. 只不过不需要提供函数所需的全部参数, 只需提供部分或不提供参数, 不提供的参数单独指定.
//普通函数
def log(date: Date, content: String) = {
println("date" + date + "\tcontent" + content)
}
val date = new Date()
log(date,"log1")
log(date,"log2")
log(date,"log3")
//偏应用函数
val logBoundDate = log(date,_:String)
logBoundDate("log1_1")
logBoundDate("log2_1")
logBoundDate("log3_1")
分析代码可知, 偏应用并不是真实的函数, 而是将某个函数的一个或多个参数指定后的表达式. 上述需求中, 需要传入时间和内容作为日志, 但时间往往是不变的, 变化的只是内容, 因此可以使用偏应用函数来处理. 其中’_’ 代表占位符, 指代第二个参数.
8. 高阶函数
高阶函数: 高阶函数是指(1)函数的参数是函数; (2)函数的返回类型是函数; (3)函数的参数和返回类型是函数 的函数.
函数作为参数或返回值进行传递时, 也是没有名字的, 因此也相当于是匿名函数, 上文也说过, 匿名函数的参数和方法体之间用 => 进行连接.
- 函数的参数是函数
def highFun1(f: (Int) => Int, num: Int) = f(num)
def temF1(num: Int) = num + 1
//调用函数
println(highFun1(temF1, 1))
代码分析: highFun1有两个参数, 其中一个参数是函数, 函数的类型匹配参数为Int, 返回为Int的函数. 由此可以看出, Scala把函数当成对象在函数之间传递, 这也是Scala函数式编程的体现.
同时在使用高阶函数时, 函数参数直接写函数名, 不需要加括号(), 加括号说明是调用函数, 使用的是函数返回的结果, 直接写函数名才是将函数切切实实地作为参数进行传递.
- 函数的返回类型是函数
def highFun2(num: Int): (Int, Int) => Double = {
def temF2(num1: Int, num2: Int): Double = {
num + num1 + num2
}
temF2
}
//调用函数
var fun = highFun2(1)
println(fun(1,1))
//上述两行代码也可简写为
println(highFun2(1)(1,1))
代码分析: highFun2有一个Int类型的参数, 返回一个(Int, Int) => Double格式的函数, 返回时可以像上述代码中写的一样, 主动定义一个函数, 然后返回, 也可以直接在最后一行写一个匿名函数返回. 如下:
def highFun3(num: Int): (Int, Int) => Double = {
(num1: Int, num2: Int) => num1 + num2 + num
}
调用highFun2时, 它返回一个函数, 这就相当于匿名函数, 可以通过一个变量来接收这个函数, 然后再通过给这个变量设置参数的形式得到返回函数的值.
- 函数的参数和返回类型都是函数
def highFun4(f: (Int,Int) => Int, num1: Int): (Int) => Double = {
(num: Int) => num + f(num1,1)
}
//调用函数
var fun = highFun4((a:Int,b:Int)=>a+b,1)
var res0 = fun(1)
println(res0)
//上述可简化
var res1 = highFun4((a:Int,b:Int)=>a+b,1)(1)
println(res1)
//如果匿名函数的参数在方法体中只使用了一次 那么可以写成_表示
var res2 = highFun4(_+_,2)(2)
println(res2)
highFun4的参数类型和返回类型不用再说了, 主要是函数调用时代码简化的过程, highFun4的匿名函数参数已经指定该函数有两个参数(这句话比较拗口, 建议读者慢慢领会), 在调用highFun4时, 如果这个函数参数的两个参数只使用一次, 那么可以用 _ 来表示这些参数.
Scala学习地越深入, 越容易发现Scala许多操作类似jQuery中的链式操作, 而且有些内容能省则省, 这就于人类日常交流类似, 双方都知道的事情, 就没有必要每次说话时再点出来.
9. 柯里化函数
柯里化函数: 柯里化函数实质上是一类高阶函数的简化.
先来看这一类高阶函数:
def highFun5(num: Int): (Int) => Int = {
def fun(a: Int) = num + a
fun
}
//调用时,可以将两个参数写一行
println(highFun5(1)(2))
再来看柯里化函数:
def klhFun(a: Int)(b: Int) = a + b
//调用方式跟上述代码一致
println(klhFun(1)(2))
满足上述样式的函数就是柯里化函数, 再比如:
def fun7(a :Int,b:Int)(c:Int,d:Int) = {
a+b+c+d
}
println(fun7(1,2)(3,4))