scala
Scala
默认不需要语句终结符”;“,它将每一行作为一个语句。如果一行放多条语句,则要使用语句终结符。
变量
可变变量,使用 var
定义;不可变变量,使用 val
定义。可以手动指定,不指定时自动推导。
var a = 2;
val b = 4;
var c: Int = 1;
数据类型
基本数据类型: Byte、 Char、 Short、 I nt、 Long、 Float、 Double、 Boolean
增强版数据类型: StringOps、 RichInt、 RichDouble、 RichChar 等...
操作符与java类似,但没有 ++ --
选择
Scala 中 if 表达式有返回值,就是if或else中最后一行返回的值。所以if表达式可以赋值给一个变量。
var res = if (true) 1 else 0;
// 当if与else返回不同类型数据时,变量自动推到为二者的公共父类。这里res为Any类型
var res = if (age > 18) "old" else 0;
// 选择语句可能返回空,此处res类型也为Any
var res = if (age > 18) "old"
循环
for while
val n = 10
for(i <- 1 to n) { // <- 表示迭代右侧列表[1,10]
print(i) // 打印不输出换行符
println(i) // 打印并输出换行符
}
for (i <- 1 until n) print(i) // [1, 9]
for (i <- "hello scala") print(i) // 迭代字符
var n = 10
while (n > 10) {
println(n)
n -= 1
}
高级for循环
if 守卫模式
for (i <- 1 to 10 if i % 2 == 0) // 只输出偶数
println(i)
for 推导式
// 返回一个Vector(2,4,6,8,10,12,14,16,18,20)
var res = for (i <- 1 to 10) yield i * 2
集合
分可变集合(scala.collection.mutable)、不可变集合(scala.collection.immutable)(默认使用不可变的,默认导入不可变类型集合的包)
HashSet、SortedSet 都存在可变和不可变类型,LinkedHashSet 只有可变的
val s = Set(1,2,3)
// s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
// s += 4 // error
val s = scala.collection.mutable.Set(1,2,3) // 可变 Set
s += 4 // 正常
val s = scala.collection.mutable.HashSet[Int]()
s += 1 // Set(1)
s += 2 // Set(1,2)
s += (3) // Set(2,1,3)
for (i <- s) println(i)
List 只有不可变,有 head tail :: 三个特殊操作
val l = List(1,2,3,4)
l.head // 1
l.tail // List(2,3,4)
l.head :: l.tail // List(1,2,3,4)
ListBuffer 可以支持动态增加或移除元素
val lb = scala.collection.mutable.ListBuffer[Int]()
lb += 1
lb += 2 // ListBuffer(1,2)
ln -= 2 // ListBuffer(1)
Map (SortedMap只有不可变类型)
val ages = Map("jack" -> 20, "tom"->21) // 默认不可变类型
ages("jack") // 20
val ages = scala.collection.mutable.Map(("jack", 20), ("tom", 21)) // 另一种初始化方式
ages("jack") = 21 // 更新值
ages += ("hehe"->12, "haha"->21) // 添加新元素
ages -= "hehe"
ages("xxx") // 查找不存在key时抛出异常
val age = ages.getOrElse("jack", 0)
// 等价于下式
val age = if(ages.contains("jack")) ages("jack") else 0
for ((key, value) <- ages) // 存在 ages.keySet ages.values
println(key + "" + value)
Array 长度不可变,底层就是java的数组,初始化后长度就固定下来了, 元素全部根据其类型初 始化, 也可以直接使用Array()创建数组, 元素类型自动推断
val a = new Array[Int](5)
// Array(0,0,0,0,0)
a(0) = 1
val a = Array("Hello", "scala")
a.sum
a.max
a.min
scala.util.Sorting.quickSort(a) // a被快速排序
ArrayBuffer 长度可变
val b = ArrayBuffer[Int]()
b += 1
b += (2,3,4,5)
b.insert(3,30) // ArrayBuffer(1,2,3,30,4,5)
b.remove(1) // ArrayBuffer(1,3,30,4,5)
b.toArray() // 转为 Array
a.toBuffer() // Array 转 ArrayBuffer
Tuple 元组,与Array类似,都是不可变。但是它可以包含不同类型的元素,Array不行;Array可更新内部值,Tuple不可以更新。元组脚标从1开始
val tup = Tuple3(1, 2.2, "str") // Tuple后的数字必须和元素长度一致
val t = (1, 3.14, "string")
t._1 // 1
t._3 // string
函数
要求必须给出所有参数类型,但返回值类型不是必须的,因为可以自动推断。不需要return,最后一行语句就是返回值。
定义:
def sayHello(name: String) = print("hello" + name)
def sayHello(name: String, age: Int) = {
println("name:" + name + "age:" + age)
age // 即为返回值
}
参数:
- 默认参数: 有时我们调用某些函数时, 不希望给出参数的具体 值, 而希望使用参数自身默认的值
- 带名参数: 在调用函数时, 也可以不按照函数定义的参数顺序 来传递参数, 而是使用带名参数的方式来传递
- 可变参数: 有时我们需要将函数定义为参数个数可变的形式, 则此时可以使用可变参数定义函数
def sayHello(fName: String, mName: String = "mid", lName: String = "last") = {}
sayHello("fName") // 只需传入一个参数就可以运行
sayHello(fName="jack", lName="Tom", mName="Mick") // 使用参数的名字指定参数
def sum(nums: Int*) = {
var res = 0
for (num <- nums)
res += num
res
}
过程
特殊的函数,定义函数时, 如果函数体直接在花括号里面而没有使用=连接, 则函数的返回值类型就是Unit, 这样的函数称之为过程
过程通常用于不需要返回值的函数
过程还有一种写法, 就是将函数的返回值类型显式定义为Unit
// 函数,返回值为 "hello " + name
def sayHello(name: String) = "hello " + name
def sayHello(name: String): String = "hello " + name
// 过程,没有返回值
def sayHello(name: String) { "hello " + name }
def sayHello(name: String): Unit = "hello " + name
lazy
Scala提供了lazy特性, 如果将一个变量声明为lazy, 则只有 在第一次使用该变量时, 变量对应的表达式才会发生计算
适用于打开文件、获取链接等耗时操作。
lazy val lines = fromFile("D://test.txt").mkString // 这条命令用于打开文件,只有使用lines变量时才会真正打开文件
面向对象
类 class
class Person {
var name = "scala"
// 定义时有括号,调用时必须有括号(不同版本可能不同)
def sayHello() = {
println("hello "+ name)
}
// 定义时没括号,调用时也没括号
def getName = name
}
val p = new Person()
p.sayHello()
p.getName
构造函数
- 主constructor: 类似Java的默认构造函数 this()
class Student(val name: String = "none", val age: Int = 0) { // 这个大括号内就是主构造函数 println("This is main constructor") }
- Scala的主constructor是与类名放在一起的, 与Java不同
- 注意: 在类中, 没有定义在任何方法或者是代码块之中的代码 就是主constructor的代码
- 主constructor中可以通过默认参数, 来给参数设置默认值
- 辅助constructor: 类似Java的重载构造函数 this(name,age)
class Student{ var name = "jack" // 此处即为主构造函数 var age = 10 def this(name: String) = { // 辅助构造函数,必须调用主构造函数 this() this.name = name } def this(name: String, age: Int) = { // 辅助构造函数,调用其他辅助构造函数,间接调用主构造函数 this(name: String) this.age = age } }
- Scala可以给类定义多个辅助constructor, 类似于Java中的 构造函数重载
- 辅助constructor可以互相调用, 第一行必须调用主constructor
对象 object
- java中object是new出来的,scala中可以new也可以自己定义。
- 相当于class的单个实例, 通常在里面放一些静态的 field或者method
- object不能定义带参数constructor, 只有空参constructor
- 第一次调用object的方法时, 会执行object的constructor 仅执行一次
- object通常作为单例模式的实现, 或者放class的静态成员
- object不能被new, 可以直接使用
// 定义 object
object person {
var age = 1
println("main constructor")
def getAge = age
}
// 使用
person.age // 第一次调用会调用主构造方法
person.getAge
伴生对象
- 如果有一个class, 还有一个与class同名的object, 那么就称 这个object是class的 伴生对象 , class是object的 伴生类
- 伴生类和伴生对象必须存放在一个.scala文件之中
- 伴生类和伴生对象最大特点在于可以互相访问private field
object Person {
var age = 1
private val money = 1 // 私有变量
println("main constructor")
def getAge = age
}
class Person(name: String = "", age: Int = 0) {
def sayHello = {
println("name: " + name + " age: " + age + " money: " + Person.money) // 此处调用了伴生对象的私有变量
}
}
apply
- apply是object中非常重要的一个特殊方法, 通常在伴生对象 中实现apply方法, 并在其中实现构造伴生类对象的功能
- 在创建对象时, 就不需要使用new Class的方式, 而是使用 Class()的方式, 隐式调用伴生对象的apply方法, 这样会让 对象创建更加简洁
class Person(val name: String) {
println("name is : " + name)
}
object Person{
def apply(name: String) = {
println("apply exec...")
new Person(name)
}
}
Person("Tom") // 此处调用 object Person 中的apply方法,返回一个 new Person(name)
main
- 和Java一样, 在Scala中如果要运行一个应用程序, 必须 有一个main方法,作为入口
- Scala中的main方法必须定义在object中, 格式为:
def main(args: Array[String])
package org.example
object mainDemo {
/**
* main 方法只能定义再object中,不能定义在class中
*/
def main(args: Array[String]):Unit = {
println("hello scala")
}
}
接口 trait
- Scala中的trait类似于Java中的interface
- 在triat中可以定义抽象方法
- 类可以使用 extends 关键字继承trait, 无论继承类还是trait 统一都是extends
- 类继承trait后, 必须实现trait中的抽象方法, 实现时不需要使 用override关键字
- scala不支持对类进行多继承, 但是支持对trait进行多重继承, 使用with关键字即可
package org.example
object PersonDemo {
def main(args: Array[String]): Unit = {
val p1 = new Person("tom")
val p2 = new Person("jack")
p1.sayHello(p2.name)
p1.makeFriend(p2)
}
}
// 定义接口
trait HelloTrait {
def sayHello(name: String):Unit
}
trait FriendTrait {
def makeFriend(p: Person): Unit
}
// 继承多个接口
class Person(val name: String) extends HelloTrait with FriendTrait {
override def sayHello(name: String): Unit = { // 重写函数
println("hello " + name)
}
override def makeFriend(p: Person): Unit = {
println("make friend with " + p)
}
}
case class 样例类
- 称为样例类, 类似于Java中的JavaBean, 只定 义field, Scala自动提供getter和setter方法, 没有method
- case class的主构造函数接收的参数通常不需要使用var或val 修饰Scala会自动使用val修饰
- Scala 自动为 case class定义了伴生对象, 也就是object, 并 且定义了apply()方法, 该方法接收主构造函数中相同的参数 并返回case class对象
函数式编程
Scala是一门既面向对象, 又面向过程的语言
在Scala中, 函数与类、 对象一样, 都是一等公民
函数赋值给变量
- Scala中的函数是一等公民, 可以独立定义, 独立存在, 而且 可以直接将函数作为值赋值给变量
- Scala的语法规定: 将函数赋值给变量时, 必须在函数后面加 上空格和下划线 (在scala3中不是必须)
def say(name: String) = { println("hello" + name) }
val sayFunc = say
val sayFunc2 = say _
// 以下三者效果相同,都能得到 helloxxx
say("xxx")
sayFunc("xxx")
sayFunc2("xxx")
匿名函数
- Scala中的函数也可以不需要命名, 这种函数称为匿名函数
- 匿名函数的语法格式: (参数名: 参数类型) => 函数体
- 可以将匿名函数直接赋值给某个变量
val sayFunc = (name: String) => println("hello " + name)
val sayFunc = (name: String) => {
println("hello " + name)
}
高阶函数
- 由于函数是一等公民, 所以我们可以直接将某个函数作为参 数传入其它函数
- 接收其它函数作为当前函数的参数, 当前函数也被称作高阶 函数 (higher-order function)
- 高阶函数可以自动推断出它里面函数的参数类型,对于只有 一个参数的函数, 还可以省去小括号
val sayFunc = (name: String) => println("hello " + name)
def exec(func: (String)=>Unit, name: String) = {
func(name)
}
exec((name: String) => println("hello " + name), "xxx") // 直接将匿名函数当作参数
exec(name => println("hello " + name), "xxx") // 匿名函数的参数类型可以自动推导,只有一个参数时可以省略小括号
exec(sayFunc, "xxx")
// hello xxx
常用高阶函数
- map 一进一出
Array(1,2,3,4,5).map(num => num*2) Array(1,2,3,4,5).map(_ * 2) // 简写形式 // Array[Int] = Array(2, 4, 6, 8, 10)
- flatMap 一进多出
Array("hello world", "hello scala").flatMap(line => line.split(" ")) // Array(hello, world, hello, scala)
- foreach 迭代
Array(1,2,3,4,5).foreach(println(_)) // 简写形式
- filter 过滤
Array(1,2,3,4,5).filter(num => num % 2 == 0) Array(1,2,3,4,5).filter(_ % 2 == 0) // Array(2,4)
- reduceLeft 聚合操作
Array(1,2,3,4,5).reductLeft((t1, t2) => t1 + t2) Array(1,2,3,4,5).reductLeft(_ + _) // 简写,只有逻辑特别简单时才能使用 // 15
高级特性
模式匹配
- Scala没有Java中的switch case语法, 但是, Scala提供了更 加强大的match case语法, 即模式匹配
- Java的switch case仅能匹配变量的值, Scala的match case可 以匹配各种情况, 比如: 变量的类型、 集合的元素、 有值没值
- 格式:变量 match { case 值 => 代码 }
变量值匹配
def func(day: Int) = {
day match {
case 1 => println("Monday")
case 2 => println("Tuesday")
case 3 => println("Wednesday")
case _ => println("other")
}
}
变量类型匹配
def func(e: Exception) = {
e match{
case e1: FileNotFoundException => println("FileNotFoundException")
case e2: IOException => println("IOException")
case _: Exception => println("Exception")
}
}
try{
val lines = scala.io.Source.fromFile("D://testxxx.txt").mkString
} catch {
case ex: FileNotFoundException => println("no file")
case ex: IOException => println("io exception")
case ex: Exception => println("exception")
}
case class 与模式匹配
class Person
case class Teacher(name: String, sub: String) extends Person
case class Student(name: String, cla: String) extends Person
def check(p: Person) = {
p match {
case Teacher(name, sub) => println("teacher: name is " + name + " sub is " + sub)
case Student(name, cla) => println("student: name is " + name + " cla is " + cla)
case _ => println("other")
}
}
Option与模式匹配
- Option有两种值, Some->表示有值, None->表示没有值
- Option通常会用于模式匹配中, 用于判断某个变量是有值还 是没有值, 这比null更加简洁明了
val m = Map(("a",1)) // Map[String, Int] = Map(a -> 1) m.get("a") // Option[Int] = Some(1) m.get("b") // Option[Int] = None // 使用map的get方法得到的就是Option对象,通过判断是Some还是None得知是否有值
val m = Map(("a",1))
def getNum(str: String) = {
val num = m.get(str)
num match {
case Some(num) => println("get num: " + num)
case None => println("None")
}
}
隐式转换
- Scala的隐式转换, 允许手动指定将某种类型的对象转换成其 它类型的对象
- Scala的隐式转换, 最核心的就是定义隐式转换函数, 即 implicit conversion function
- 隐式转换函数与普通函数唯一的语法区别是要以implicit开 头而且最好要定义函数返回类型
Scala默认会使用两种隐式转换
- 源类型, 或者目标类型的伴生对象里面的隐式转换函数
- 当前程序作用域内可以用唯一标识符表示的隐式转换函数
如果隐式转换函数不在上述两种情况下的话, 那么就必须手动 使用import语法引入某个包下的隐式转换函数
建议: 仅仅在需要进行隐式转换的地方
class Cat(val name: String) {
def catchMouse() = {
println(name + " catch mouse")
}
}
class Dog(val name: String)
implicit def object2Cat(obj: Object): Cat = {
if (obj.getClass == classOf[Dog]) {
val dog = obj.asInstanceOf[Dog]
new Cat(dog.name)
}
else Nil
}
val d = new Dog("狗")
d.catchMouse() // 隐式转换调用方法
// 狗 catch mouse