概述
Scala是一门专门为计算而生的语言,Scala将(Java或者C++ )面向对象和函数式编程结合在一起。其中函数式编程强调的是通过传递算子(代码|函数)实现大规模的数据集的计算。Scala虽然是一门独立的编程语言,但是它可以无缝的和Java语言对接,Scala编译的代码可以直接运行在Java虚拟机(JVM)上。
语法
一、变量\常量
语法 : var|val 变量名[:类型] = 值
注意 :
1. Scala中的值类型包括Byte、Short、Char、Int、Long、Float、Double、Boolean
2. Scala是纯面向对象语言,遵守"万物皆对象"的设计原则,因此Scala中值类型也是对象,没有Java中的基本数据类型
3. Scala中所有的值类型都归属于scala.*包中,无需声明,直接使用
4. Scala中的Stirng类型使用的是Java中的java.lang.String类型
5. Scala中值类型的默认类型:整数(Int)、小数(Double)
6. Scala中的变量类型支持自动推断,因此在定义变量时类型可以不写,不写则使用默认类型
7. Scala推荐使用常量,即声明时使用val关键字
// 声明变量
var a:Int = 9
// 简写
var a = 9
// 声明常量
val b:String = "DouDou"
// 简写
val b = "DouDou"
类型转换
通俗的讲就是自动类型提升,小值可以赋给大值,但是大值不可以赋给小值
// 定义Byte类型的变量
scala> var a : Byte = 10
a: Byte = 10
// 将Byte类型的变量赋值给Short类型
// 小类型 给 大类型 所有可以赋值
scala> var b: Short = a
b: Short = 10
// 同样的 Short类型的变量给Int类型 小类型给大类型 赋值成功
scala> var c : Int = b
c: Int = 10
// 将Int类型的变量赋值类Short类型 大类型给小类型赋值
// 编译不通过 ERROR
scala> var d :Short = c
<console>:12: error: type mismatch;
found : Int
required: Short
var d :Short = c
^
运算符
Scala中支持的运算符包括 :
- 赋值运算符 :
=
- 关系运算符 :
>、<、<=、>=、!=、==
- 逻辑运算符 :
&&、||、!
- 位运算符 :
&、|、~、^、<<、>>、>>>
- 算术运算符 :
+、-、*、/、%
注意:
- Scala中没有
++
和--
的语法,但是有+=
和-=
的语法- Scala是纯面向对象的 运算符也是值类型中的方法
// 关系运算符
scala> 10 > 8
res3: Boolean = true
// 逻辑运算符
scala> 1==1 && 3>2
res7: Boolean = true
// 位运算符
// 位运算符采用值的二进制形式进行计算 效率极高
scala> 1&2
res12: Int = 0
// 运算符也是值类型中的方法
scala> 1.+(1)
res1: Int = 2
Scala类型层次结构
在Scala中,所有的值都有类型,包括数值和函数。下图阐述了类型层次结构的一个子集。
Any
是所有类型的超类型,也称为顶级类型。它定义了一些通用的方法如equals
、hashCode
和toString
。Any
有两个直接子类:AnyVal
和AnyRef
。AnyVal
代表值类型。有9个预定义的非空的值类型分别是:Double
、Float
、Long
、Int
、Short
、Byte
、Char
、Unit
和Boolean
。Unit
(类似于java中的void
)是不带任何意义的值类型,它仅有一个实例可以像这样声明:()
。所有的函数必须有返回,所以说有时候Unit
也是有用的返回类型。AnyRef
代表引用类型。所有非值类型都被定义为引用类型。在Scala中,每个用户自定义的类型都是AnyRef
的子类型。如果Scala被应用在Java的运行环境中,AnyRef
相当于java.lang.Object
。Nothing
是所有类型的子类型,也称为底部类型。没有一个值是Nothing
类型的。它的用途之一是给出非正常终止的信号,如抛出异常、程序退出或者一个无限循环(可以理解为它是一个不对值进行定义的表达式的类型,或者是一个不能正常返回的方法)。Null
是所有引用类型的子类型(即AnyRef
的任意子类型)。它有一个单例值由关键字null
所定义。Null
主要是使得Scala满足和其他JVM语言的互操作性,但是几乎不应该在Scala代码中使用。我们将在后面的章节中介绍null
的替代方案。
二、分支语句
Scala中的分支语句和Java中的分支语句类似,不同的是
- Scala中所有的表达式都是有返回值的
- 我们在使用分支语句的时候必须返回一个值,而表达式的最后一行就是该表达式的返回值
- 如果表达式中没有返回值时,例如打印语句
println()
,那么表达式的值就是()
即Unit
类型- Scala中的表达式可以返回不同类型的值,只要你定义的变量的类型足够大
- 在Scala中,如果代码块有多行代码时,必须用大括号
{}
包裹起来,如果代码块只有一行内容,则大括号可以省略。
if 语句
// 语法 :
if (布尔表达式){
// 满足执行
}
//-------------------------------------
scala> val age = 18
age: Int = 18
scala> :paste
// Entering paste mode (ctrl-D to finish)
if(age > 16){
println("青年")
}
// Exiting paste mode, now interpreting.
青年
if … else … 语句
// 语法 :
if (布尔表达式){
// 满足执行
}else{
// 不满足执行
}
//--------------------------------------
scala> :paste
// Entering paste mode (ctrl-D to finish)
if(age>16){
println("青年")
}else{
println("未成年")
}
// Exiting paste mode, now interpreting.
未成年
if … else if … else 语句
// 语法 :
if (布尔表达式1){
// 满足表达式1执行
} else if (布尔表达式2){
// 满足表达式2执行
} else {
// 都不满足执行
}
//------------------------------------
// 表达式的最后一行就是返回值
val d =
if(age>18){
age + 1
}else if(age>10){
age + 2
}else{
age + 3
}
// val d:Int = 16
//------------------------------------
// 当代码块中只有一行代码的时候 可以省略大括号
val f =
if(age>18)age + 1
else if(age>10) age + 2
else age + 3
// val f:Int = 16
三、循环
while 循环
# 语法:
while(循环条件){
// 循环体
}
do … while … 循环
# 语法:
do{
// 循环体
}while(循环条件)
for 循环
Scala 中的For循环相比Java中的For循环更加的简洁
# java 中的 for 循环
for(int i = 0; i<=100;i++){
// 循环体
}
# scala 中的 for 循环
for(i <- 0 to 100){
println(i)
}
// 语法1 :
// 1 to 100 得到的是一个数值区间Range [0,100],包含起始和结束数值
for(i <- 0 to 100){
// 循环体
}
// 语法2 :
// 1 until 100 的到的是一个数值区间Range[0,100),包含起始和不包含结束数值
for(i <- 0 until 100){
// 循环体
}
嵌套 For 循环
在Scala中多重嵌套的 For 循环可以使用简化写法
在for的
()
中多重循环用;
分割即可
for(m <- 1 to 9; n <- 1 to 9){
// 这里将会执行 81 次
// m 每执行1次 n 执行9次
}
指定迭代步长
在使用
m to n
的语法时,可以指定中间迭代的步长,默认为1
// 语法 :
// m to n by step
scala> 1 to 10 by 2
res24: scala.collection.immutable.Range = Range(1, 3, 5, 7, 9)
for(n <- 1 to 10 by 2){
// n : 1, 3, 5, 7, 9
}
if 守卫
在迭代时添加判断条件 决定是否进入代码块
// 通过循环计算 1~10 奇数之和
// 普通写法
var sum = 0
for(n <- 1 to 10){
if( n % 2 != 0){
sum += n
}
}
// if守卫简化实现
var sum = 0
for(n <- 1 to 10 if n % 2 != 0){
sum += n
}
// sum = 25
break
Java中break表示结束循环
在Scala中没有
break
和continue
关键字,但是Scala提供了另外一种方式来实现类似于break的功能
// 导入指定的包
import scala.util.control.Breaks._
// 构建breakable代码块
breakable {
for (n <- 1 to 10) {
// 这里的break()是一个方法
// 在scala中如果是一个无参的方法,可以省去(),直接写方法名
if (n == 5) break()
println(n)
}
}
这种方式底层是通过抛出和捕获异常完成的,运行时效率较低,因此应尽量避免使用这套机制。
yield
for循环中的yield会将表达式的值记录下来,保存在集合中,循环结束后将集合返回
- 首先会将循环中yield表达式的值记录下来存入缓冲区中
- 当循环结束时将缓冲区中的所有值存放在一个
Vector
类型的集合中,然后返回集合- 新集合中元素的类型和遍历集合的类型是一致的
scala> val newCollection = for(i <- 1 to 3) yield i + 1
newColle: scala.collection.immutable.IndexedSeq[Int] = Vector(2,3,4)
四、函数
函数的声明
// 语法 :
def 函数名(参数列表) : 返回值类型 = {
// 函数体
}
// 声明普通函数
def sum(x: Int, y: Int): Int = {
x + y // 自动将代码块的最后一行内容作为返回值 则此时的返回值就是 x + y
}
// 声明普通函数【简化】
// 省略返回值类型 `:Int` 自动推导返回值的类型
def sum2(x: Int, y: Int) = {
x + y
}
// 声明普通函数【继续简化】
// 当函数只有一行代码时可以省略括号
def sum2(x: Int, y: Int) = x + y
// 无返回值的函数 返回值声明为Unit类型 类似于Java中void Unit类型的值为()
def sum3(x: Int, y: Int): Unit = {
println(x + y)
}
// 无返回值的函数【简化】
// 省略 `: Unit =`
def sum4(x: Int, y: Int) {
println(x + y)
}
总结:
- 有返回值的函数,在声明时可以省略
:返回值类型
,Scala自动根据类型推导返回值类型- 无返回值的函数,在声明时可以省略
:Unit=
,Scala会自动认为是无返回值的函数
可变长参数
Java 中声明可变长参数
public void m1(String... args)
Scala中声明可变长参数
def 函数名(args*):返回值类型 = {}
// 与Java中一样,声明的可变长参数必须是参数列表中的最后一个参数
def m2(init: Int, args: Int*) = {
var default = init
for (n <- args) {
default += n
}
default
}
参数默认值
Scala中的参数可以有默认值,在使用时:
- 如果调用的时候有传值,使用传递的值进行计算
- 如果调用的时候没有传值,使用声明时的默认值进行计算
声明语法 :
( 参数名:参数类型 = 默认值 )
def sum(x: Int, y: Int, z: Int = 10): Int = {
x+y+z
}
带名参数
带名参数指的是在调用方法时根据参数名传值
语法 :
方法名(参数名称 = 值)
注意 :
- 带名参数使用时传参的顺序可以随意
- 带名参数可以和参数默认值一起使用,即有默认值的参数可以不写
// 声明一个函数
def sum2(x: Int, y: Int = 10, z: Int): Int = {
x + y + z
}
// 调用函数
println(sum2(x = 1, z = 2))
// 或者这样调用 参数的顺序可以随意 同时变量y使用默认值
println(sum2(z = 2, x = 1))
Lazy值( 懒加载 | 懒值 )
Scala中的常量可以被声明为
懒值
,即该变量只有在第一次使用时,才会进行初始化语法 :
lazy 常量的声明
package function
import scala.io.Source._
/**
* 懒加载
*/
object Function04 {
def main(args: Array[String]): Unit = {
// 立即进行初始化 程序直接报错
// val source = fromFile("D://abc.txt")
// 懒加载的值 如果只是声明而不使用 程序不会报错
lazy val source2 = fromFile("D://abc.txt")
// 一旦该值被使用,懒加载的值初始化了 就会报错 因为abc.txt不存在
source2.mkString
}
}
递归函数
// 递归函数 和Java的类似
// Scala中代码块的最后一行就是返回值 因此不需要写return关键字
def m1(n:Int):Int = {
if(n == 1) 1
else n * m1(n - 1)
}
匿名函数
匿名函数就是没有名字的函数
语法 :
参数列表 => 函数体
-
Scala作为一款函数式编程语言 大量使用了匿名函数
-
Scala中内置了23个匿名函数对象
Function0 ~ Function22
,Scala严格遵守面向对象设计模式,因此,每一个匿名函数的背后对应一个函数对象
// 示例
(x:Int,y:Int) => x + y
// 这就是一个Function2[Int,Int,Int]对象 该对象表示接受2个Int类型的参数返回Int类型的值的函数
// 以此类推 :
// Function3 表示接受3个参数的函数对象
// Function4 表示接受4个参数的函数对象
// ...
package function
/**
* 匿名函数
*/
object Function06 {
def main(args: Array[String]): Unit = {
// 匿名函数声明
// Function2[Int,Int,String] 就是该函数对象的类型
// 表名此函数接受2个Int类型的参数,返回String类型的值
val f2: Function2[Int, Int, String] = (x: Int, y: Int) => x + y + " "
// 匿名函数使用
println(f2(1, 2))
println("-------------------------------------")
// 这里声明了一个Function[Int,Unit]类型的函数对象
// 表名此函数接受1个Int类型的参数,返回Unit类型的值(没有返回值)
val f1: Function1[Int, Unit] = (n: Int) => println(n)
val list = List(1, 2, 3, 4, 5)
// 将函数对象作为方法的参数
list.foreach(f1)
println("-------------------------------------")
list
.filter((n: Int) => n != 3) // 保留集合中符合条件的数据
.foreach((n: Int) => println(n)) // 1 2 4 5
}
}
柯理化(Currying)
柯理化函数是指将接受多个参数到函数,改造为接受一个参数的函数,并且返回一个函数对象的过程。
// 普通函数
def sum(x: Int, y: Int) = {
x + y
}
// 柯理化函数
// 该函数的返回值是一个函数对象Function1,也就是函数返回函数
def sum2(x: Int) = {
(y: Int) => x + y
}
// 【简写1】省略括号
def sum2(x:Int) = (y:Int) => x + y
// 【简写2】省略了 `=` 将匿名函数[(y:Int) => x + y] 的连接符 修改为了 = 即可
def sum3(x: Int)(y: Int) = x + y
说明:def sum(x:Int,y:Int) = x + y 接受2个参数 准备完成的计算工作是 x + y
柯理化就是先传入一个参数,使方法中的一个变量的值固定 然后返回一个值固定的函数对象
即 def sum(x:Int) ={ (y:Int) => x + y } 此时调用sum(2) 则返回一个函数对象 (y:Int) => 2 + y
五、数组
数组就是内存中的一段连续的空间,是存储同一类型的数据的集合
数组的声明
// 语法 : val|var arr = Array[泛型](元素列表)
// 示例 :
val arr = Array[Int](1,2,3,4,5)
// Scala中支持类型推断 因此在定义数组时可以不写泛型
val arr = Array(1,2,3,4,5)
数组的使用
- 数组的长度 (length) : 1 - n
- 数组的下标(index): 0 ~ length - 1
package array
object ArrayDemo {
def main(args: Array[String]): Unit = {
// scala数组的声明
val arr = Array[Int](1, 2, 3, 4, 5)
// 遍历方法1
for(n <- 0 until arr.length){
println(arr(n)) // 注意: 获取数组中元素使用的是 数组名(下标)
}
println("-----------------------------------")
// 遍历方法2
for(n <- arr){
println(n)
}
// 修改数组中的元素
arr(3) = 10
println("-----------------------------------")
// 遍历方法3
for(n <- arr.indices){ // indices 获取到的是数组的下标数值区间
println(arr(n))
}
}
}
可边长数组
- Array是定长数组,一旦声明,长度固定不可改变
- ArrayBuffer是可变长的数组,数组会随着元素的增多自动扩容
// 声明
val ab = ArrayBuffer(1,2,3,4,5)
// 添加元素
ab += 6
// 遍历
ab.foreach(println)
Array和ArrayBuffer相互转换
// arrayBuffer ---> array 可变到定长的转换
val arr = ab.toArray
// array ---> arrayBuffer 定长到可变长的转换
arr.toBuffer
多维数组
// 定义
val arr = Array.ofDim[Int](10, 4) // 类似于10行4列的二维表
// 操作
arr(1)(2) = 2
// 遍历
for (m <- 0 to arr.length - 1) {
for (n <- 0 to arr(m).length - 1) {
print(arr(m)(n) + "\t")
}
println()
}
六、类
简单类
语法: class 类名{}
// 声明一个Animals
class Animals{}
// 创建对象
val animals:Animals = new Animals()
// 或
val animals:Animals = new Animals
- 创键的类中默认提供一个无参的主构造器
- 创建对象时如果使用的是无参构造器,括号可以省略
成员和方法
- Scala中定义的成员全部都是
private
修饰的- Scala中成员的getter方法名:
成员名()
- Scala中成员的setter方法名:
成员名_$eq(参数)
class Animals{
// Scala中声明成员的时候必须指定初始值
// 一旦声明了成员后 Scala会自动使用 `private` 修饰变量 ,并且为其提供公开的getter/setter方法
// getter : name()、age()
// setter : name_$eq(name:String)、age_$eq(age:Int)
var name: String = ""
var age: Int = 1
// 声明常量成员时,依然使用 `private` 修饰,并且为其提供公开的getter方法,没有setter方法
val color:String = "白色"
// Scala中可以使用private关键字修饰变量,Scala会为其提供私有的getter/setter方法
private var birthday = new java.util.Date()
// Scala中可以使用private关键字修饰常量,Scala会为其提供私有的getter方法,没有setter方法
private val age = true
// 方法直接定义在类中即可
def run():Unit = {
println("动物在跑~~")
}
}
@BeanProperty
此注解加载成员的前面,Scala编译时,会自动产生Java版本和Scala版本的getter/setter方法
package classes
import scala.beans.BeanProperty
object CourseTest {
def main(args: Array[String]): Unit = {
val chinese = new Chinese
// Java版本的getter/setter方法
chinese.getCourseName
chinese.setCourseName("文言文")
// Scala版本的getter/setter方法
chinese.courseName = "四书五经"
val courseName = chinese.courseName
println(courseName)
}
}
class Chinese {
@BeanProperty var courseName: String = "中文"
}
辅助构造器
辅助构造器,类似于java重载的构造方法
- scala的一个类中,可以有一个主构造器和若干个辅助构造器
- 辅助构造器,使用的关键字
this
- 辅助构造器内,第一行必须调用主构造器或者辅助构造器
// scala默认提供一个无参的主构造器
class Fish {
var name: String = ""
var color: String = ""
// 辅助的构造器
def this(name: String) {
this() // 辅助构造器必须以调用一个主构造器或者辅助构造器开始
this.name = name
}
def this(name: String, color: String) {
this(name)
this.color = color
}
}
// 调用
val fish1 = new Fish() // 调用无参数的主构造器
val fish2 = new Fish("金鱼") // 调用无参数的主构造器
val fish3 = new Fish("金鱼", "黄色") // 调用无参数的主构造器
主构造器
- 主构造器的参数列表直接写在类名之后:`class Fish(var name:String,var color:String){}
- 主构造器的方法体,是整个类,也就是类的
{}
的所有内容,注意主构造器方法体中的成员和方法仅仅是声明,并不会执行- 主构造器保证创建对象时主构造器定义的变量一定能够被初始化
// 主构造器
class Panda(var name: String, var color: String) {
var age: Int = 0
// 方法体
println("before")
def sayHello(str: String): String = {
str + name
}
println("after")
}
七、对象
对象的作用是代替
Java
中static
关键字的用法,因为Scala没有static
关键字,所以如果需要定义静态成员或者方法,使用object
类型
单例对象
不管创建多少次,只存在一个对象实例
使用
object
修饰的类即为单例对象,类中的所有成员和方法都是静态的,全类共享,可直接通过类名访问
package objectes
/**
* scala中一个类,类型为object,此类即为单例对象
*/
object SingletonObject {
// 静态属性
var id: Int = 0
// 静态方法
def incrementId(): Int = {
this.id = this.id +1
this.id
}
def main(args: Array[String]): Unit = {
val s1 = SingletonObject
val s2 = SingletonObject
val s3 = new SingletonObject()
val s4 = new SingletonObject()
println(s1 == s2) //true【单例】
println(s3 == s4) //false【多例】
val id1 = s1.incrementId()
val id2 = s2.incrementId()
println(id1+"\t"+id2) // 2 2
}
}
class SingletonObject {}
伴生对象&伴生类
在同一个源文件中,如果class对象和obejct对象的名字相同,那么class类称为伴生类,object对象称为伴生对象
// 伴生类
class Person {
def sayHi(): String = {
"Hi:" + Person.name + "\t" + Person.id
}
}
// 伴生对象
object Person {
val id: Int = 0
private val name: String = "zs"
def main(args: Array[String]): Unit = {
val person = new Person
println(person.sayHi())
}
}
apply & unapply
apply方法是object类型中的一个方法,通常该方法作为该类的工厂方法,用来创建对象
即 : 调用伴生对象中apply方法,用于创建伴生类对象
package objectes
class Cat(var name: String, var color: String) {}
object Cat {
// apply方法创建伴生类对象 该方法通常定义再object中
def apply(name: String, color: String): Cat = new Cat(name, color)
def main(args: Array[String]): Unit = {
// 普通方法创建Cat对象
val cat = new Cat("小花", "white")
// apply 工厂方法,用以创建类的对象,通常定义在伴生对象中
// 创建伴生类对象时【简化写法】
val cat2 = Cat("小黑","black") // 等价于Cat.apply("小黑","black")
val cat3 = Cat("小黑","black") // 等价于Cat.apply("小黑","black")
println(cat2 == cat3) // false
}
}
unapply方法也是object类型中的一个方法,作用于apply方法相反,用于解构对象
即将对象的各个属性赋值给新的变量,供其他地方使用
package objectes
class Cat(var name: String, var color: String) {
}
object Cat {
// apply方法创建伴生类
def apply(name: String, color: String): Cat = new Cat(name, color)
// 将伴生类对象 拆箱为成员
// Option 【避免出现空指针异常,有值返回 Some 无值返回 None】
// - None
// - Some
def unapply(arg: Cat): Option[(String, String)] = {
if (arg == null) None
else Some((arg.name, arg.color)) //(name,color) scala元组,可以存放任意类型的list集合 类似于java中的ArrayList[Object]
}
def main(args: Array[String]): Unit = {
// 调用apply方法创建Cat对象
val cat = new Cat("小花", "white")
// ================================================
// unapply方法应用
val Cat((name, color)) = cat
// 相当于:
// Cat.unapply(cat3) = Some(cat3.name,cat3.color)
// val name = cat3.name
// val color = cat3.color
println(name + "\t" + color)
}
}
型中的一个方法,作用于apply方法相反,用于解构对象
即将对象的各个属性赋值给新的变量,供其他地方使用
package objectes
class Cat(var name: String, var color: String) {
}
object Cat {
// apply方法创建伴生类
def apply(name: String, color: String): Cat = new Cat(name, color)
// 将伴生类对象 拆箱为成员
// Option 【避免出现空指针异常,有值返回 Some 无值返回 None】
// - None
// - Some
def unapply(arg: Cat): Option[(String, String)] = {
if (arg == null) None
else Some((arg.name, arg.color)) //(name,color) scala元组,可以存放任意类型的list集合 类似于java中的ArrayList[Object]
}
def main(args: Array[String]): Unit = {
// 调用apply方法创建Cat对象
val cat = new Cat("小花", "white")
// ================================================
// unapply方法应用
val Cat((name, color)) = cat
// 相当于:
// Cat.unapply(cat3) = Some(cat3.name,cat3.color)
// val name = cat3.name
// val color = cat3.color
println(name + "\t" + color)
}
}