Scala

概述

概述

  • 体现了面向对象,函数式编程等多种不同的语言范式,且融合了不同语言新的特性
  • scala语言是基于java语言开发的,所以大部分的java代码可以直接在scala中使用
  • 代码可以不需要分号结尾: scala推荐一行代码中只有一个逻辑,那么分号可以省略

入门案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package cc.mousse

/*
package:java中的包

object:声明一个对象,在编译时会编译为两个类文件
这个声明的对象的类型为:当前类型$
Java:HelloWorld helloWorld = new HelloWorld();
Scala:HelloWorld = new HelloWorld$()

HelloWorld:对象名称

def:声明方法的关键字

main:scala程序的入口方法名

():方法参数列表

args: Array[String]:参数
Java:String[] args //Java是一个强类型的语言
Scala:args: Array[String]

args:参数名
Array[String]:参数类型
::分隔符

Array[String]:Array是一个数组类型
Scala语言是一个完全面向对象的语言,所以万物皆对象
数组也是对象,也有自己的类型:Array,这里的中括号表示泛型
在Java中以String[]数组为例,[]为数组的类型,String为数组中的元素类型

: Unit:Unit表示返回值类型,Unit类型是scala中新的类型,为了代替void关键字,表示没有返回值
Scala格式:名称: 类型
参数名: 参数类型
变量名: 变量类型
方法名: 方法的返回值类型

=:赋值
Java中赋值不统一问题
User user = new User();
user.xxx;

public void test() = {
System.out.println("Hello Scala World")
}

test();

*/
object HelloWorld {

def main(args: Array[String]): Unit = {
System.out.println("hello world!")
println("scala")
}

}

变量/数据类型

变量

概念

  • 够通过取值推断变量的类型,那么变量类型可以省略

  • 如果使用多态,那么类型不能省略

    1
    var name3 = "Tom";
  • Java语法中变量在使用前进行初始化就可以,但Scala必须显示进行初始化操作

可变变量

1
2
// String name1 = "Tom";
var name1: String = "Tom"

不可变变量

1
2
// final String name2 = "Tom";
val name2: String = "Tom"

标识符

概念

  • Scala中的标识符可以用于声明运算符

    1
    2
    3
    4
    5
    val + = "zhangsan"
    val @@ = "zhangsan"
    val :: = "zhangsan"
    // 颜文字
    val :-> = "lisi"
  • 在编译时,将特殊符号编译为转换的变量名,这个变量名以$开头的

  • 一般情况下,标识符起名时不能使用$开头

  • 可以用反引号使用关键字

    1
    2
    println(`private`)
    Thread.`yield`()

字符串

概念

  • 在Scala中,字符串的类型实际上就是Java中的String类

拼接

  • 方式1:字符串拼接,+

    1
    println("name: " + name)
  • 方式2:传值字符串,%s

    1
    printf("name: %s\n", name)
  • 方式3:插值字符串,s"$”

    1
    2
    println(s"name: $name")
    println(s"name: ${name.substring(0)}")

多行字符串

  • 默认竖线代表顶格符,可通过stripMargin('')改变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    val str1 =
    """
    #Hello
    #Scala
    #""".stripMargin('#')
    println(str1)
    val sql =
    """
    | select
    | id
    | from
    | (
    | select
    | *
    | from t_user
    | where id = 1
    | order by id desc
    |
    | ) a
    | group by id
    |
    |""".stripMargin

输入输出

控制台

  • read:控制台读取

    1
    val str: String = StdIn.readLine();

文件

  • 数据源

    • 绝对路径:不可改变的路径
    • 本地路径:file:///c:/test/test.txt
    • 网络路径:http://www.xxx.com
    • 相对路径:可以改变的路径,一定存在一个基准路径
    • IDEA中基准路径为项目的根路径
  • Scala进行文件读写操作,用的都是Java中的I/O类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    val source: BufferedSource = Source.fromFile("data/world.txt")
    val strings: Iterator[String] = source.getLines()
    while (strings.hasNext) {
    println(strings.next())
    }
    source.close()

    val writer = new PrintWriter(new File("data/test.txt"))
    writer.write("Hello World!")
    writer.close()

网络

  • C/S模拟

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    object SlaverObject {

    def main(args: Array[String]): Unit = {
    // 启动服务器
    val server = new ServerSocket(9999)
    println("server started")
    // 接收客户端的连接请求,会阻塞
    val client = server.accept()
    println("connected")
    // 接收信息
    val in = new ObjectInputStream(client.getInputStream)
    val user = in.readObject()
    println(s"get message from client: $user")
    // 关闭流
    in.close()
    client.close()
    server.close()
    }

    }


    object MasterObject {

    def main(args: Array[String]): Unit = {
    // 连接服务器
    val client = new Socket("localhost", 9999)
    // 发送数据
    val out = new ObjectOutputStream(client.getOutputStream)
    val user = new User("Tom", 23)
    out.writeObject(user)
    println(s"send message to server: $user")
    // 关闭流
    out.flush()
    out.close()
    client.close()
    }

    }

数据类型

概念

  • Scala是完全面向对象的语言,所以不存在基本数据类型的概念,有的只是任意值对象类型AnyVal和任意引用对象类型AnyRef

继承关系

graph TD
Any --> AnyVal
Any --> AnyRef

AnyVal --> Double --> Nothing
AnyVal --> Float --> Nothing
AnyVal --> Long --> Nothing
AnyVal --> Int --> Nothing

Double -..-> Float -..-> Long -..-> Int
Int -..-> Short -..-> Byte
Int -..-> Char

AnyVal --> Short --> Nothing
AnyVal --> Byte --> Nothing
AnyVal --> Boolean --> Nothing
AnyVal --> Char --> Nothing
AnyVal --> StringOps --> Nothing
AnyVal --> Unit --> Nothing
AnyRef --> ScalaCollections --> Null
AnyRef --> AllJavaClasses --> Null
AnyRef --> OtherScalaClasses --> Null
Null --> Nothing
  • Any:通用类型

    1
    var a: Any = "123"
  • Nothing:用于统一方法的异常和正常的返回

    1
    2
    3
    def test(): Nothing = {
    throw new Exception()
    }
  • Unit:Unit是一个类型,这个类型只有一个对象,打印就是小括号

    1
    2
    val u: Unit = testUnit()
    def testUnit(): Unit = {}
  • (Scala collections):Scala集合类型

    1
    val list: AnyRef = List(1, 2, 3, 4)
  • Null:在scala中是一个类型,只有一个对象,就是null

    1
    2
    val n = null
    val user: User = null
  • (other Scala classes):Scala其他类型

    1
    val obj1: AnyRef = DataTypeDemo
  • (all Java classes):Java所有类型

    1
    val obj: AnyRef = new User()

隐式转换

  • 编译器将类型进行了转换,所以可以编译通过,这个转换开发人员看不见,将这样的转换称之为隐式转换

  • 常量的运算在编译期

    1
    val aChar: Char = 'a' + 1
  • 变量的运算在运行时计算,类型转换错误会报

    1
    2
    3
    // 进行加法运算时byte会提升成int,int不能转为char
    val aByte: Byte = 'a'
    val aChar: Char = aByte + 1
  • 所有的类型都可以转换为字符串

    1
    2
    val b1 : Byte = 20
    println(b1.toString)
  • 字符串也可以转换为数值

    1
    2
    val s = "123"
    val i1 = s.toInt

运算符

关系运算

==

  • Scala语法中双等号就是比较对象的内容,是非空equals操作,和equals不一样

    1
    2
    3
    4
    5
    6
    7
    val s1 = new String("abc")
    val s2 = new String("abc")
    // s1 = null, s2 != null:false
    // s1 = null, s2 = null:true
    println(s1 == s2)
    // s1 = null:报错
    println(s1.equals(s2)
  • eq方法在编译后就是Java中的双等号,对引用类型来说就是地址的比较

    1
    println(s1.eq(s2))
1
2
3
4

val s = "abc" * 2
// abcabc
println(s)

赋值运算

+=

  • ++运算有歧义,容易理解出现错误,所以scala中没有这样的语法,所以采用+=的方式来代替

运算符本质

概念

  • 在Scala中其实是没有运算符的,所有运算符都是方法(可以自定义方法)

    1
    2
    // abcabc
    val a = "abc" * 2
  • Scala是一个完全面向对象的语言,数字也是对象

    • 方法在调用时,可以省略点
    • 方法如果参数只有一个或没有,那么小括号可以省略

流程控制

分支控制

概念

  • 没有三元运算符,使用if分支判断替代

  • 表达式都有返回值,返回结果为表达中满足条件的最后一行代码的执行结果

    1
    2
    3
    4
    5
    6
    7
    val res = if (age == 30) {
    30
    } else {
    null
    }
    // 30
    println(res)

循环控制

使用方法

  • 方法1:START to END by STEP

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // for (int i = 1; i <= 5; i++)
    // 1 to 5 = 1.to(5)
    // for (i: Int <- 1.to(5))
    for (i <- 1 to 5) {
    println(i)
    }
    // for (int i = 1; i <= 5; i + 2)
    for (i <- 1 to 5 by 2) {
    }
  • 方法2:Range(START, (END + 1), STEP)(不包含参数2)

    1
    2
    3
    val range = Range(1, 5, 2)
    for (i <- Range(1, 5, 2)) {}
    for (i <- Range(5, 1, -1)) {}
  • 方法3:START until (END + 1) by STEP

    • 与Range效果相同

循环守卫

  • 满足条件才会进入循环体
1
2
3
4
5
6
7
8
9
10
// 循环体进入次数:3
for (i <- Range(1, 5) if i != 3) {
println("i = " + i)
}
// 循环体进入次数:4
for (i <- Range(1, 5)) {
if (i != 3) {
println("i = " + i)
}
}

循环嵌套

1
2
3
4
5
6
7
8
9
for (i <- Range(1, 5); j <- Range(1, 4)) {
println("i = " + i + ",j = " + j)
}

for (i <- Range(1, 5)) {
for (j <- Range(1, 4)) {
println("i = " + i + ",j = " + j)
}
}

引入变量

1
2
3
4
5
6
7
8
9
for (i <- Range(1, 5); j = i - 1) {
println("j = " + j)
}

// 单层for循环打印三角
val n = Integer.parseInt(StdIn.readLine())
for (i <- Range(1, n + 1, 2); j = (n - i) >>> 1) {
println(" " * j + "*" * i)
}

返回值

  • 返回的是集合类

  • yield:每一次的循环结果保存下来

    1
    2
    3
    4
    5
    val res = for (i <- Range(1, 5)) yield {
    i
    }
    // Vector(1,2,3,4)
    println(res)

跳出循环

  • 没有continue/break关键字,采用面向对象的方式来代替

  • 采用抛出异常的方式来跳出循环,需使用Breaks.breakable()捕捉异常使其不抛出异常

    1
    2
    3
    4
    5
    6
    7
    8
    Breaks.breakable(){
    for (i <- Range(1, 5)) {
    if (i == 3) {
    Breaks.break()
    }
    }
    }
    println("there")

函数式编程

定义

方法

  • 方法就是函数
  • 功能的封装,强调功能的从属关系
  • 类中声明的函数叫方法
  • 可以重写重载

函数

  • 功的封装,不强调功能的从属关系
  • 在方法或函数中
  • 函数作用域比较窄
  • 编译后,函数会被编译为一个新的方法private static final Unit test$1()
  • 不可以重写重载
  • 能嵌套
  • 如果函数名称和方法名称相同,那么调用时默认为函数调用

声明方法

  • def 函数名(参数1:参数类型1, 参数2:参数类型2):函数返回值类型 = {函数体}

参数

可变参数

  • 可变参数在使用时都是集合对象
  • 可变参数和参数默认值是不能联合声明
  • 参数类型后面增加星号
1
2
3
4
5
def fun2(name: String*): Unit = { }
// 一个参数都没有 : List()
fun2()
// 一或多个参数 WrappedArray(zhangsan, lisi, wangwu)
fun2("zhangsan", "lisi", "wangwu")

默认参数

  • 底层就是编译为一个方法,不传参数时由编译器自动调用这个方法
  • 函数的参数默认以val声明,意味着不能改
  • 可变参数和参数默认值是不能联合声明
1
def fun4(password: String = "000000"): Unit = { }

带名参数

  • 传递参数时,增加参数的名称,用于改变传参的顺序
  • 参数在传递时默认为顺序匹配,可以通过特殊的语法改变传值的顺序
1
2
def fun4(password: String = "000000", name: String): Unit = { }
fun4(name = "zhangsan")

至简原则

概念

  • 函数体会将满足条件的最后一行的代码的执行结果作为函数的返回值

    1
    2
    3
    def fun(): String = {
    return "lisi"
    }
  • 如果函数返回数据,那么可以推断出返回值类型的话,返回值类型可以省略

    1
    2
    3
    def fun() = {
    return "lisi"
    }
  • 如果函数体的逻辑代码只有一行的,那么大括号可以省略

    1
    def fun() = "lisi"
  • 如果函数的参数列表中没有声明任何的参数,那么参数列表可以省略

    1
    def fun = "lisi"
  • 当函数省略参数列表的声明时,调用这个函数不能增加小括号

    1
    println(fun)
  • 如果函数明确声明为Unit,那么函数体中的return关键字不会被返回

  • 如果函数体中使用return返回结果,那么一定要声明返回值类型

    1
    2
    3
    4
    def fun(): Unit = {
    // 不会报错
    return "zhangsan"
    }
  • 如果希望省略Unit,但同时又不希望函数体中的return起作用,那么可以将等号同时省略,称为过程函数

    1
    2
    3
    def fun() {
    return "zhangsan"
    }
  • 关键字def和函数名也可以省略,称之为匿名函数,匿名函数不能独立使用

函数返回

作为值

  • 函数也是对象也有对象类型

  • 若将函数作为整体,而不是执行结果赋值给变量,那么需要采用特殊符号:下划线

    1
    2
    3
    4
    def fun(): Unit = {
    println("this is function1")
    }
    val val = fun _
  • 函数独立使用时,参数声明没有个数限制

  • 将函数作为对象给别人使用,那么函数的参数声明的个数最多为22个

    • 调用方式1

      1
      2
      // 0为参数个数
      val val1: Function0[Unit] = fun1
    • 调用方式2

      1
      2
      3
      val val3: () => Unit = fun1
      val val4: String => Unit = fun2
      val val5: (String, String) => Unit = fun3
  • 之所以使用下划线让函数作为对象使用,因为代码中没有明确变量的类型,所以需要通过取值类推断,如果变量声明的类型为函数类型,那么可以不使用下划线让函数作为对象

作为参数

  • 匿名函数

    • 如果函数体的逻辑代码只有一行,大括号可以省略,代码写在一行中

    • 如果参数的类型可以推断出来,那么参数类型可以省略的

      1
      fun5((x, y) => x + y)
    • 如果参数只有一个的话,参数列表的小括号可以省略

    • 如果参数在使用时,按照顺序只使用了一次,那么可以使用下划线代替参数

      1
      fun5(_ + _)
    • 使用匿名函数时,给定的参数直接放回,不能使用下划线代替,必须完整

作为返回值

  • 一般应用于将内部的函数在外部使用,这种方式不推荐自己定义类型
1
2
3
4
5
6
7
8
9
def outerFun() = {
def innerFun(): Unit = {
println("this is inner function")
}

innerFun _
}

outerFun()()

抽象控制

  • 参数类型不完整,那么在传递参数时,也是不完整:只有传递代码就可以,不需要完整的声明
  • 可以采用控制抽象设计语法
1
2
3
4
def fun6(op: => Unit): Unit = {
op
}
fun6(println("this is function6"))

闭包

  • 概念
    • 将当前的代码形成了一个闭合的环境,这个环境称之为闭包环境,简称为闭包
    • 一个函数使用了外部的变量,把这个变量包含到了它的内部来使用,改变了这个变量的生命周期
  • 版本区别
    • Scala2.12版本前闭包功能采用的是匿名函数类型实现
    • Scala2.12版本闭包功能采用的是更改函数声明实现
  • 出现位置
    • 内部函数在外部使用时会有闭包
    • 将函数作为对象使用,会有闭包
    • 所有的匿名函数都有闭包
1
2
3
4
5
6
7
def outerFun1(x: Int) = {
def innerFun1(y: Int) = {
x + y
}
innerFun1 _
}
println(outerFun1(1)(2))

柯里化

  • 将无关的参数分离开
1
2
3
4
5
6
7
8
9
10
11
def test(a: Int, b: Int): Unit = {
for (i <- 1 to a) println(i)
for (j <- 1 to b) println(j)
}
test(1, 2)

def test1(a: Int)(b: Int): Unit = {
for (i <- 1 to a) println(i)
for (j <- 1 to b) println(j)
}
test1(1)(2)

递归

  • scala中要求递归函数必须明确声明返回值类型
  • 函数内部调用自身
  • 一定要有跳出递归的逻辑
  • 递归函数在调用时传递的参数之间有关系

伪递归/尾递归

  • Scala中尾递归不是真正的递归,是由编译器进行了优化形成了while循环
  • Java中尾递归不会优化为while循环
  • 尾递归也会出问题
1
2
3
4
def fun9(): Unit = {
println("test")
fun9()
}

惰性函数

  • 函数结果没使用,那么这个函数就不会执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def fun10(): String = {
println("print")
"return"
}

// print
// -----
// return
val val6 = fun10()
println("----------")
println(val6)

// -----
// print
// return
lazy val val7 = fun10()
println("----------")
println(val7)

面向对象编程

概念

  • Scala中的源码可以声明多个类,而且可以声明多个公共类,名称可以和文件名不一样

  • 可以让源码文件中多次使用package关键字
  • 源码物理路径和包名没有关系
  • 明确包的作用域,可以在package关键字的后面增加大括号
  • 同一个源码中,子包中可以直接访问父包中的内容
  • 可以将包当成对象来用,直接声明属性和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cc
package mousse {
def method1(): Unit = {
println("this is method1")
}
package object1 {
object PackageDemo {
def main(args: Array[String]): Unit = {
method1()
method2
}
}
}
}
1
2
3
4
5
package object object1 {
def method2: Unit = {
println("this is method2")
}
}

导入

  • Java中的import功能比较单一(导入其他包中的类、静态导入),但是不能省略马丁想赋予import更多的功能

  • 星号在Scala中有特殊用于,所以不能使用在import语法中,需要采用特殊符号:下划线

    1
    import java.util._
  • import关键字可以在任何地方使用

    1
    2
    3
    4
    5
    object ImportDemo {
    def main(args: Array[String]): Unit = {
    import java.util.Date
    }
    }
  • 可以在一行中导入同一个包中多个类

    1
    import java.util.{ArrayList, List, LinkedList}
  • 导包

    1
    2
    import java.util
    new util.ArrayList()
  • 屏蔽/隐藏类

    1
    2
    3
    import java.util._
    // java.sql.Data被隐藏
    import java.sql.{Date => _, _}
  • Scala中导入类的操作,是以相对路径(当前包路径)的方式导入的,如果想要使用绝对路径的方式,那么需要增加特殊操作:_root_

    1
    println(new _root_.java.util.HashMap())
  • 给类起别名

    1
    2
    import java.util.{HashMap => JavaHashMap}
    println(new JavaHashMap())

属性

  • 属性就是类中的变量,在编译时,编译器会将变量编译为类的(私有的)属性,同时提供了属性对应的set/get(不遵循bean规范)方法

    1
    2
    // private String name = "Tom";
    var name: String = "Tom"
    • 给类的属性赋值,等同于调用对象属性的set方法
    • 访问类的属性时,等同于调用对象属性的get方法
  • val声明的属性,在编译时,会给属性添加final关键字,编译器不会提供属性的set方法

    1
    2
    // private final int age = 20;
    val age: Int = 20
  • 使用private会使属性所对应的get/set方法变为私有的

    1
    private var national: String = "China"
  • 变量必须显示地初始化,若希望类的属性和Java一样可以由系统进行初始化可以采用特殊符号:_

    1
    var city: String = _
  • Scala中给属性提供的set/get方法不遵循Java Bean规范,使用@BeanProperty注解会添加针对该属性的set/get方法

    1
    @BeanProperty var email: String = _

访问权限

  • Java
    • private:同类
    • (default):同类,同包
    • protected:同类,同包,子类(调用者,父类信息在编译时加载到子类(单线继承))
    • public:任意
  • Scala
    • private:同类
    • private[包名]:包私有,同包
    • protected:受保护的,同类,子类,没有同包
    • (default):什么都不写就是公共的。没有public关键字

方法

  • 所谓的方法,其实就是类中声明的函数,属于类的一部分

  • 常用方法:使用预先声明好的方法

    • java.lang这个包中的类在Java使用时,不需要显示导入使用
    • Scala中也存在同样的操作
      • java.lang
      • scala
      • Predef
  • 重载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    object OverloadDemo {

    def main(args: Array[String]): Unit = {
    val aClass: ParentClass = new ChildClass
    // this is parent
    printClass(aClass)
    }

    def printClass(parentClass: ParentClass): Unit ={
    println("this is parent")
    }

    def printClass(childClass: ChildClass): Unit = {
    println("this is child")
    }

    class ParentClass {}

    class ChildClass extends ParentClass {}

    }
  • 重写

    • 如何区分父类,子类中相同的方法,需要采用TODO 动态绑定机制
    • 在调用对象的成员方法过程中,将方法和对象的实际内存进行绑定,然后调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class OverrideDemo {
    public static void main(String[] args) {
    ParentClass aClass = new ChildClass();
    // 20
    System.out.println(aClass.sum());
    }
    }

    class ParentClass {
    private int num = 10;
    public int sum() {
    return num + 10;
    }
    }

    class ChildClass extends ParentClass {
    private int num = 20;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class OverrideDemo {
    public static void main(String[] args) {
    ParentClass aClass = new ChildClass();
    // 30
    System.out.println(aClass.sum());
    }
    }

    class ParentClass {
    private int num = 10;
    public int sum() {
    return getNum() + 10;
    }
    public int getNum() {
    return num;
    }
    }

    class ChildClass extends ParentClass {
    private int num = 20;
    @Override
    public int getNum() {
    return num;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class OverrideDemo {
    public static void main(String[] args) {
    ParentClass aClass = new ChildClass();
    // 20
    // ParentClass.sum()
    System.out.println(aClass.sum());
    }
    }

    class ParentClass {
    private static int num = 10;
    public static int sum() {
    return num + 10;
    }
    }

    class ChildClass extends ParentClass {
    private static int num = 20;
    public static int sum() {
    return num + 20;
    }
    }

对象

  • 创建方法

    • 反射
    • new
    • 反序列化
    • clone
  • 构造

    • Java中的构造方法

      • 提供无参,公共的构造方法
      • 构造方法可以重载的
      • 构造方法可以互相调用
      • 必须显示调用父类有参的构造方法
      • 构造方法的名称应该和类型一致
    • Scala中的构造方法

      • 提供无参,公共的构造方法
      • Scala中构造方法的名称和类名不一致
    • Scala是一个完全面向对象的语言,又是一个完全面向函数的语言,所以类也是一个函数:声明一个类就等同于声明一个函数

    • 类名的后面可以声明小括号,表示构造参数列表

      • 如果提供了类的构造方法,那么JVM不会再给类提供无参的构造方法
      • 之所以在类名后面提供构造方法,主要目的就是为了类的初始化
      1
      2
      3
      4
      class User() {
      // 类的初始化
      // 构造方法体和类的主题内容
      }
    • 主构造方法

      • 用于完成类的初始化操作
    • 辅助构造方法

      • 在类初始化完成后,做一些辅助功能
      • 辅助构造方法的名字是this关键字,其他和普通方法一样
      • 辅助构造方法执行前,应该首先调用主构造方法完成类的初始化
      • 辅助构造方法可以重载,并可以互相调用,调用的辅助构造方法应该提前声明
      • 在构造参数前使用var或val声明
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      // this.name = name
      class Child(/*主构造方法*/var childName: String) extends Parent(childName)/*父类的有参构造初始化*/ {
      println("child load behind parent")
      // 辅助构造方法
      def this() = {
      // 调用主构造方法
      this("Tom")
      println("this()")
      }
      // 辅助构造方法
      def this(age: Int) = {
      // 调用前面声明的辅助构造方法
      this()
      println(s"this($age)")
      }
      }

      class Parent(var parentName: String) {
      println("parent load first")
      }
    • 私有构造:声明一个公共的,静态的,返回本类型的方法,用于获取对象

      1
      2
      3
      4
      5
      6
      7
      class PrivateClass private() {}

      object PrivateClass {
      def getInstance(): PrivateClass = {
      new PrivateClass
      }
      }

伴生类/对象

  • Scala中没有静态语法,但是可以直接使用Java中的静态操作

  • Scala采用了一种特殊的处理方式来代替静态语法:object

    • object关键字可以用于创建对象,对象的名字就是声明的名字
  • classobject的关系

    • 使用object关键字声明的类和对象有关系,这个对象等同于伴随着这个类创建时所产生的,所以将这个对象称之为伴生对象,这个类称之为伴生类
    • 伴生对象就是一个对象,可以访问伴生类中的所有东西,包括私有
    • 伴生对象其实就是马丁模拟静态语法所产生的
  • 一般将静态语法操作的代码写在伴生对象中,将成员方法或属性写在伴生类中

  • 伴生对象就是单例的,伴生对象只需要声明即可,无需构建,所以不需要构造参数列表

    • 单例模式存在一个问题:创建的对象不会被回收,需要显示地回收(设置为null)
    • 如果伴生对象中构建对象的方法名成为apply,编译器可自动识别,方法名可省略
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 调用类的构造方法
    val user = new User()
    // 调用伴生对象apply方法
    val singletonDemo1 = SingletonDemo.apply()
    val singletonDemo2 = SingletonDemo()
    // 伴生对象本体
    val singletonDemo3 = SingletonDemo

    class SingletonDemo private {}

    object SingletonDemo {
    def apply() = {
    new SingletonDemo
    }
    }

抽象

  • 抽象类
    • 抽象类没有办法直接实例化,需要由子类继承后完成实例化操作
    • 子类继承抽象类后,可以声明为抽象类,也可以将父类的抽象方法补充完整
  • 抽象属性
    • 底层实现时其实就是抽象方法
      • 抽象属性:编译时不会在类中声明属性,而是会声明属性抽象的set/get方法
      • 重写属性:编译时会在类中声明私有属性,同时提供属性公共的set/get方法
    • 重写
      • 抽象属性:补充完整
      • 完整属性:需添加override关键字
  • 抽象方法
    • Scala中不完整的方法就是抽象,所以无需增加abstract关键字
    • 重写
      • 抽象方法:补充完整
      • 完整方法:需添加override关键字
      • 推荐只要重写都添加override
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 抽象类
abstract class User {
// 抽象属性:只有声明,没有初始化
// 编译时不会在类中声明属性,而是会声明属性抽象的set/get方法
var name: String
val age: Int = 10
// 抽象方法:只有声明,没有实现
def test(): Unit
def test1(): Unit = {}
}

class Child extends User {
// 属性:编译时,会在类中声明私有属性,同时提供属性公共的set/get方法
// 子类可以重写父类的抽象属性,补充完整即可
var name = "Tom"
// 子类可以重写父类的完整属性,那么必须要添加override关键字
// 需使用val以避免歧义
override val age: Int = 20
// 子类重写父类的抽象方法,直接补充完整即(开发时,推荐,只要重写,都添加override)
override def test(): Unit = {}
// 子类重写父类的完整方法,必须添加override关键字
override def test1(): Unit = {}
}

特质/特质

概念

  • 将多个对象中相同的特征从对象中剥离出来形成独立的一个结构,称之为trait(特征
  • 如果一个对象符合这个特征,那么可以将这个特征加入到这个对象,这个加入的过程,称之为混入(extends
  • 可以将Trait理解为接口和抽象类的结合体(Scala没有接口)
  • 继承方式
    • 1特质:采用extends关键字
    • 多特质:第一个特质采用extends,后续采用with
    • 存在父类和特质:使用extends关键字继承父类,使用with关键字来混入特征
  • 初始化顺序
    1. 父类的特质
    2. 父类
    3. 特质1
    4. 特质2
    5. 当前类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
trait Runnable {
def run(): Unit
}

trait Eatable {
def eat(): Unit
}

class Animal {}

class Person extends Animal with Runnable with Eatable {
override def run() = println("run")
override def eat() = println("eat")
}

动态混入

1
2
val user = new User with UpdateUser
user.update()
1
2
3
4
5
6
7
trait UpdateUser {
def update() = println("update user")
}

class User {
def insert() = println("insert user")
}

动态叠加

  • Java中不能类的多继承 : 砖石问题

  • Scala采用了一种功能叠加的方式解决砖石问题

  • super不是父特质的意思,是上一级(上一个)的意思(super在编译时起作用)

    1
    2
    3
    4
    5
    // 初始化顺序: Operator→Log→Database
    // 调用顺序
    // 用户→[DataBase.opData()→[Log.opData()→[Operator.opData()[MySQL]]]]
    // 向数据库中向日志文件中操作数据
    new MySQL().opData()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    trait Operator {
    def opData() = println("操作数据")
    }

    trait DataBase extends Operator {
    override def opData(): Unit = {
    print("向数据库中")
    super.opData()
    }
    }

    trait Log extends Operator {
    override def opData(): Unit = {
    print("向日志文件中")
    super.opData()
    }
    }

    class MySQL extends Log with DataBase {
    }
  • 可以使用spuer[特质]指定某个特质

    1
    2
    // 向数据库中操作数据
    new MySQL().opData()
    1
    2
    3
    4
    5
    6
    trait DataBase extends Operator {
    override def opData(): Unit = {
    print("向数据库中")
    super[Operator].opData()
    }
    }

扩展

反射

  • 字符串底层数组长度不可变,数组中的内容可变
  • 字符串工具类没提供改变数组中内容值的方法,需使用反射修改
1
2
3
4
5
6
7
8
9
10
11
12
13
val str = " b d "
// 获取反射类型信息
val stringClass: Class[String] = classOf[String]
// 得到私有属性
val field: Field = stringClass.getDeclaredField("value")
field.setAccessible(true)
// 从封装的属性中取出对应的数组
val obj: Object = field.get(str)
val chars: Array[Char] = obj.asInstanceOf[Array[Char]]
chars(0) = 'a'
chars(2) = 'c'
chars(4) = 'e'
println(str)

枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
object Test {
def main(args: Array[String]): Unit = {
println(Color.RED)
println(Color.RED.id)
}
}

// 枚举类
object Color extends Enumeration {
val RED = Value(1, "red")
val YELLOW = Value(2, "yellow")
val BLUE = Value(3, "blue")
}

应用类

1
2
3
4
5
6
7
8
// 先执行构造体再执行方法
object AppDemo {
println("first")
def main(args: Array[String]): Unit = {
println("third")
}
println("second")
}
1
2
3
4
5
object AppDemo extends App {
println("first")
println("second")
println("third")
}

定义别名

1
type JavaStrStrMap = java.util.HashMap[String, String]

集合

概念

  • Scala的集合有三大类:序列Seq、集Set、映射Map,所有的集合都扩展自Iterable特质
  • 对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本,一般可以根据集合所在包名进行区分
  • Scala默认提供的集合都是不可变

通用方法

基础操作

1
2
3
4
5
6
7
8
9
10
val ints = ArrayBuffer(1, 2, 3, 4)
println(ints.length)
println(ints.size)
println(ints.isEmpty)
println(ints.contains(2))
// 去重
println(ints.distinct)
println(ints.reverse)
ints.foreach(println)
ints.iterator

集合操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 取出第一个数
println(ints.head)
// 取出第一以外的数
println(ints.tail)
// 取出倒数第一个数
println(ints.last)
// 取出倒数第一以外的数
println(ints.init)
// 取出前几个
println(ints.take(2))
// 取出后几个
println(ints.takeRight(2))
// 去除正数第几个并返回(从1开始)
println(ints.drop(1))
// 去除倒数第几个并返回(从1开始)
println(ints.dropRight(1))
// 交集,并集,差集
println(list1.intersect(list2))
println(list1.union(list2))
println(list1.diff(list2))

运算

1
2
3
println(ints.sum)
println(ints.max)
println(ints.product)

聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// TODO reduce
// 自定义数据操作的方法
// 集合的数据无论是多少,最基本的数据操作其实都是两两计算
// map => reduce => 简化,规约(聚合)
ints.reduce((x: Int, y: Int) => x - y)
println(ints.reduce(_ - _))
// reduceRight底层先反转再reduceLeft
// [1,2,3,4,5] = 1-(2-(3-(4-5)))
println(ints.reduceRight(_ - _))
// TODO fold
// 折叠,用于集合与集合外的元素聚合
val num = 5
// (5 1)2)3)4)
ints.fold(num)(_ + _)
// 原理和reduceRight差不多
// (1 2)3)4)5)
ints.foldRight(num)(_ - _)
// 扫描:把临时计算结果保留下来
println(ints.scan(num)(_ - _))
println(ints.scanRight(num)(_ - _))
// reduce和reduceLeft的区别
val list1 = List(1, 2, 3, 4)
// (A1, A1) => A1:类型需相等
list1.reduce()
// (B, Int) => B:B和提供的类型有关系
list1.reduceLeft()
// fold和foldLeft的区别
// (A1)((A1,A1)=>A1):类型需相等
list1.fold()()
// (B)((B, Int)=>B)
list1.foldLeft()()
// "1234"
list1.foldLeft("")(_ + _)

功能函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 由集合对象提供函数执行自定义的功能
// TODO Map
// map => 映射(转换) => K->V
val arr1 = ArrayBuffer(1, 2, 3, 4)
// map方法需要传递一个参数,这个参数的类型为函数类型: Int => B
println(arr1.map(_ * 2))
// 将整体拆分成个体的操作,称之为扁平化
// 一次扁平化操作只能对最外层进行操作
val arr2 = ArrayBuffer(ArrayBuffer(1, 2), ArrayBuffer(3, 4))
println(arr2.flatten)
// 自定义扁平化
val arr3 = Array("Hello Scala", "Hello Hadoop")
println(arr3.flatMap(_.split(" ")).mkString(","))
// TODO filter
// filter方法可以对集合中的每一条数据进行筛选过滤
// 满足条件(true)的数据保留,不满足条件(false)的数据丢弃
println(arr1.filter(_ % 2 != 0))
// TODO groupBy
// 根据指定的规则对每一条数据进行分组
println(arr1.groupBy(num => {
if (num % 2 == 0) "偶数"
else "奇数"
}))
println(arr1.groupBy(_ % 2))
val arr4 = ArrayBuffer("Hello", "Scala", "Hadoop", "Spark")
println(arr4.groupBy(_.charAt(0)))
// TODO sortBy
// 排序:通过指定的规则对每一条数据进行排序处理,默认为升序
val arr6 = ArrayBuffer(1, 3, 4, 2)
// 作为数字排
println(arr6.sortBy(num => num))
// 作为数字降序排
println(arr6.sortBy(num => num)(Ordering.Int.reverse))
val arr7 = ArrayBuffer("1", "11", "2", "3", "22")
// 字典序排序
println(arr7.sortBy(str => str))
// 数字大小排序
println(arr7.sortBy(str => str.toInt))

数组

概念

  • 严格意义上数组不是集合,Scala中给数组一个特定的类型:Array
  • Scala默认提供的集合都是不可变

不可变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// TODO 构建
// 构建Scala中的数组,其实等同于构造Java的数组
val array = new Array[String](3)
// 使用集合的伴生对象构建集合,并同时初始化
val array1 = Array(1, 2, 3, 4)
// TODO 访问
// 可以根据索引访问数组的元素
array.update(1, "a")
// 中括号在scala中表示泛型,所以不能在索引操作中使用,使用小括号
array(2) = "b"
// 放在集合前面
val ints1: Array[Int] = array1.+:(5)
// scala中如果运算符是以冒号结尾,那么运算规则为从后向前计算
// array1 +: 5 = 5.+:(array1)
val ints11: Array[Int] = 5 +: array1
// 放在集合后面
val ints2: Array[Int] = array1.:+(5)
val ints22: Array[Int] = array1 :+ 5
// 集合与集合相加
val ints3: Array[Int] = ints1 ++: ints2
// TODO 遍历
// 方式1
for (str <- array1) println(str)
// 方式2:生成字符串
println(ints1.mkString)
// 以,分隔
println(ints2.mkString(","))
// 方式3:foreach方法是一个循环的方法,需要传递一个参数,这个从参数的类型是函数类型
// 函数类型:Int => U
// ints3.foreach(foreachFunction)
// ints3.foreach((num:Int)=>{println(num)})
// ints3.foreach((num:Int)=>println(num))
// ints3.foreach((num)=>println(num))
// ints3.foreach(num=>println(num))
ints3.foreach(println(_))
// TODO 其他操作
// 合并数组
val ints4: Array[Int] = ints1 ++: ints2
val ints5: Array[Int] = Array.concat(ints1, ints2)
// 创建指定范围的数组
val arr7: Array[Int] = Array.range(0, 2)
// 填充
val arr8: Array[Int] = Array.fill[Int](5)(-1)
arr8.foreach(println)

多维

1
2
val matrix: Array[Array[Int]] = Array.ofDim[Int](2, 3)
matrix.foreach(list => println(list.mkString(" ")))

可变

1
2
3
4
5
6
7
8
9
10
11
12
13
val buffer = new ArrayBuffer[String]()
val buffer1 = ArrayBuffer("a", "b", "c")
// TODO 操作
buffer.append("a", "b", "c")
buffer.appendAll(Array("a", "b", "c"))
buffer.insert(1, "insert")
buffer.update(0, "update")
buffer.update(0, "update")
buffer(0) = "update"
buffer.remove(1)
buffer.remove(1, 2)
// 会生成新ArrayBuffer
val newBuffer: ArrayBuffer[String] = buffer -= "a"

转换

1
2
3
4
5
6
7
8
9
10
// 不可变 => 可变
val buffer2: mutable.Buffer[String] = array.toBuffer
// 可变 => 不可变
val array2: Array[String] = buffer2.toArray
// Java集合 => Scala集合
import scala.collection.JavaConverters._
val list = new java.util.ArrayList()
list.asScala.foreach(println)
// Scala集合 => Java集合
val java: util.List[Int] = List(1, 2, 3, 4).asJava

WordCount案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 读取文件获取原始数据
val source = Source.fromFile("data/world.txt")
val lines = source.getLines().toArray
source.close()
// 将原始数据切分成单词
val words = lines.flatMap(_.split(" "))
// 对分词结果进行分组操作
val wordGroup = words.groupBy(s => s)
// 数量统计
// 如果数据在转换时,无需对key进行操作,只对v进行处理时,可以使用mapValues方法
val wordCount = wordGroup.mapValues(_.length).toMap
// 使用Tuple
val wordCount = wordGroup.map(kv => (kv._1, kv._2.length))
// 打印
println(wordCount)
1
2
3
4
5
6
7
8
9
// 优化
val source = Source.fromFile("data/world.txt")
val lines = source.getLines().toArray
source.close()
val wordCount = lines
.flatMap(_.split(" "))
.groupBy(s => s)
.map(kv => (kv._1, kv._2.length))
println(wordCount)

Seq

概念

  • 数据有序,可以放重复数据
  • 一般会从采用List

不可变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
val seq = Seq(1, 2, 3, 4)
val list1 = List(1, 2, 3, 4)
// TODO 数据操作
// list后添加元素
val list2 = list1 :+ 5
// list前添加元素
val list3 = 5 +: list1
// Nil在集合中表示空集合
// Nil.::(3).::(2).::(1)
val list4 = 1 :: 2 :: 3 :: Nil
// list4作为整体
val list5 = 1 :: 2 :: list4 :: Nil
// list4作为个体
val list6 = 1 :: 2 :: list4 ::: Nil

可变

1
2
3
4
5
val list7 = ListBuffer(1, 3, 4, 2, 1)
// 改变自身
list7.update(0, 5)
// 创建新的
val list8 = list7.updated(0, 6)

转换

1
2
val list9 = list7.toList
val list10 = list9.toBuffer

Set

1
2
3
4
5
6
7
8
9
10
11
12
13
// TODO Set
// 数据无序,不可重复
// TODO 不可变
val set1 = Set(1,2,3,4,1,2,3,4)
// TODO 可变
val set2 = mutable.Set(1,2,3,4)
val isSuccess = set2.add(5)
// update方法用于更新set集合
// 更新元素为包含(添加)
set2.update(5, true)
// 更新元素为不包含(删除)
set2.update(4, false)
set2.remove(3)

Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// map存储的数据也是K-V键值对
// map描述了一个数据无序,K不能重复的集合
// Scala中k-v键值对非常特殊
// 不可变
val map1 = Map("a" -> 1, "b" -> 2, "c" -> 3)
// 可变
val map2 = mutable.Map("a" -> 1, "b" -> 2, "c" -> 3)
map2.put("d", 4)
map2.update("a", 7)
map2.remove("a")
// Java中从HashMap中获取一个不存在的key,会返回null
// HashMap允许放空键空值
// Option类型专门为了解决空指针问题设计的
// Option:选项,对象只有2个Some, None
val maybeInt: Option[Int] = map2.get("a")
if (maybeInt.isEmpty) {
// 默认值
println("没有对应key的值, 默认值: " + maybeInt.getOrElse(0))
} else {
println("对应key的值:" + maybeInt.get)
}
println("获取指定key的值:" + maybeInt.getOrElse(0))a
println(map2.getOrElse("a", 0))

Tuple

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Scala可以将无关的元素组合在一起,形成一个整体来进行访问,这种整体结构称之元素组合
// 简称元组 - Tuple
val tuple1 = (1, "Tom", 30)
// 因为元组中的数据没有关系,所以只能通过顺序号进行访问
println(tuple1._1)
println(tuple1._2)
// Tuple也是一个集合对象,所以也有类型:(Int, String, Int)
// Scala中元组也有专门的类型
// Tuple类型最多存放元素的数量为22个。但是类型没有约束
val tuple2: Tuple3[Int, String, Int] = (1, "Tom", 30)
val tuple3: (Int, String, Int) = (1, "Tom", 30)
// 如果元组中的元素只有2个,称之为对偶元组,也可以称之为键值对
val tuple4 = (1, "Tom")
val tuple5: (Int, String) = 1 -> "Tom"
val map1 = Map(("a", 1), ("b", 2))
map1.foreach(t => println(s"${t._1}=${t._2}"))
// 将Map转换为List
val list1: List[(String, Int)] = map1.toList

并行

1
2
3
4
5
6
<!-- Scala2.13之后并行模块变成了外部库,和XML、Swing、parser-combinators等一样需要在maven项目的pom.xml中手动导入 -->
<dependency>
<groupId>org.scala-lang.modules</groupId>
<artifactId>scala-parallel-collections_3</artifactId>
<version>1.0.4</version>
</dependency>
1
2
3
4
5
6
// Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算
// 单线程
println((0 to 100).map(_ => Thread.currentThread().getName))
// 多线程
import scala.collection.parallel.CollectionConverters._
println((0 to 100).par.map(_ => Thread.currentThread().getName))

模式匹配

概念

  • Scala中的模式匹配类似于Java中的switch语法(Scala没有switch
  • Scala从语法中补充了更多的功能,可按照指定的规则对数据或对象进行匹配
  • 当数据满足某一个分支时,执行完毕后就直接跳出
  • case _ 分支类似于default语言,分支匹配其实就是顺序匹配
  • 如果数据没有匹配任何规则,会发生错误
1
2
3
4
5
6
7
8
val age = 30
age match {
case 10 => println(10)
case 20 => println(20)
case 30 => println(30)
// 任意值
case _ => println("other")
}

匹配规则

匹配常量

1
2
3
4
5
6
def describe1(x: Any) = x match {
case 5 => "Int five"
case "hello" => "String hello"
case true => "Boolean true"
case '+' => "Char +"
}

匹配类型

  • 类型前增加变量名称,这个变量就是将数据转换成指定类型的变量
  • 如果想要使用下划线代表的数据,可以给下划线起名来使用
  • Scala中类型匹配时不考虑泛型,Array类除外
1
2
3
4
5
6
7
8
9
10
11
12
13
def describe2(x: Any) = x match {
// 会自动把x转为i
case i: Int => "Int:" + (i + 10)
case s: String => "String hello"
// 下划线表示任意类型,类型匹配时不考虑泛型(Array除外)
case m: List[_] => "List"
// Array[Int], 这里的Int不是真正的泛型
// java => String[]
// scala => Array[String]
case c: Array[Int] => "Array[Int]"
// default
case someThing => "something else " + someThing
}

匹配数组

  • 判断给定的数组的规则,对一个数组集合进行遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for (
arr <- Array[Any](
Array(0),
Array(1, 0),
Array(0, 1, 0),
Array(1, 1, 0),
Array(1, 1, 0, 1),
Array("hello", 90)
)
) {
val result = arr match {
// 匹配Array(0)这个数组
case Array(0) => "0"
// 匹配有两个元素的数组,然后将将元素值赋给对应的x,y
case Array(x, y) => s"$x,$y"
// 匹配以0开头和数组
case Array(0, _*) => "以0开头的数组"
case _ => "something else"
}
println("result = " + result)
}

匹配列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for (
list <- Array(
List(0),
List(1, 0),
List(0, 0, 0),
List(1, 0, 0),
List(88)
)
) {
val result = list match {
// 匹配List(0)
case List(0) => "0"
// 匹配有两个元素的List
case List(x, y) => x + "," + y
case List(0, _*) => "0 ..."
case _ => "something else"
}
println(result)
}
1
2
3
4
5
6
7
val list: List[Int] = List(1, 2, 5, 6, 7)
list match {
// List(1, 2):1::2::Nil
// 1-2-List(5, 6, 7)
case first :: second :: rest => println(first + "-" + second + "-" + rest)
case _ => println("something else")
}

匹配元组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for (
tuple <- Array(
(0, 1),
(1, 0),
(1, 1),
(1, 0, 2)
)
) {
val result = tuple match {
// 是第一个元素是0的元组
case (0, _) => "0 ..."
// 匹配后一个元素是0的对偶元组
case (y, 0) => "" + y + "0"
case (a, b) => "" + a + " " + b
case _ => "something else"
}
println(result)
}

匹配对象

1
2
3
4
5
6
7
8
def getUser() = {
User("Tom", 30)
}
val user = getUser()
user match {
case User("Tom", 20) => println("Tom-20")
case _ => println("nothing")
}
  • 普通类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class User {
    var name: String = _
    var age: Int = _
    }

    object User {
    // apply: Attribute => Object
    def apply(name: String, age: Int) = {
    val user = new User
    user.name = name
    user.age = age
    user
    }

    // unapply: Object => Attribute
    def unapply(user: User): Option[(String, Int)] = {
    Option((user.name, user.age))
    }
    }
  • 样例类

    • 如果在类的前面的增加case关键字,这个类专门用于模式匹配称之为样例类
    • 在编译时,会自动生成/增加/重写了大量的方法
      • 样例类会自动实现可序列化接口
      • 样例类的构造参数直接能够作为属性使用,(不能修改,如果想要修改,需要将参数使用var声明)
      • 样例类自动生成伴生对象,而且其中自动声明了apply/unapply
    1
    case class User(var name: String, age: Int)

偏函数

  • 全量函数:函数进行处理时必须对所有的数据进行处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    val list2 = List(1, 2, 3, 4)
    // map只支持全量函数操作
    // 2 () 6 ()
    val list3 = list2.map(
    num => {
    if (num % 2 != 0) {
    num * 2
    }
    }
    )
  • 偏函数: 函数进行处理时只对满足条件的数据进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
// 将List中的Int类型的元素加一,并去掉字符串
val list4: List[Any] = List(1, 2, 3, 4, 5, 6, "test")
// 全量函数
list4.map{
case i: Int => i + 1
case other => other
}.filter(_.isInstanceOf[Int])

// 偏函数
val list5 = list4.collect{
case i : Int => i + 1
}
println(list5)

应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
val (id, name, _) = (1, "Tom", 2000)
println(name)

val map = Map(("a", 1), ("b", 2), ("c", 3))
for ((k, 2) <- map) {
println(k)
}

val list1 = List(
(("河北", "鞋"), 10),
(("河北", "衣服"), 20),
(("河北", "电脑"), 15),
)
println(
// 不直观
list1.map(t => (t._1._1, (t._1._2, t._2 * 2)))
)
println(
// 下面的代码中,使用模式匹配需要注意:
// 1. 匹配数据时,需要使用case关键字
// 2. case分支可能存在多个,那么需要将map的小括号换成大括号
list1.map { case ((prv, item), cnt) => (prv, (item, cnt * 2)) }
)

异常

概念

  • 异常分类
    • 编译时异常:编译器为了程序的健壮性,提示开发者,代码是会有问题的,应该想办法针对于不同的问题来解决
    • 运行时异常
  • Scala中异常没有分类,无需显示抛出方法异常,没有throws关键字
  • 如果Java程序调用Scala代码,Scala需要使用@throws[Exception]使Java明确异常

Java异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static int finallyTest() {
// 1 return关键字不会马上返回结果
// 2 所有的return返回同一个值:临时变量, 1 + 1 + 1 = 3
int i = 0;
try {
// _temp = i++, _temp = 0, i = 1
// return _temp
return i++;
} finally {
// _x = ++i =, i = 2, _x = 2
// return _temp
//return ++i;
++i;
}
}

Scala异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def main(args: Array[String]): Unit = {
//new FileInputStream("xxxx")
try {
val i = 1 / 0
} catch {
case e: ArithmeticException => println("算术异常")
case e: Exception => println("其他异常")
}
}

@throws[Exception]
def throwException() = {
throw new Exception("this is a exception")
}

隐式转换

概念

  • 如果程序编译出错,编译器会尝试在整个的作用域中查找能够让程序编译通过的方式
  • 如果找到,编译器会尝试二次编译,让之前编译出现错误的代码经过转换后能够编译通过
  • 这个转换过程看不见但是存在,称为隐式转换
  • 所谓的隐式转换其实就是类型的转换

隐式函数

  • 同一作用域中不能有多个相同转换规则
1
2
3
4
5
6
// 隐式函数
implicit def doubleToInt(d: Double): Int = {
d.toInt
}

val age: Int = getAge()
1
2
3
def getAge(): Double = {
30.0
}

隐式参数&变量

  • 隐式参数不用传递,这个传递的过程由编译器完成
  • 在同一个作用域中,如果相同的转换规则的多个数据,会发生错误
1
2
3
4
5
6
7
8
9
10
11
12
13
// TODO 隐式参数
def register(implicit password: String = "000000"): Unit = {
println(s"密码: $password")
}

// TODO 隐式变量
implicit val password: String = "111111"
// 111111
register
// 000000
register()
// 123456
register("123456")

隐式类

  • 所带的构造参数有且只能有一个
  • 不能为顶级对象
1
2
3
4
5
implicit class UserExt(user: User) {
def update(): Unit = {
println("update user")
}
}

作用范围

  • 当前代码作用域
  • 父类或伴生对象
  • 特征或伴生对象
  • 在其他地方声明(包对象)
  • 直接导入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
object TransformDemo1 extends Parent with MyTrait {
// TODO 隐式转换的范围
def main(args: Array[String]): Unit = {
val user = new User
user.insert()
user.update()
user.delete()
user.select()
user.drop()
// 直接导入
import cc.mousse.transform.AnotherObject._
user.desc()
}

class User {
def insert(): Unit = {
println("insert user")
}
}
// 当前代码作用域
implicit class UserExtInTheSameClass(user: User) {
def update(): Unit = {
println("update user")
}
}

}

// 父类或伴生对象
class Parent {
import cc.mousse.transform.TransformDemo1.User
implicit class UserExtInParentClass(user: User) {
def delete(): Unit = {
println("delete user")
}
}
}

// 特征或伴生对象
trait MyTrait {
import cc.mousse.transform.TransformDemo1.User
implicit class UserExtInTrait(user: User) {
def select(): Unit = {
println("select user")
}
}
}

// 直接导入
object AnotherObject {
implicit class UserExtInAnotherClass(user: cc.mousse.transform.TransformDemo1.User) {
def desc(): Unit = {
println("desc user")
}
}
}
1
2
3
4
5
6
7
8
// 在其他地方声明(包对象)
package object transform {
implicit class UserExtInPackage(user: cc.mousse.transform.TransformDemo1.User) {
def drop(): Unit = {
println("drop user")
}
}
}

泛型

概念

  • 泛型和类型的区别
    • 类型对外部的数据做约束,泛型对内部的数据做约束
    • 泛型和类型的层次不一样,不能作为整体来考虑
  • 泛型在某些场合中,其实就是类型参数,用于向类中传递参数
  • 泛型其实只在编译时有效, 将这个操作称之为“泛型擦除“
  • 为了使用方便,可以定义泛型的边界

泛型转换

概念

  • 将类型和泛型当成一个整体来使用

协变

  • 将类型和泛型联合使用,类型相同时如果泛型存在父子类关系,那么联合的类型也就存在父子类关系+T
1
2
3
val msg1: Message[Parent] = new Message[Parent]
val msg2: Message[Parent] = new Message[Child]
class Message[+T] {}

逆变

  • 类型相同,泛型之间存在父子关系,那么让联合后的类型存在子父关系-T
1
2
3
val msg3: Message1[Parent] = new Message1[Parent]
val msg4: Message1[Parent] = new Message1[GrandParent]
class Message1[-T] {}

泛型边界

上限

  • _ >: T
1
2
3
4
new Producer[Parent].produce(new Message[GrandParent])
class Producer[T] {
def produce(msg: Message[_ >: T]): Unit = msg
}

下限

  • _ <: T
1
2
3
4
new Consumer[Parent].consume(new Message[Child])
class Consumer[T] {
def consume(msg: Message[_ <: T]): Unit = msg
}

上下文限定

1
2
3
4
def f[A: Test](a: A) = println(a)
implicit val test: Test[Parent] = new Test[Parent]
f(new Parent())
class Test[T]{}

正则表达式

  • 模式匹配匹配的是规则:类型,常量,元组,集合,数组,对象,参数
  • 正则表达式也是匹配规则:String
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def checkTel(tel: String): Boolean = {
if (tel.size == 11) {
var r = true
Breaks.breakable {
for (c <- tel) {
try {
c.toString.toInt
} catch {
case e: Exception => {
e.printStackTrace()
r = false
Breaks.break()
}
}
}
}
r
} else {
false
}
}

Scala
http://docs.mousse.cc/Scala/
作者
Mocha Mousse
发布于
2025年5月26日
许可协议