Scala —> Java++

  • Scala 是面向对象、函数式的编程语言
  • Scala 基于 JVM,与 Java 完全兼容,具有跨平台、可移植性、垃圾回收等特性
  • Scala 更适合大数据的处理,对集合类型有非常好的支持

image-20230218172208287

Scala 与 Java 与 JVM

image-20230124162547212

Scala 代码的执行过程与 Java 非常类似,先编译,后运行

.scala —> .class 字节码 —> 执行

Hello World

Java

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

Scala

object HelloWorld {
  def main(args: Array[String]): Unit = {
    println("hello world")
  }
}
  • object 关键字,声明一个单例对象/伴生对象

  • main 方法,从外部可以直接调用执行的方法

  • def 方法名(参数名: 参数类型): 返回值类型 = {

    ​ 方法体

    }

  • 不需要分号

反编译 Hello World

target 目录中一共生成了两份 .class 文件

image-20230112164535555

使用 JavaDecompiler 工具进行反编译可得:

HelloWorld$.class

public final class HelloWorld$ {
  public static HelloWorld$ MODULE$;
  
  public void main(String[] args) {
    scala.Predef$.MODULE$.println("hello world");
    System.out.println("hello from scala");
  }
  
  private HelloWorld$() {
    MODULE$ = this;
  }
}

HelloWorld.class

import scala.reflect.ScalaSignature;

@ScalaSignature(bytes = "\006\001%:Q\001B\003\t\002!1QAC\003\t\002-AQAE\001\005\002MAQ\001F\001\005\002U\t!\002S3mY><vN\0357e\025\0051\021A\002;fgR\004\024g\001\001\021\005%\tQ\"A\003\003\025!+G\016\\8X_JdGm\005\002\002\031A\021Q\002E\007\002\035)\tq\"A\003tG\006d\027-\003\002\022\035\t1\021I\\=SK\032\fa\001P5oSRtD#\001\005\002\t5\f\027N\034\013\003-e\001\"!D\f\n\005aq!\001B+oSRDQAG\002A\002m\tA!\031:hgB\031Q\002\b\020\n\005uq!!B!se\006L\bCA\020'\035\t\001C\005\005\002\"\0355\t!E\003\002$\017\0051AH]8pizJ!!\n\b\002\rA\023X\rZ3g\023\t9\003F\001\004TiJLgn\032\006\003K9\001")
public final class HelloWorld {
  public static void main(String[] paramArrayOfString) {
    HelloWorld$.MODULE$.main(paramArrayOfString);
  }
}

可见,Scala 代码编译时,实际上被编译回了 Java 代码

另外,此处使用了单例模式

变量和常量

Java

// 变量
int a = 1;

// 常量
final int b = 666;

Scala

// 变量
var i = 1;

var i : Int = 1

// 常量
val j = 666

val j : Int = 666
  • 变量类型可省略

    Scala 编译器可对当前变量类型作自动推断

  • 变量类型确定后,不可修改

    Scala 是强数据类型语言

  • 变量声明时,必须有初始值

  • 区别 var 变量 和 val 常量

标识符命名规范

  • 以字母或下划线开头,后接字母、数字、下划线
  • 以操作符开头,且仅包含操作符 + - * / # !
  • 用反引号包括的任意字符串,即使是 Scala 关键字也可以

字符串

  • +号拼接

    val name = "alice";
    val age = 18;
    println(name + age)
    alice18
  • *号多次复制和拼接

    val name = "alice";
    println(name * 3)
    alicealicealice
  • printf 通过%传值

    val name = "alice";
    val age = 18
    printf("%s is %d years old", name, age)
    alice is 18 years old
  • 字符串模板(插值字符串),通过$取值

    // 模板字符串格式
    println(s"")
    println(f"")
    println(raw"")
    val name = "alice";
    val age = 18
    println(s"${name} is ${age} years old")
    alice is 18 years old
  • 三引号,保留多行字符串原格式

    val sql = s"""
    			|select *
                |from
                | student
                |where
                | name = ${name}
                |""".stripMargin
    
    println(sql)
    select *
    from
     student
    where
     name = alice

控制台输入

println("input:")
val str = StdIn.readLine()

println("output:")
println(str)
input:
123
output:
123

读写文件

// 读取文件
Source.fromFile("D:\\IdeaProjects\\ScalaTest\\src\\main\\resources\\test.txt").foreach(print)
// 写入文件
val writer = new PrintWriter(new File("D:\\IdeaProjects\\ScalaTest\\src\\main\\resources\\output.txt"))
writer.write("123456")
writer.close()

数据类型

Java

  • 基本类型:

    byte short int long float double char boolean

  • 引用类型:

    对象

    基本类型对应的包装类 Byte Short Integer Long Float Double Character Boolean

Scala

image-20230115224941042

  • Scala 中的一切数据都是对象,都是 Any 的子类

  • Scala 中的数据类型分为数值类型AnyVal和引用类型AnyRef,且两者都是对象

  • Unit 对应 Java 中的 void

    用于作为一种特殊的返回值类型,表示方法没有返回值

    Unit 是一种数据类型,只有一个对象()

  • Null 是一种数据类型,只有一个对象null,是所有引用类型AnyRef的子类

  • Nothing 位于 Scala 类型层次中的最低层,是任何类型的子类

    用于作为一种特殊的返回值类型,可将异常返回值抛给其他函数

函数

基本语法

image-20230117175517918

函数定义

// 函数 1:无参,无返回值
def test1(): Unit = {
    println("无参,无返回值")
}

test1()

// 函数 2:无参,有返回值
def test2(): String = {
    return "无参,有返回值"
}

println(test2())

// 函数 3:有参,无返回值
def test3(s: String): Unit = {
    println(s)
}

test3("jinlian")

// 函数 4:有参,有返回值
def test4(s: String): String = {
    return s + "有参,有返回值"
}

println(test4("hello "))

// 函数 5:多参,无返回值
def test5(name: String, age: Int): Unit = {
    println(s"$name, $age")
}

test5("dalang", 40)

// 函数 6:多参,有返回值
def test6(a: Int, b: Int): Int = {
    return a + b;
}

println(test6(666, 777))

函数参数

可变参数

def test(s: String*): Unit = {
    println(s)
}

test("hello")
test("a", "b", "c")
test()
WrappedArray(hello)
WrappedArray(a, b, c)
List()

变长参数,返回包装数组

若不传入任何值,则返回List()

若存在多个参数,那么可变参数一般放在最后

参数默认值

def test( name : String, age : Int = 30 ): Unit = {
    println(s"$name, $age")
}

test("alice")
test("alice", 18)
alice, 30
alice, 18

若默认值参数传入值,则会覆盖默认值

一般将默认值参数放在最后

带名参数

当函数参数过多时,为了方便调用,可以打乱传入顺序,用参数名指定传入值

或者,当大量参数有默认值,只需要传入某些参数时,会非常方便

def test( name : String, age : Int = 30 ): Unit = {
    println(s"$name, $age")
}

test(age = 66, name = "bob")
bob, 66

函数至简原则

// 函数标准写法
def f(s: String): String = {
    return s + " abc"
}
println(f("Hello"))
//(1) return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
def f1(s: String): String = {
    s + " abc"
}

println(f1("this is f1"))

//(2)如果函数体只有一行代码,可以省略花括号
def f2(s: String): String = s + " abc"

println(f2("this is f2"))

//(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略
def f3(s: String) = s + " abc"

println(f3("this is f3"))

//(4)如果有 return,则不能省略返回值类型,必须指定。
def f4(): String = {
    return "this is f4"
}

println(f4())
//(5)如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
def f5(): Unit = {
    return "this is f5"
}

println(f5())

//(6)如果期望是无返回值类型,可以省略等号,将无返回值的函数称之为过程
def f6() {
    println("this is f6")
}

f6()

//(7)如果函数无参,但是声明了参数列表,那么调用时小括号可加可不加
def f7() = "this is f7"

println(f7())
println(f7)

//(8)如果函数没有参数列表,那么小括号可以省略,但是调用时小括号必须省略
def f8 = "this is f8"

println(f8)

//(9)如果不关心名称,只关心逻辑处理,那么函数名可以省略
(name: String) => {
    println(name)
}

匿名函数

// 一般的匿名函数,Lambda 表达式
val f1 = (name: String) => {
    println(name)
}

f1("this is f1")

// 以函数作为参数
def f2(func: String => Unit): Unit = {
    func("this is func in f2")
}

f2(f1)
this is f1
this is func in f2
  • 对于 f1,虽然是匿名函数,但仍然可以指定一个函数名

  • 对于 f2,以函数作为参数,参数名是 func

    String => Unit 注意此处不是 Lambda 表达式,而是表示 func 的参数类型是 String,返回值类型是 Unit

集合

Scala 中的集合分为三类:Seq 序列、Set 集、Map 映射,所有集合都扩展自 Iterable 特质

对于大部分集合类,Scala 都同时具有可变和不可变两种版本,分别位于 scala.collection.mutablescala.collection.immutable

不可变集合

不可变集合是指该集合对象不可修改,一旦发生修改就会返回一个新对象,而原对象保持不变,类似于 Java 中的 String

image-20230117164519657

  • Seq 是 Java 当中没有的,其中 List 属于 Seq
  • IndexedSeq 和 LinearSeq
    • IndexedSeq 通过索引进行查找定位,速度快,如 String 就是一个索引集合
    • LinearSeq 为线性集合,一般通过遍历进行查找

可变集合

可变集合是指可以对该集合直接进行修改,不会返回新对象,类似于 Java 中的 StringBuilder

image-20230117165823037

数组 Array

不可变数组

  • 第一种方法定义:val array = new Array[Int](10)

    [Int] 表示元素类型

    (10) 表示数组大小

  • val array = Array(666, 777)

可变数组 ArrayBuffer

val arr = ArrayBuffer[Any](555, 666, 777)

[Any] 表示可存放任意数据类型

//(1)创建并初始赋值可变数组
val arrayBuffer = ArrayBuffer[Any](1, 2, 3)
//(2)遍历数组
for (i <- arrayBuffer) {
    println(i)
}
println(arrayBuffer.length) // 3
println("arr01.hash=" + arrayBuffer.hashCode())
//(3)增加元素
// 追加数据
arrayBuffer.+=(4)
// 向数组最后追加数据
arrayBuffer.append(5, 6)
// 向指定的位置插入数据
arrayBuffer.insert(0, 7, 8)
println("arr01.hash=" + arrayBuffer.hashCode())
//(4)修改元素
arrayBuffer(1) = 9 //修改第 2 个元素的值
println("--------------------------")
for (i <- arrayBuffer) {
    println(i)
}
println(arrayBuffer.length) // 5

两者相互转换

//不可变数组转可变数组
arr1.toBuffer

//可变数组转不可变数组
arr2.toArray 

多维数组

val array = Array.ofDim[Double](3,4)

该二维数组中有 3 个一维数组,每个一维数组有 4 个元素

列表 List

不可变列表

val list1 = List(666, 777)
println(list1)

val list2 = 666 :: 777 :: Nil
println(list2)
List(666, 777)
List(666, 777)

两种方式均可生成相同的列表

双冒号

image-20230120173745362

:: 双冒号实际是 Scala 的 List 抽象类中的一个方法

x :: AA.::(x) 作用是插入元素 x 至列表 A 头部,并返回新生成的列表

三冒号

image-20230120174446641

::: 三冒号也是 List 抽象类中的方法

val list1 = List(666, 777)
val list2 = List(888, 999)

println(list1 :: list2)
println(list1 ::: list2)
List(List(666, 777), 888, 999)
List(666, 777, 888, 999)

当列表 A 和列表 B 调用 :: 双冒号时,返回的是一个嵌套结构的新列表

当列表 A 和列表 B 调用 ::: 三冒号时,返回的是包含两者所有元素的新列表(扁平化,拆开 + 重组)

可变列表 ListBuffer

//(1)创建一个可变集合
val buffer = ListBuffer(1, 2, 3, 4)
//(2)向集合中添加数据
buffer.+=(5)
buffer.append(6)
buffer.insert(1, 2)
//(3)打印集合数据
buffer.foreach(println)
//(4)修改数据
buffer(1) = 6
buffer.update(1, 7)
//(5)删除数据
buffer.-(5)
buffer.-=(5)
buffer.remove(5)

集 Set

默认情况下,Scala 使用不可变集合,如果想使用可变集合,需要引用 scala.collection.mutable.Set

不可变 Set

Set 默认是不可变集合,数据无序,且数据不可重复

val set = Set(1, 2, 3, 4, 5, 6, 3)

for (x <- set) {
    println(x)
}
5
1
6
2
3
4

可变 Set

//(1)创建可变集合
val set = mutable.Set(1, 2, 3, 4, 5, 6)
//(2)集合添加元素
set += 8
//(3)向集合中添加元素,返回一个新的 Set
val ints = set.+(9)
println(ints)
println("set2=" + set)
//(4)删除数据
set -= (5)
//(5)打印集合
set.foreach(println)
println(set.mkString(","))
Set(9, 1, 5, 2, 6, 3, 4, 8)
set2=Set(1, 5, 2, 6, 3, 4, 8)
1
2
6
3
4
8
1,2,6,3,4,8

映射 Map

与 Java 类似,Scala 中的 Map 也是一个散列表,存储内容是键值对 key-value 映射

不可变 Map

//(1)创建不可变集合 Map
val map = Map("a" -> 1, "b" -> 2, "c" -> 3)
//(2)访问数据
// 使用 get 访问 map 集合的数据,会返回特殊类型 Option(选项): 有值 (Some ),无值(None)
for (elem <- map.keys) {
    println(elem + "=" + map.get(elem).get)
}
//(3)如果 key 不存在,返回 0
println(map.get("d").getOrElse(0))
println(map.getOrElse("d", 0))
//(4)循环打印
map.foreach((kv) => {
    println(kv)
})
a=1
b=2
c=3
0
0
(a,1)
(b,2)
(c,3)

可变 Map

//(1)创建可变集合
val map = mutable.Map("a" -> 1, "b" -> 2, "c" -> 3)
//(2)向集合增加数据
// 将数值 4 添加到集合,并把集合中原值 1 返回
map.+=("d" -> 4)
val maybeInt: Option[Int] = map.put("a", 4)
println(maybeInt.getOrElse(0))
//(3)删除数据
map.-=("b", "c")
//(4)修改数据
map.update("d", 5)
map("d") = 5
//(5)打印集合
map.foreach((kv) => {
    println(kv)
})
1
(d,5)
(a,4)

元组 Tuple

元组可以理解为一个容器,可以存放各种相同或不同类型的数据

即多个无关的数据封装为一个整体,称为元组

元组中最大只能有 22 个元素

//(1)声明元组的方式:(元素 1,元素 2,元素 3)
val tuple: (Int, String, Boolean) = (40, "bobo", true)
//(2)访问元组
// 通过元素的顺序进行访问,调用方式:_顺序号
println(tuple._1)
println(tuple._2)
println(tuple._3)
// 通过索引访问数据
println(tuple.productElement(0))
// 通过迭代器访问数据
for (elem <- tuple.productIterator) {
    println(elem)
}
//(3)Map 中的键值对其实就是元组,只不过元组的元素个数为 2,称之为 对偶
val map = Map("a" -> 1, "b" -> 2, "c" -> 3)
val map1 = Map(("a", 1), ("b", 2), ("c", 3))
map.foreach(tuple => {
    println(tuple._1 + "=" + tuple._2)
})

队列 Queue

val queue = new mutable.Queue[String]()

queue.enqueue("a", "b", "c")

println(queue.dequeue())
println(queue.dequeue())
println(queue.dequeue())
a
b
c

并行集合

Scala 为了充分使用多核 CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算

// 串行
val result1 = (0 to 10).map { case _ =>
    Thread.currentThread.getName
}

//并行
val result2 = (0 to 10).par.map { case _ =>
    Thread.currentThread.getName
}

println(result1)
println(result2)
Vector(main, main, main, main, main, main, main, main, main, main, main)

ParVector(scala-execution-context-global-12, scala-execution-context-global-12, scala-execution-context-global-14, scala-execution-context-global-14, scala-execution-context-global-14, scala-execution-context-global-13, scala-execution-context-global-16, scala-execution-context-global-16, scala-execution-context-global-15, scala-execution-context-global-15, scala-execution-context-global-15)

集合常用函数

基本操作

val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7)
//(1)获取集合长度
println(list.length)
//(2)获取集合大小,等同于 length
println(list.size)
//(3)循环遍历
list.foreach(println)
//(4)迭代器
for (elem <- list.iterator) {
    println(elem)
}
//(5)生成字符串
println(list.mkString(","))
//(6)是否包含
println(list.contains(3))

衍生集合

val list1: List[Int] = List(1, 2, 3, 4, 5, 6, 7)
val list2: List[Int] = List(4, 5, 6, 7, 8, 9, 10)
//(1)获取集合的头
println(list1.head)
//(2)获取集合的尾(除去头的其余所有元素)
println(list1.tail)
//(3)集合最后一个数据
println(list1.last)
//(4)集合初始数据(不包含最后一个)
println(list1.init)
//(5)反转
println(list1.reverse)
//(6)取前(后)n 个元素
println(list1.take(3))
println(list1.takeRight(3))
//(7)去掉前(后)n 个元素
println(list1.drop(3))
println(list1.dropRight(3))
//(8)并集
println(list1.union(list2))
//(9)交集
println(list1.intersect(list2))
//(10)差集
println(list1.diff(list2))

println("===========")

//(11)拉链
// 如果两个集合的元素个数不相等,那么会将同等数量的数据进行拉链 ,多余的数据省略不用
println(list1.zip(list2))
//(12)滑窗
list1.sliding(2, 5).foreach(println)
1
List(2, 3, 4, 5, 6, 7)
7
List(1, 2, 3, 4, 5, 6)
List(7, 6, 5, 4, 3, 2, 1)
List(1, 2, 3)
List(5, 6, 7)
List(4, 5, 6, 7)
List(1, 2, 3, 4)
List(1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 7, 8, 9, 10)
List(4, 5, 6, 7)
List(1, 2, 3)
===========
List((1,4), (2,5), (3,6), (4,7), (5,8), (6,9), (7,10))
List(1, 2)
List(6, 7)

集合计算 简单函数

val list: List[Int] = List(1, 5, -3, 4, 2, -7, 6)
//(1)求和
println(list.sum)
//(2)求乘积
println(list.product)
//(3)最大值
println(list.max)
//(4)最小值
println(list.min)
//(5)排序
// 按照元素大小排序
println(list.sortBy(x => x))
// 按照元素的绝对值大小排序
println(list.sortBy(x => x.abs))
// 按元素大小升序排序
println(list.sortWith((x, y) => x < y))
// 按元素大小降序排序
println(list.sortWith((x, y) => x > y))
8
5040
6
-7
List(-7, -3, 1, 2, 4, 5, 6)
List(1, 2, -3, 4, 5, 6, -7)
List(-7, -3, 1, 2, 4, 5, 6)
List(6, 5, 4, 2, 1, -3, -7)
  • sorted

    对一个集合进行自然排序,通过传递隐式的 Ordering

  • sortBy

    对一个属性或多个属性进行排序,通过它的类型

  • sortWith

    基于函数的排序,通过一个 comparator 函数,实现自定义排序的逻辑

集合计算 高级函数

val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val nestedList: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
val wordList: List[String] = List("hello world", "hello hi", "hello scala")
//(1)过滤
println(list.filter(x => x % 2 == 0))
//(2)转化/映射
println(list.map(x => x + 1))
//(3)扁平化
println(nestedList.flatten)
//(4)扁平化+映射,flatMap 相当于先进行 map 操作,再进行 flatten 操作
// 主要用于分词
println(wordList.flatMap(x => x.split(" ")))
//(5)分组
println(list.groupBy(x => x % 2))
List(2, 4, 6, 8)
List(2, 3, 4, 5, 6, 7, 8, 9, 10)
List(1, 2, 3, 4, 5, 6, 7, 8, 9)
List(hello, world, hello, hi, hello, scala)
Map(1 -> List(1, 3, 5, 7, 9), 0 -> List(2, 4, 6, 8))

Reduce 操作

Reduce 简化(归约)

通过指定规则,将集合中的数据进行聚合,获取结果

val list = List(1,2,3,4)

// 将数据从左往右逐个相减,并基于上一次结果实现运算
// reduce 底层调用的其实就是 reduceLeft
// val i1 = list.reduceLeft((x,y) => x-y)
val i1 = list.reduce((x, y) => x - y)
println("i1 = " + i1)

val i2 = list.reduceRight((x, y) => x - y)
println("i2 = " + i2)
i1 = -8
i2 = -2

Fold 操作

Fold 与 Reduce 操作类似,区别在于 Fold 有初始值,且 foldRight 运算过程不同

val list = List(1, 2, 3, 4)
// fold 方法使用了函数柯里化,存在两个参数列表
// 第一个参数为零值(初始值),第二个参数为简化规则
// fold 底层为 foldLeft
val i1 = list.foldLeft(1)((x, y) => x - y)
val i2 = list.foldRight(10)((x, y) => x - y)
println(i1)
println(i2)
-9
8

注意 i2 的实际运算过程:

1 - (2 - (3 - (4 - 10)))

Map 合并

此处合并是相加,而不是覆盖

// 两个 Map 的数据合并
val map1 = mutable.Map("a" -> 1, "b" -> 2, "c" -> 3)
val map2 = mutable.Map("a" -> 4, "b" -> 5, "d" -> 6)

val map3: mutable.Map[String, Int] = map2.foldLeft(map1) {
    (map, kv) => {
        val k = kv._1
        val v = kv._2
        map(k) = map.getOrElse(k, 0) + v
        map
    }
}

println(map3)
Map(b -> 7, d -> 6, a -> 5, c -> 3)

模式匹配

Scala 中的模式匹配类似于 Java 中的 switch

Java

int i = 10;
switch (i) {
    case 10 :
        System.out.println("10");
        break;
    case 20 :
        System.out.println("20");
        break;
    default :
        System.out.println("other number");
        break;
}

Java 语法中,switch 括号里的值只能是整型、字符型、字符串,即 byte、short、int、char、String 类型

case 后面的值只能是常量、常量表达式,包括整型、字符型、字符串、枚举类型

Scala

val i = 10
i match {
    case 10 => println("10")
    case 20 => println("20")
    case _ => println("other number")
}

Scala 的模式匹配相对于 Java,扩展了更多功能,更加强大

基本语法

var a: Int = 10
var b: Int = 20
var operator: Char = '*'

var result = operator match {
    case '+' => a + b
    case '-' => a - b
    case '*' => a * b
    case '/' => a / b
    case _ => "illegal"
}

println(result)
200
  • 如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句

    若此时没有 case _ 分支,那么会抛出 MatchError

  • 每个 case 中,不需要使用 break 语句,自动中断 case

  • match case 语句可以匹配任何类型,而不只是字面量

  • => 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,可以使用{}括起来,也可以不括

模式守卫

如果想要匹配某个范围的数据,就需要增加条件守卫

def abs(x: Int) = x match {
    case i: Int if i >= 0 => i
    case j: Int if j < 0 => -j
    case _ => "type illegal"
}

println(abs(-5))
5

模式匹配扩展

匹配常量

Scala 中模式匹配可以匹配所有的字面量,包括字符串、字符、数字、布尔值等

def main(args: Array[String]): Unit = {
    println(describe('+'))
}

def describe(x: Any) = x match {
    case 5 => "Int five"
    case "hello" => "String hello"
    case true => "Boolean true"
    case '+' => "Char +"
}
Char +

匹配类型

def describe(x: Any) = x match {
    case i: Int => "Int"
    case s: String => "String hello"
    case m: List[_] => "List"
    case c: Array[Int] => "Array[Int]"
    case someThing => "something else " + someThing
}

def main(args: Array[String]): Unit = {
    //泛型擦除
    println(describe(List(1, 2, 3, 4, 5)))
    //数组例外,可保留泛型
    println(describe(Array(1, 2, 3, 4, 5, 6)))
    println(describe(Array("abc")))
}
List
Array[Int]
something else [Ljava.lang.String;@1b604f19

匹配数组

Scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的,且第一个元素 为 0 的数组

// 对一个数组集合进行遍历
for (arr <- Array(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) => x + "," + y
        // 匹配以 0 开头和数组
        case Array(0, _*) => "以 0 开头的数组" 
        case _ => "something else"
    }
    println("result = " + result)
}
result = 0
result = 1,0
result = 以 0 开头的数组
result = something else
result = something else
result = hello,90

匹配列表

方式一

// lists 是一个存放 List 集合的数组
// 下面的 for 循环依次遍历五个 List,并进行判断
for (lists <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0), List(88))) {
    val result = lists match {
        //匹配 List(0)
        case List(0) => "0"
        //匹配有两个元素的 List
        case List(x, y) => x + "," + y
        //匹配 0 开头的数组
        case List(0, _*) => "0 ..."
        case _ => "something else"
    }
    println(result)
}
0
1,0
0 ...
something else
something else

方式二

val list: List[Int] = List(1, 2, 5, 6, 7)

list match {
    case first :: second :: rest => println(first + "-" +
      second + "-" + rest)
    case _ => println("something else")
}
1-2-List(5, 6, 7)

匹配元组

// 对一个元组集合进行遍历
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)
}
0 ...
10
1 1
something else

匹配对象

object Test {
    def main(args: Array[String]): Unit = {
        val student = new Student("alice", 18)
        
        val result = student match {
            case Student("alice", 18) => "name alice, age 18"
            case _ => "something else"
        }
        
        println(result)
    }
}

// 定义 Student 类
class Student(val name: String, val age: Int)

// 定义 Student 伴生对象
object Student {
    def apply(name: String, age: Int): Student = new Student(name, age)
    
    // 必须实现 unapply 方法,用于拆解对象属性
    def unapply(student: Student): Option[(String, Int)] = {
        if (student == null){
            None
        } else {
            Some((student.name, student.age))
        }
    }
}
name alice, age 18
样例类

上述匹配对象代码比较复杂,样例类对其进行了优化

  • 样例类与一般类相比,可自动生成伴生对象,且伴生对象中自动提供了一些常用方法,如 applyunapplytoStringequalshashCodecopy

  • 样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例类可以直接使用模式匹配,而无需自己实现 unapply 方法

  • 构造器中的每一个参数都成为 val,除非它被显式地声明为 var(不建议这样做)

object Test {
    def main(args: Array[String]): Unit = {
        val user: User = User("zhangsan", 20)
        val result = user match {
            case User("zhangsan", 20) => "name zhangsan, age 20"
            case _ => "no"
        }
        println(result)
    
    }
}

case class User(name: String, age: Int)
name zhangsan, age 20

异常

Scala 异常处理语法上和 Java 类似,但一些细节有所不同

Java

public class TestJava {
    public static void main(String[] args) {
        try {
            int a = 10;
            int b = 0;
            int c = a / b;
        }catch (ArithmeticException e){
            // catch 时,需要将范围小的写到前面
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("finally");
        }
    }
}
  • Java 语言按照 try—catch—finally 的方式来处理异常
  • 不管有没有异常捕获,都会执行 finally,因此通常可以在 finally 代码块中释放资 源
  • 可以有多个 catch 分别捕获对应的异常,这时需要把范围小的异常类写在前面, 范围大的异常类写在后面,否则编译错误

Scala

object Test {
    def main(args: Array[String]): Unit = {
        try {
            var n = 10 / 0
        } catch {
            case ex: ArithmeticException => {
                // 发生算术异常
                println("发生算术异常")
            }
            case ex: Exception => {
                // 对异常处理
                println("发生了异常 1")
                println("发生了异常 2")
            }
        } finally {
            println("finally")
        }
    }
}
  • 将可疑代码封装在 try 块中,并使用了 catch 来捕获异常,若发生异常,则 catch 将进行处理,且程序不会终止
  • Scala 的异常的工作机制和 Java 一样,但是 Scala 没有 checked 异常(即编译时异常), 异常都是在运行时捕获处理
  • throw 关键字抛出异常对象,所有异常都是 Throwable 的子类型,另外 throw 表达式是有类型的,就是 Nothing

隐式转换

当编译器第一次编译失败的时候,会在当前的环境中查找能让代码编译通过的方法,将类型进行转换,实现二次编译

隐式函数

隐式转换可以在不改动任何代码的情况下,扩展某个类的功能

object Test {
    def main(args: Array[String]): Unit = {
        // 使用 implicit 关键字声明的函数,称之为隐式函数
        implicit def convert(arg: Int): MyRichInt = {
            new MyRichInt(arg)
        }
    
        // 此处通过隐式函数,自动把 2 转换为了 MyRichInt
        println(2.myMax(6))
    }
}

class MyRichInt(val self: Int) {
    def myMax(i: Int): Int = {
        if (self < i) 
            i 
        else 
            self
    }
    def myMin(i: Int): Int = {
        if (self < i) 
            self 
        else 
            i
    }
}

当想调用对象某一功能时,如果编译错误,那么编译器会尝试在当前作用域内,查找对应功能的转换规则

这个调用过程是由编译器完成的,所以称之为隐式转换,也称之为自动转换

隐式参数

object Test {
    def main(args: Array[String]): Unit = {
        implicit val str: String = "good world"
        
        def hello(implicit str: String): Unit = {
            println(str)
        }
    
        // 此处调用 hello 函数并没有主动传入参数,但编译器找到了隐式参数
        hello
    }
}
good world

泛型

协变和逆变

class MyList[+T]{ //协变
}
class MyList[-T]{ //逆变
}
class MyList[T] //不变

SonFather 的子类

  • 协变: MyList[Son] 作为 MyList[Father] 的子类

  • 逆变: MyList[Son] 作为 MyList[Father] 的父类

  • 不变:MyList[Father]MyList[Son] 无父子关系

泛型上下限

Class PersonList[T <: Person]{ //泛型上限
}
Class PersonList[T >: Person]{ //泛型下限
}

泛型的上下限的作用是对传入的泛型进行限定

object Test {
    def main(args: Array[String]): Unit = {
        test(classOf[SubChild])
        test[Child](new SubChild)
    }
    
    //泛型通配符之上限
    def test[A <: Child](a:Class[A]): Unit ={
     println(a)
    }
    //泛型通配符之下限
//    def test[A >: Child](a:Class[A]): Unit ={
//     println(a)
//    }
    //泛型通配符之下限 形式扩展
    def test[A >: Child](a: A): Unit = {
        println(a.getClass.getName)
    }
}

class Parent{}
class Child extends Parent{}
class SubChild extends Child{}
class org.example.SubChild
org.example.SubChild