• 一文学会Scala【Scala一站式学习笔记】


    为什么要学习Scala语言

    最直接的一点就是因为我们后面要学的Spark框架需要用到Scala这门语言。但是Spark其实是同时支持Scala语言和Java语言的,为什么非要学Scala呢,使用java它难道不香吗?
    这就要说第二点了:看下面的代码,使用Spark统计文件内单词出现的次数这个需求,使用java代码和scala代码的区别是有多么的明显,在代码量上来说,scala是完胜java的,所以在实际工作中开发spark代码,我们都是需要使用scala的,使用java实现函数式编程太别扭了,代码量太大,这个就是我们学习scala的最直接的原因。

    image.png

    image.png

    什么是Scala

    Scala是一门多范式的编程语言,它是一种类似Java的编程语言,它设计的初衷是为了实现可伸缩的语言、并集成面向对象编程和函数式编程的各种特性。Scala基于Java虚拟机,也就是基于JVM的一门编程语言。所有Scala代码,都需要编译为字节码,然后交由Java虚拟机来运行。
    Scala和Java可以无缝相互操作,Scala可以任意调用Java代码,这个特性是非常好的

    如何快速掌握Scala语言

    scala语言本身是很反人类的,特别是你熟悉了例如java之类的编程语言之后再来学scala,会感觉到既爱又恨
    那我们如何快速掌握Scala语言的使用呢?
    首先大家在学习的过程中需要对比分析Java和Scala在使用上的区别,这样可以加深我们的理解。然后没事的时候可以尝试着用Scala代码改写你之前的Java代码。

    最后的最后,交给大家一个绝招,一个终极方案,那就是多练!多练!多练。因为针对编程语言,其实就是需要掌握它的一些基本语法,大家都是有其它语言编程基础的,在这学习Scala语言,其实主要就是掌握这门语言和其它语言的一些不同之处,只要把这些东西捋顺了,那上手也是很容易的。

    Scala环境安装配置

    注意:由于Scala是基于Java虚拟机的,所以使用 Scala 之前必须先安装 Java,Java我们已经安装过了。那在这里我们先到官网下载Scala安装包
    Scala现在有三个主要在使用的版本, 2.11,2.12,2.13。目前的话2.12使用的比较多,所以我们就使用这个版本

    image.png

    image.png

    下载好了之后就需要安装了
    安装很简单,直接解压就可以了,在这里我们把scala的安装包解压到了D盘下面的scala目录下

    D:\scala\scala-2.12.11 
    
    • 1

    接下来就该配置SCALA_HOME和PATH环境变量了,和Java的操作是一样的

    SCALA_HOME=D:\scala\scala-2.12.11
    PATH=...%SCALA_HOME%\bin...
    
    • 1
    • 2

    进入cmd命令行,验证一下,输入scala命令,可以看到下面界面就说明安装配置成功了。

    C:\Users\yehua>scala
    Welcome to Scala version 2.10.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.
    Type in expressions to have them evaluated.
    Type :help for more information.
    scala>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Scala命令行

    Scala命令行也称为Scala解释器(REPL),它会快速编译Scala代码为字节码,然后交给JVM来执行
    这里的REPL表示:Read(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循环)
    在Scala命令行内,输入Scala代码,解释器会直接返回结果
    如果你没有指定变量来存放计算的值,那么值默认的名称会显示为res开头的变量,而且会显示结果的数据类型

    scala> 1+1
    res0: Int = 2
    
    • 1
    • 2

    在后面可以继续使用res0这个变量,以及它里面存放的值
    例如:

    scala> 5 * res0
    res1: Int = 10
    
    • 1
    • 2

    scala的命令行也有自动补全功能,使用起来还是比较方便的
    输入res,按键盘上的tab键,下面就会列出目前以res开头的变量名称

    Scala的基本使用

    变量

    Scala中的变量分为两种:可变 var 和 不可变 val

    • 可变var:可以随时修改var声明的变量的值
    • 不可变val:val声明的变量,值不能被修改,否则会报错: error: reassignment to val
    scala> var a = 1
    a: Int = 1
    scala> a = 2
    a: Int = 2
    scala> val b = 1
    b: Int = 1
    scala> b = 2
    <console>:8: error: reassignment to val
    b = 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意:在实际工作中,针对一些不需要改变值的变量,通常建议使用val,这样可以不用担心值被错误的修改(等于java中的final类型)。这样可以提高系统的稳定性和健壮性!

    无论声明val变量,还是声明var变量,都可以手动指定变量的类型
    如果不指定,Scala会自动根据值,进行类型推断 val c = 1 等价于 val c: Int = 1

    scala> val c = 1
    c: Int = 1
    scala> val c: Int = 1
    c: Int = 1
    
    • 1
    • 2
    • 3
    • 4

    数据类型

    Scala中的数据类型可以分为两种,基本数据类型和增强版数据类型
    基本数据类型有: Byte、Char、Short、Int、Long、Float、Double、Boolean
    增强版数据类型有: StringOps、RichInt、RichDouble、RichChar 等。scala使用这些增强版数据类给基本数据类型增加了上百种增强的功能
    例如:RichInt提供的有一个to函数, 1.to(10) ,此处Int会先隐式转换为RichInt,然后再调用其to函数

    scala> 1.to(10)
    res2: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7,8,9,10)
    
    • 1
    • 2

    注意,to函数还可以这样写

    scala> 1 to 10
    res3: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7,8,9,10)
    
    • 1
    • 2

    使用基本数据类型,直接就可以调用RichInt中对应的函数

    scala> 1.toString()
    res4: String = 1
    
    • 1
    • 2

    操作符

    Scala的算术操作符与Java的算术操作符没有什么区别
    比如 +、-、*、/、% 等,以及 &、|、^、>>、<< 等
    注意:Scala中没有提供++、–操作符
    我们只能使用+和- ,比如count = 1,count++是错误的,必须写做count += 1

    scala> var count = 1
    count: Int = 1
    scala> count++
    <console>:9: error: value ++ is not a member of Int
    count++
    ^
    scala> count += 1
    scala> count
    res8: Int = 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    if 表达式

    在Scala中,if表达式是有返回值的,就是if或者else中最后一行语句返回的值,这一点和java中的if是不一样的,java中的if表达式是没有返回值的
    例如: val age = 20; if (age > 18) 1 else 0

    scala> val age = 20
    age: Int = 20
    scala> if(age > 18) 1 else 0
    res9: Int = 1
    
    • 1
    • 2
    • 3
    • 4

    在这因为if表达式是有返回值的,所以可以将if表达式赋予一个变量

    scala> val res = if(age > 18) 1 else 0
    res: Int = 1
    
    • 1
    • 2

    由于if表达式是有值的,而if和else子句的值的类型可能还不一样,此时if表达式的值是什么类型呢?
    注意:Scala会自动进行推断,取两个类型的公共父类型

    例如,if(age > 18) 1 else 0,表达式的类型是Int,因为1和0都是Int
    例如,if(age > 18) “old” else 0,此时if和else的值分别是String和Int,则表达式的值是Any类型,Any是String和Int的公共父类型

    scala> if(age > 18) 1 else 0
    res12: Int = 1
    scala> if(age > 18) "old" else 0
    res13: Any = old
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果if后面没有跟else,则默认else的值是Unit,也可以用()表示,类似于java中的void或者null
    例如,val age = 12; if(age > 18) “old”。此时就相当于if(age > 18) “old” else ()。
    此时表达式的值是Any

    scala> if(age > 18) "old" else ()
    res17: Any = ()
    
    • 1
    • 2

    如果想在scala REPL中执行多行代码,该如何操作?
    使用 :paste 和 ctrl+D 的方式
    :paste 表示代码块的开始
    ctrl+D 表示代码块的结束

    语句终结符

    Scala默认不需要语句终结符,它将每一行作为一个语句
    如果一行要放多条语句,则前面的语句必须使用语句终结符
    语句终结符和Java中的一样,就是我们平时使用的分号

    scala> val age = 20; if(age > 18) 1 else 0
    age: Int = 20
    res0: Int = 1
    
    • 1
    • 2
    • 3

    循环

    • print和println
      在讲循环之前,先来看一下打印命令print和println
      print打印时不会加换行符,而println打印时会加一个换行符,这个特性和Java中的打印语句的特性是一样的
    • for循环
      for循环本身的特性就没什么好说的了,直接上案例,主要注意一下scala中的for和java中的for在语法层面的区别
    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    val n = 10
    for(i <- 1 to n)
    println(i)
    // Exiting paste mode, now interpreting.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    n: Int = 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这里面的to可以换成until

    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    val n = 10
    for(i <- 1 until 10)
    println(i)
    // Exiting paste mode, now interpreting.
    1
    2
    3
    4
    5
    6
    7
    8
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    对比两次执行的结果发现
    1 to 10 可以获取1~10之间的所有数字
    1 until 10可以获取1~9之间的所有数字

    所以在这需要注意了,to 和 until 其实都是函数,一个是闭区间,一个是开区间,具体用哪个就要看你的需求了

    for循环针对字符串还可以用

    scala> for(c <- "hello scala") println(c)
    h
    e
    l
    l
    o
    s
    c
    a
    l
    a
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意:在这里我在for循环后面没有使用花括号,都省略了,主要是因为for循环的循环体代码就只有一行,如果有多行,就需要使用花括号了,否则,最终执行的结果就不是我们想要的

    • while循环
      while循环,它的用法和java中的while也是很像的,主要看一下语法层面的区别
    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    var n = 10
    while(n>0){
    println(n)
    n -= 1
    }
    // Exiting paste mode, now interpreting.
    10
    9
    8
    7
    6
    5
    4
    3
    2
    1
    n: Int = 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    高级for循环

    最后来看一下高级for循环的用法
    if守卫
    if守卫模式,假设我们想要获取1~10之间的所有偶数,使用普通的for循环,需要把每一个数字都循环出来,然后判断是否是偶数。
    如果在for循环里面使用if守卫,可以在循环的时候就执行一定的逻辑,判断数值是否是偶数。

    scala> for(i <- 1 to 10 if i % 2 == 0) println(i)
    2
    4
    6
    8
    10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这是if守卫模式的用法
    for推导式
    for推导式,一个典型例子是构造集合

    我们在使用for循环迭代数字的时候,可以使用yield指定一个规则,对迭代出来的数字进行处理,并且创建一个新的集合。

    scala> for(i <- 1 to 10) yield i *2
    res16: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10, 12,14,16,18,20)
    
    • 1
    • 2

    Scala的集合体系

    Scala中的集合体系,集合在工作中属于经常使用的数据结构

    首先看一下整个集合体系结构,这个结构与Java的集合体系非常相似

    image.png

    集合的顶层接口是Iterable,Iterable接口下面还有一些子接口, Set、Seq、Map
    这几个子接口下面有具体的实现类

    • set下面有HashSet、LinkedHashSet、SortedSet等等
    • seq下面有List、Buffer、Range等等
    • Map下面有HashMap、SortedMap、LinkedHashMap等等
    • 其中Buffer下面还有两个常用的,ArrayBuffer、ListBuffer

    这个集合体系的时候,还会关联讲到 Array和Tuple 这两个数据结构

    集合

    Scala中的集合是分成可变和不可变两类集合的

    • 其中可变集合就是说,集合的元素可以动态修改
    • 而不可变集合就是说,集合的元素在初始化之后,就无法修改了

    可变集合:在 scala.collection.mutable 这个包下面
    不可变集合:在 scala.collection.immutable 这个包下面
    我们在创建集合的时候,如果不指定具体的包名,默认会使用不可变集合

    Set

    Set代表一个没有重复元素的集合
    这个集合的特性和Java中Set集合的特性基本一样
    Set集合分为可变的和不可变的集合,默认情况下使用的是不可变集合
    Set可以直接使用,并且不需要使用new关键字,来看一下

    scala> val s = Set(1,2,3)
    s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
    
    • 1
    • 2

    本来Set是一个接口,但是却可以创建对象,更神奇的是竟然还不需要使用new关键字,这就有点颠覆我们的认知了

    注意了,在学习Scala的时候,可以拿Java来进行对比,加深理解,但是不要全部拿Java里面的知识点来硬套,因为它们两个有些地方还是不一样的。

    image.png

    只要前面带有object的,可以直接创建对象,并且不需要使用new关键字所以set可以直接使用。

    注意:默认情况下直接创建的set集合是一个不可变集合,在这可以看到是在immutable包里面
    的,不可变集合中的元素一经初始化,就不能改变了,所以初始化后再向里面添加元素就报错了。

    
    scala> val s = Set(1,2,3)
    s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
    scala> s += 4
    <console>:9: error: value += is not a member of scala.collection.immutable.Set
    s += 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    但是注意,我使用s + 4 这种操作是可以的, 因为 s + 4 返回的是一个新的集合了,相当于在之前的集合的基础上,创建一个新的集合,新的集合包含之前集合的元素和我们新增的4这个元素这个大家需要能够区分开。如果想要创建一个可变的set集合,可以使用mutable包下面的set集合,显式指定包名

    scala> val s = scala.collection.mutable.Set(1,2,3)
    s: scala.collection.mutable.Set[Int] = Set(1, 2, 3)
    scala> s += 4
    res34: s.type = Set(1, 2, 3, 4)
    
    • 1
    • 2
    • 3
    • 4

    Set常用子类有: HashSet、LinkedHashSet、SortedSet

    • HashSet:这个集合的特点是:集合中的元素不重复、无序
    • LinkedHashSet:这个集合的特点是:集合中的元素不重复、有序,它会用一个链表维护插入顺序,可以保证集合中元素是有序的
    • SortedSet:这个集合的特点是:集合中的元素不重复、有序,它会自动根据元素来进行排序

    先看HashSet

    image.png

    HashSet集合分为可变和不可变之分, immutable 包下面的是不可变的,后期无法新增元素。
    在这里可以使用new关键字,也可以不使用,因为HashSet既是class,又是object,但是包名需要指定,否则无法识别

    scala> val s = new scala.collection.mutable.HashSet[Int]()
    s: scala.collection.mutable.HashSet[Int] = Set()
    scala> s +=1
    res35: s.type = Set(1)
    scala> s +=2
    res36: s.type = Set(1, 2)
    scala> s +=5
    res38: s.type = Set(1, 5, 2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    List

    List属于Seq接口的子接口,List代表一个不可变的列表

    image.png

    创建一个list

    scala> val l = List(1, 2, 3, 4)
    l: List[Int] = List(1, 2, 3, 4)
    
    • 1
    • 2

    注意:为什么有的地方需要写类的全路径,而有的不需要呢?
    由于immutable包是默认导入的,所以不需要导包,但是也会有个别虽然在immutable包下面的,但是不写全路径还是报错,原谅它把,反正你都带全路径肯定是没有问题的,后期我们会使用idea来开发,也不需要考虑包名的问题,不过在这为了演示起来更加清晰,就使用scala的命令行了。
    针对List有 head 、 tail 以及 :: 这几个操作
    先演示一下 head、tail 操作

    scala> l.head
    res49: Int = 1
    scala> l.tail
    res51: List[Int] = List(2, 3, 4)
    
    • 1
    • 2
    • 3
    • 4
    • head:表示获取List中的第一个元素
    • tail:表示获取List中第一个元素之后的所有元素

    那其实head和tail就可以获取list中的所有元素了
    通过 :: 操作符,可以将head和tail的结果合并成一个List

    scala> l.head :: l.tail
    res52: List[Int] = List(1, 2, 3, 4)
    
    • 1
    • 2

    针对List中的元素进行迭代和前面讲的Set集合的迭代是一样的

    scala> val l = List(1, 2, 3, 4)
    l: List[Int] = List(1, 2, 3, 4)
    scala> for(i <- l) println(i)
    1
    2
    3
    4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里List是不可变的列表,在实际工作中使用的时候会很不方便,因为我们很多场景下都是需要向列表中动态添加元素,这个时候该怎么办呢?
    Scala还提供的有一个ListBuffer
    ListBuffer:可以支持动态增加或者移除元素

    scala> val lb = scala.collection.mutable.ListBuffer[Int]()
    lb: scala.collection.mutable.ListBuffer[Int] = ListBuffer()
    scala> lb +=1
    res56: lb.type = ListBuffer(1)
    scala> lb +=2
    res57: lb.type = ListBuffer(1, 2)
    scala> lb +=5
    res58: lb.type = ListBuffer(1, 2, 5)
    scala> lb -=5
    res59: lb.type = ListBuffer(1, 2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ListBuffer也可以直接使用for循环迭代

    scala> for(i <- lb) println(i)
    1
    2
    
    • 1
    • 2
    • 3

    Map

    Map是一种可迭代的键值对(key/value)结构

    创建一个不可变的Map

    scala> val ages = Map("jack"->30,"tom"->25,"jessic"->23)
    ages: scala.collection.immutable.Map[String,Int] = Map(jack -> 30, tom -> 25,
    scala> ages("jack")
    res100: Int = 30
    
    • 1
    • 2
    • 3
    • 4

    创建一个可变的Map

    scala> val ages = scala.collection.mutable.Map("jack"->30,"tom"->25,"jessic"-
    ages: scala.collection.mutable.Map[String,Int] = Map(jessic -> 23, jack -> 30
    scala> ages("jack")
    res101: Int = 30
    
    • 1
    • 2
    • 3
    • 4

    还有一种创建Map的简易方式,这种方式创建的是不可变Map

    scala> val ages = Map(("jack",30),("tom",25),("jessic"->23))
    ages: scala.collection.immutable.Map[String,Int] = Map(jack -> 30, tom -> 25,
    
    • 1
    • 2

    更新map中的元素

    scala> ages("jack") = 31
    scala> ages
    res104: scala.collection.mutable.Map[String,Int] = Map(jessic -> 23, jack ->
    
    • 1
    • 2
    • 3

    增加多个元素

    scala> ages += ("hehe" -> 35, "haha" -> 40)
    res105: ages.type = Map(hehe -> 35, jessic -> 23, jack -> 31, tom -> 25
    
    • 1
    • 2

    移除元素

    scala> ages -= "hehe"
    res106: ages.type = Map(jessic -> 23, jack -> 31, tom -> 25, haha -> 40)
    
    • 1
    • 2

    遍历
    遍历map的entrySet

    scala> for ((key, value) <- ages) println(key + " " + value)
    jessic 23
    jack 31
    tom 25
    haha 40
    
    • 1
    • 2
    • 3
    • 4
    • 5

    最后看一下Map的几个子类
    HashMap、SortedMap和LinkedHashMap

    • HashMap:是一个按照key的hash值进行排列存储的map
    • SortedMap:可以自动对Map中的key进行排序【有序的map】
    • LinkedHashMap:可以记住插入的key-value的顺序

    HashMap分为可变和不可变的,没有什么特殊之处
    在这主要演示一下SortedMap和LinkedHashMap

    Array

    Scala中Array的含义与Java中的数组类似,长度不可变
    由于Scala和Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上就是Java数组。数组初始化后,长度就固定下来了,而且元素全部根据其类型进行初始化

    scala> val a = new Array[Int](10)
    a: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
    scala> a(0)
    res65: Int = 0
    scala> a(0)=1
    scala> a(0)
    res67: Int = 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    也可以直接使用Array()创建数组,元素类型自动推断

    scala> val a = Array("hello", "world")
    scala> a(0)
    res68: String = hello
    scala> val a1 = Array("hello", 30)
    a1: Array[Any] = Array(hello, 30)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果想使用一个长度可变的数组,就需要使用到ArrayBuffer了

    ArrayBuffer

    Scala中ArrayBuffer与Java中的ArrayList类似,长度可变
    ArrayBuffer:添加元素、移除元素
    如果不想每次都使用全限定名,则可以预先导入ArrayBuffer类

    scala> import scala.collection.mutable.ArrayBuffer
    import scala.collection.mutable.ArrayBuffer
    
    • 1
    • 2
    • 初始化

    使用ArrayBuffer()的方式可以创建一个空的ArrayBuffer
    注意:也支持直接创建并且初始化ArrayBuffer(1,2,3,4)

    scala> val b = new ArrayBuffer[Int]()
    b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()
    
    • 1
    • 2
    • 添加元素
      使用+=操作符,可以添加一个元素,或者多个元素
      b += 1 或者 b += (2, 3, 4, 5)
    scala> b += 1
    res69: b.type = ArrayBuffer(1)
    scala> b += (2, 3, 4, 5)
    res70: b.type = ArrayBuffer(1, 2, 3, 4, 5)
    
    • 1
    • 2
    • 3
    • 4

    使用insert()函数可以在指定位置插入元素,但是这种操作效率很低,因为需要移动指定位置后的所有元素向3号角标的位置添加一个元素 30

    scala> b.insert(3,30)
    scala> b
    res72: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 30, 4
    
    • 1
    • 2
    • 3
    • 移除元素

    使用 remove() 函数可以移除指定位置的元素

    注意:Array与ArrayBuffer可以互相进行转换

    • b.toArray:ArrayBuffer转Array
    • a.toBuffer:Array转ArrayBuffer

    数组常见操作

    • 遍历Array和ArrayBuffer的两种方式
      由于Array和ArrayBuffer都是有角标的,所以在迭代数组中元素的时候除了可以使用前面迭代集合的
      方式还可以使用角标迭代
    scala> val b=ArrayBuffer(1, 2, 3, 4, 5)
    b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5)
    scala> for(i <- b) println(i)
    1
    2
    3
    4
    5
    scala> for(i <- 0 until b.length ) println(b(i))
    1
    2
    3
    4
    5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 求和、求最大值
    scala> val a = Array(3, 2, 1, 4, 5)
    a: Array[Int] = Array(3, 2, 1, 4, 5)
    scala> val sum = a.sum
    sum: Int = 15
    scala> val max = a.max
    max: Int = 5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 数组排序
    scala> scala.util.Sorting.quickSort(a) 
    scala> a
    res99: Array[Int] = Array(1, 2, 3, 4, 5)
    
    • 1
    • 2
    • 3

    Tuple

    Tuple:称之为元组,它与Array类似,都是不可变的,但与数组不同的是元组可以包含不同类型的元素Tuple中的元素角标从 1 开始

    注意:目前 Scala 支持的元组最大长度为 22 ,对于更大长度可以使用集合或数组

    scala> val t = (1, 3.14, "hehe")
    t: (Int, Double, String) = (1,3.14,hehe)
    scala> t._1
    res117: Int = 1
    scala> t._3
    res118: String = hehe
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    总结

    前面讲了很多集合体系中的数据结构,有的是可变的,有的是不可变的,有的是既是可变的又是不可变的,听起来有点乱,在这里我们总结一下
    可变集合: LinkedHashSet、ListBuffer、ArrayBuffer、LinkedHashMap
    不可变集合: List、SortedMap
    可变+不可变集合: Set、HashSet、SortedSet、Map、HashMap
    还有两个编外人员:
    Array、Tuple
    Array:长度不可变,里面的元素可变
    Tuple:长度不可变,里面的元素也不可变

    Scala中函数的使用

    函数的定义

    在Scala中定义函数需要使用 def 关键字,函数包括函数名、参数、函数体。Scala要求必须给出函数所有参数的类型,但是函数返回值的类型不是必须的,因为Scala可以自己根据函数体中的表达式推断出返回值类型。函数中最后一行代码的返回值就是整个函数的返回值,不需要使用return,这一点与Java不同,java中函数的返回值是必须要使用return的
    下面来实现一个单行函数和多行函数

    • 单行函数
    scala> def sayHello(name: String) = print("Hello, " + name)
    sayHello: (name: String)Unit
    scala> sayHello("Scala")
    Hello, Scala
    
    • 1
    • 2
    • 3
    • 4
    • 多行函数
    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    def sayHello(name: String, age: Int) = {
    	println("My name is "+name+",age is "+age)
    	age
    }
    // Exiting paste mode, now interpreting.
    sayHello: (name: String, age: Int)Int
    scala> sayHello("Scala",18)
    My name is Scala,age is 18
    res120: Int = 18
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    函数的参数

    • 默认参数
      在Scala中,有时候我们调用某些函数时,不希望给出参数的具体值,而是希望使用参数自身默认的值,此时就需要在定义函数时使用默认参数。如果给出的参数不够,则会从左往右依次应用参数。
    scala> def sayHello(fName: String, mName: String = "mid", lName: String = "last")
    sayHello: (fName: String, mName: String, lName: String)String
    scala> sayHello("zhang","san")
    res122: String = zhang san last
    
    • 1
    • 2
    • 3
    • 4
    • 带名参数
      在调用函数时,也可以不按照函数定义的参数顺序来传递参数,而是使用带名参数的方式来传递。
    scala> def sayHello(fName: String, mName: String = "mid", lName: String = "last")
    sayHello: (fName: String, mName: String, lName: String)String
    scala> sayHello(fName = "Mick", lName = "Tom", mName = "Jack")
    res127: String = Mick Jack Tom
    
    • 1
    • 2
    • 3
    • 4
    • 可变参数
      在Scala中,有时我们需要将函数定义为参数个数可变的形式,则此时可以使用变长参数来定义函数。
    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    def sum(nums: Int*) = {
    	var res = 0
    	for (num <- nums) res += num
    	res
    }
    // Exiting paste mode, now interpreting.
    sum: (nums: Int*)Int
    scala> sum(1,2,3,4,5)
    res129: Int = 15
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    特殊的函数-过程

    在Scala中,定义函数时,如果函数体直接在花括号里面而没有使用=连接,则函数的返回值类型就是Unit,这样的函数称之为过程
    过程通常用于不需要返回值的函数
    过程还有一种写法,就是将函数的返回值类型显式定义为Unit
    比较一下这四种写法的区别

    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
    
    • 1
    • 2
    • 3
    • 4

    前面两种写法的效果是一样的,都是函数
    后面两种写法的效果是一样的,都是过程

    lazy

    Scala提供了lazy特性,如果将一个变量声明为lazy,则只有在第一次使用该变量时,变量对应的表达式才会发生计算
    什么场景下需要使用lazy特性呢?
    这种特性对于特别耗时的操作特别有用,比如打开文件这个操作。

    scala> import scala.io.Source._
    import scala.io.Source._
    scala> lazy val lines = fromFile("D://test.txt").mkString
    lines: String = <lazy>
    
    • 1
    • 2
    • 3
    • 4

    即使D://test.txt文件不存在,代码也不会报错,只有变量使用时才会报错,这就是lazy这个特性。

    Scala面向对象编程

    在这里我们主要学习Scala中的类、对象和接口
    注意:
    Scala中类和java中的类基本是类似的
    Scala中的对象时需要定义的,而java中的对象是通过class new出来的
    Scala中的接口是trait,java中的接口是interface

    类-class

    首先看一下类
    Scala中定义类和Java一样,都是使用 class 关键字
    和Java一样,使用new关键字创建对象
    那下面来看一个具体案例
    定义Person类,

    class Person{
    var name = "scala"
    def sayHello(){
    	println("Hello,"+name)
    }
    def getName= name
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意:如果在定义方法的时候指定了(),那么在调用的时候()可写可不写,如果在定义方法的时候没指定(),则调用方法时肯定不能带()

    scala> val p = new Person()
    p: Person = Person@23b8d9f3
    scala> p.sayHello()
    Hello,scala
    scala> p.sayHello
    Hello,scala
    scala> println(p.getName)
    scala
    scala> println(p.getName())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    constructor

    类创建好了,下面我们来看一下类中的构造函数,
    Scala类中的构造函数可以分为主构造函数和辅助构造函数
    这两种构造函数有什么区别呢?
    主constructor:类似Java的默认构造函数 this()
    辅助constructor:类似Java的重载构造函数 this(name,age)

    主constructor

    那先来看一下主构造函数
    Scala的主constructor是与类名放在一起的,与Java不同,Java中的构造函数是写在类内部的
    注意:在类中,没有定义在任何方法或者是代码块之中的代码就是主constructor的代码

    先定义一个Student类

    class Student(val name:String,val age:Int){
    println("your name is " + name + ", your age is " + age)
    }
    
    • 1
    • 2
    • 3

    创建对象

    scala> new Student("zs",19)
    your name is zs, your age is 19
    res8: Student = Student@3134153d
    
    • 1
    • 2
    • 3

    在创建对象的时候,类中的println语句执行了,说明这个语句属于主构造函数中的代码,主constructor中还可以通过使用默认参数,来给参数设置默认值

    class Student(val name:String = "jack",val age:Int = 20){
    println("your name is " + name + ", your age is " + age)
    }
    
    • 1
    • 2
    • 3

    创建对象

    scala> new Student()
    your name is jack, your age is 20
    res10: Student = Student@7ddd84b5
    scala> new Student("tom",18)
    your name is tom, your age is 18
    res11: Student = Student@a09303
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Scala中,可以给类定义多个辅助constructor,类似于java中的构造函数重载。辅助constructor之间可以互相调用,但是第一行必须调用主constructor
    来看一个案例:
    给学生指定姓名和年龄信息

    class Student {
    var name = "jack"
    var age = 10
    def this(name: String) {
    this()
    this.name = name
    }
    def this(name: String, age: Int) {
    this(name)
    this.age = age
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    执行

    scala> val s = new Student("tom")
    s: Student = Student@1a538ed8
    scala> val s = new Student("mick",30)
    s: Student = Student@319642db
    
    • 1
    • 2
    • 3
    • 4

    对象-object

    我们学习的scala中的class和java中的class是类似的,下面来看一个java中没有的内容,叫Object
    那大家可能有疑问了,Java中也有object,通过class就可以创建object
    但是注意了,在scala中,我们可以直接定义一个object,就像定义class一样。
    object:相当于class的单个实例,通常在里面放一些静态的field或者method
    object不能定义带参数的constructor,只有空参的constructor
    第一次调用object的方法时,会执行object的constructor,也就是执行object内部不在任何方法中的代码,因为它只有空参的构造函数。但是注意,object的constructor的代码只会在他第一次被调用时执行一次,以后再次调用就不会再执行了object通常用于作为单例模式的实现,或者放class的一些静态成员,比如工具方法。object可以直接使用,不能new创建一个object,使用object关键字

    object Person {
    	var age = 1
    	println("this Person object!")
    	def getAge = age
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    执行,直接通过Object的名称调用属性或者方法即可,类似于Java中的静态类

    伴生对象

    前面学习了class和object,那下面再来看一个特殊的概念,伴生对象
    如果有一个class,还有一个与class同名的object,那么就称这个object是class的 伴生对象 ,class是object的 伴生类

    注意:伴生类和伴生对象必须存放在一个.scala文件之中
    伴生类和伴生对象最大特点在于可以互相访问private field

    创建一个Person object和Person class

    object Person {
    private val fdNum= 1
    	def getFdNum = fdNum
    }
    
    class Person(val name: String, val age: Int) {
    	def sayHello = println("Hi, " + name + ",you are " + age + " years old!" +
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    执行

    scala> new Person("tom",20).sayHello
    Hi, tom,you are 20 years old!, and you have 1 friend.
    scala> Person.fdNum
    <console>:9: error: value fdNum is not a member of object Person
    Person.fdNum
    ^
    scala> Person.getFdNum
    res26: Int = 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    apply

    apply是object中非常重要的一个特殊方法,通常在伴生对象中实现apply方法,并在其中实现构造伴生类对象的功能
    在创建对象的时候,就不需要使用new Class的方式,而是使用Class()的方式,隐式调用伴生对象的
    apply方法,这样会让对象创建更加简洁

    例如:Array的伴生对象的apply方法就实现了接收可变数量的参数,以及会创建一个Array对象
    val a = Array(1, 2, 3, 4, 5)

    image.png

    从Array object的源码中可以看出来,它里面就是在apply方法内部使用new Array创建的对象。下面我们来自己定义一个伴生类和伴生对象

    class Person(val name: String){
    	println("my name is,"+name)
    }
    object Person {
    	def apply(name: String) = {
    		println("apply exec...")
    		new Person(name)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    执行

    scala> new Person("tom")
    my name is,tom
    res29: Person = Person@63917fe1
    scala> Person("tom")
    apply exec...
    my name is,tom
    res30: Person = Person@35e74e08
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意:在这里new Person(“zhang”) 等于 Person(“zhang”),都是获取Person的对象
    只不过Person(“zhang”)是用的object中apply方法, 而new Person(“zhang”)是直接基于class创建的。这两种方式肯定Person(“zhang”)这种方式使用起来更加方便简洁

    main方法

    接下来看一下scala中的main方法和Java一样,在Scala中如果要运行一个应用程序,必须有一个main方法,作为入口
    Scala中的main方法必须定义在object中,格式为 def main(args: Array[String])
    这就需要在编辑器中操作了,我们可以使用eclipse或者idea,但是eclipse对scala的支持不太好,所以建议使用idea。

    首先确认一下idea中是否集成了scala语言插件
    打开idea,点击 configure–>plugins

    image.png

    确认scala的插件是否已经安装

    image.png

    接着创建maven项目

    image.png

    点击开启自动引入依赖

    到这还没完,因为此时我们是无法创建scala代码的,这个项目中也没有集成scala的sdk,只有java的

    image.png

    接下来就需要给这个项目添加scala的sdk了

    image.png

    image.png

    image.png

    image.png

    添加成功以后是这样的

    image.png

    此时查看项目的依赖,发现scala的sdk已经添加进来了。

    image.png

    最终点击ok即可

    以后再创建想要创建scala的maven项目,只需要进入到这个界面确认项目中是否有scala的依赖,没有的话直接点击右边的加号按钮添加即可

    image.png

    image.png

    idea的scala开发环境配置好了,但是我一般还是喜欢再增加一些配置
    目前项目的src目录下有一个java目录,这个目录表示是放java代码的,当然了你在里面写scala代码肯定是没有问题的。只是针对我这种稍微有点强迫症的用起来就有点别扭了。
    在实际工作中可能我们一个项目既需要使用到java代码,也需要使用到scala代码,所以最好还是建议把java代码和scala代码分开存放,这样比较清晰

    这样创建的scala目录是有问题的,你有没有发现这个目录的颜色和java目录的颜色都不一样因为你在这直接创建的scala目录是一个普通的目录,而java那个目录是一个source根目录。
    所以我们也需要把scala目录变为source根目录。这样操作

    image.png

    这样操作之后,就发现scala的目录颜色就正常了
    image.png

    下面就可以正式开始写代码了,scala相关的代码就放到scala目录中
    现在scala目录下创建包: com.imooc.demo
    然后创建一个scala的object
    先选中Scala Class

    image.png

    image.png

    Scala 开发HelloWorld程序

    package com.imooc.demo
    /**
    * Created by xuwei
    */
    object mainDemo {
    def main(args: Array[String]): Unit = {
    println("hello scala!")
    
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行代码,右键run即可

    这就是Scala中main方法的用法,注意:main方法只能定义在object中,不能定义在class中

    接口-trait

    scala中的接口,这个接口也是比较特殊的
    Scala中的接口称为trait,trait类似于Java中的interface
    在triat中可以定义抽象方法

    类可以使用 extends 关键字继承trait,无论继承类还是trait统一都是使用 extends 这个关键字。
    类继承trait后,必须实现trait中的抽象方法,实现时不需要使用 override 关键字。
    scala不支持对类进行多继承,但是支持对trait进行多重继承,使用 with 关键字即可
    下面我们就来看一个接口多继承的案例

    object PersonDemo {
    def main(args: Array[String]): Unit = {
    val p1 = new Person("tom")
    val p2 = new Person("jack")
    p1.sayHello(p2.name)
    p1.makeFriends(p2)
    }
    }
    trait HelloTrait { def sayHello(name: String)}
    trait MakeFriendsTrait { def makeFriends(p: Person)}
    class Person(val name: String) extends HelloTrait with MakeFriendsTrait {
    def sayHello(name: String) = println("Hello, " + name)
    def makeFriends(p: Person) = println("Hello, my name is " + name + ", your
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Scala函数式编程

    什么是函数式编程

    Scala是一门既面向对象,又面向过程的语言。因此在Scala中有非常好的面向对象的特性,可以使用Scala来基于面向对象的思想开发大型复杂的系统和工程;而且Scala也面向过程,因此Scala中有函数的概念。在Scala中,函数与类、对象一样,都是一等公民,所以说scala的面向过程其实就重在针对函数的编程了,所以称之为函数式编程

    函数赋值给变量

    Scala中的函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量。
    Scala的语法规定,将函数赋值给变量时,必须在函数后面加上空格和下划线。
    来看一个案例:将函数赋值给变量

    scala> def sayHello(name: String) { println("Hello, " + name) }
    sayHello: (name: String)Unit
    scala> val sayHelloFunc = sayHello _
    sayHelloFunc: String => Unit = <function1>
    scala> sayHelloFunc("scala")
    Hello, scala
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    匿名函数

    Scala中的函数也可以不需要命名,这种函数称为匿名函数
    匿名函数的语法格式:(参数名: 参数类型) => 函数体
    (参数名: 参数类型) :是函数的参数列表
    可以将匿名函数直接赋值给某个变量

    看一个例子

    scala> val sayHelloFunc = (name: String) => println("Hello, " + name)
    sayHelloFunc: String => Unit = <function1>
    scala> val sayHelloFunc = (name: String) => {println("Hello, " + name)}
    sayHelloFunc: String => Unit = <function1>
    
    • 1
    • 2
    • 3
    • 4

    注意:如果函数体有多行代码,则需要添加{}

    高阶函数

    由于函数是一等公民,所以说我们可以直接将某个函数作为参数传入其它函数。
    这个功能是极其强大的,也是Java这种面向对象的编程语言所不具备的
    这个功能在实际工作中是经常需要用到的。
    接收其它函数作为当前函数的参数,当前这个函数也被称作高阶函数 (higher-order function)
    看一个例子:
    先定义一个匿名函数,赋值给变量sayHelloFunc

    val sayHelloFunc = (name: String) => println("Hello, " + name)
    
    • 1

    再定义一个高阶函数,这个高阶函数的参数会接收一个函数
    参数: (String) => Unit 表示这个函数接收一个字符串,没有返回值

    def greeting(func: (String) => Unit, name: String) { func(name) } 
    
    • 1

    使用:

    scala> greeting(sayHelloFunc, "scala")
    Hello, scala
    
    • 1
    • 2

    或者还可以这样用,直接把匿名函数的定义传过来也是可以

    scala> greeting((name: String) => println("Hello, " + name),"scala")
    Hello, scala
    
    • 1
    • 2

    高阶函数可以自动推断出它里面函数的参数类型,对于只有一个参数的函数,还可以省去小括号

    # 先定义一个高阶函数
    def greeting(func: (String) => Unit, name: String) { func(name) }
    # 使用高阶函数:完整写法
    greeting((name: String) => println("Hello, " + name), "scala")
    # 使用高阶函数:高阶函数可以自动推断出参数类型,而不需要写明类型
    greeting((name) => println("Hello, " + name), "scala")
    # 使用高阶函数:对于只有一个参数的函数,还可以省去其小括号
    greeting(name => println("Hello, " + name), "scala")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    常用高阶函数

    刚才是我们自己实现的高阶函数,其实我们在工作中自己定义高阶函数的场景不多,大部分场景都是去使用已有的高阶函数。下面我们来看几个常见的高阶函数

    • map:对传入的每个元素都进行处理,返回一个元素
    • flatMap:对传入的每个元素都进行处理,返回一个或者多个元素
    • foreach:对传入的每个元素都进行处理,但是没有返回值
    • filter:对传入的每个元素都进行条件判断,如果返回true,则保留该元素,否则过滤掉该元素
    • reduceLeft:从左侧元素开始,进行reduce操作

    map

    map的使用:

    scala> Array(1, 2, 3, 4, 5).map(num=>{num * 2})
    res38: Array[Int] = Array(2, 4, 6, 8, 10)
    
    • 1
    • 2

    可以简写为:

    scala> Array(1, 2, 3, 4, 5).map(_ * 2)
    res40: Array[Int] = Array(2, 4, 6, 8, 10)
    
    • 1
    • 2

    flatMap

    flagMap的使用

    scala> Array("hello you","hello me").flatMap(line=>line.split(" "))
    res53: Array[String] = Array(hello, you, hello, me)
    
    • 1
    • 2

    可以简写为:

    scala> Array("hello you","hello me").flatMap(_.split(" "))
    res54: Array[String] = Array(hello, you, hello, me)
    
    • 1
    • 2

    foreach

    foreach的使用

    scala> Array(1, 2, 3, 4, 5).map(_ * 2).foreach(num=>println(num))
    2
    4
    6
    8
    10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以简写为:

    scala> Array(1, 2, 3, 4, 5).map(_ * 2).foreach(println(_))
    2
    4
    6
    8
    10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    filter

    filter的使用

    scala> Array(1, 2, 3, 4, 5).filter(num=>num % 2 == 0)
    res46: Array[Int] = Array(2, 4)
    
    • 1
    • 2

    可以简写为:

    scala> Array(1, 2, 3, 4, 5).filter(_ % 2 == 0)
    res47: Array[Int] = Array(2, 4)
    
    • 1
    • 2

    reduceLeft

    reduceLeft的使用
    表示先对元素1和元素2进行处理,然后将结果与元素3处理,再将结果与元素4处理,依次类推
    spark中有一个reduce函数,和这个函数的效果一致

    scala> Array(1, 2, 3, 4, 5).reduceLeft((t1,t2)=>t1+t2)
    res50: Int = 15
    
    • 1
    • 2

    这个操作操作就相当于 1 + 2 + 3 + 4 + 5

    可以简写为:

    scala> Array(1, 2, 3, 4, 5).reduceLeft( _ + _)
    res49: Int = 15
    
    • 1
    • 2

    案例:函数式编程

    统计多个文本内的单词总数
    使用scala的io包读取文本文件内的数据

    val lines01 = scala.io.Source.fromFile("D://a.txt").mkString
    val lines02 = scala.io.Source.fromFile("D://b.txt").mkString
    
    • 1
    • 2

    注意:下面这一行是核心代码,使用了链式调用的函数式编程

    lines.flatMap(_.split( " ")).map((_, 1)).map(_._2).reduceLeft(_ + _) 
    
    • 1

    lines.flatMap(.split( " ")) :表示对每一行数据使用空格进行切割,返回每一个单词
    .map((
    , 1)) :针对每一个单词,都转成tuple类型,tuple中的第1个元素是这个单词,第2个元素表示单词出现的次数1
    .map(_.2) :迭代每一个tuple,获取tuple中的第2个元素
    .reduceLeft(
    + _) :对前面获取到的元素进行累加求和

    Scala高级特性

    模式匹配

    模式匹配是Scala中非常有特色,非常强大的一种功能。
    模式匹配,其实类似于Java中的 switch case 语法,即对一个值进行条件判断,然后针对不同的条件,进行不同的处理
    不过Scala没有Java中的 switch case 语法,但是,Scala提供了更加强大的 match case 语法,就是这个模式匹配
    Java的 switch case 仅能匹配变量的值,而Scala的 match case 可以匹配各种情况,比如:变量的类型、集合的元素,有值没值

    对变量的值进行模式匹配

    match case语法格式:变量 match { case 值 => 代码 }
    如果值为下划线,则代表了不满足以上所有情况下的默认处理

    def demo1(day: Int) {
    day match {
    case 1 => println("Monday")
    case 2 => println("Tuesday")
    case 3 => println("Wednesday")
    case _ => println("none")
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    执行

    scala> demo1(1)
    Monday
    scala> demo1(4)
    none
    
    • 1
    • 2
    • 3
    • 4

    注意:match case中,只要一个case分支满足并处理了,就不会继续判断下一个case分支了,这一点与Java不同,java的switch case需要用break停止向下执行

    变量类型的模式匹配

    Scala的模式匹配一个强大之处就在于,可以直接匹配类型,而不是值!!!这点是java的switch case绝对做不到的
    语法格式:变量 match { case 变量: 类型 => 代码 }
    典型的一个应用场景就是针对异常的处理

    import java.io._
    def processException(e: Exception) {
    e match {
    case e1: IllegalArgumentException => println("IllegalArgumentException "
    case e2: FileNotFoundException => println("FileNotFoundException " + e2)
    case e3: IOException => println("IOException " + e3)
    case _: Exception => println("Exception " )
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    执行

    scala> processException(new Exception())
    Exception
    
    • 1
    • 2

    在try-catch异常中的应用

    try {
    val lines02 = scala.io.Source.fromFile("D://test02.txt").mkString
    } catch {
    case ex: FileNotFoundException => println("no file")
    case ex: IOException => println("io exception")
    case ex: Exception => println("exception")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    case class与模式匹配

    Scala中提供了一种特殊的类,用 case class 进行声明,中文可以称为样例类
    case class 其实有点类似于Java中的JavaBean的概念。即只定义field,会由Scala在编译时自动提供get和set方法,但是没有其它的method
    case class 的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来,在这用哪个区别都不大)
    Scala自动为 case class 定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回 case class 对象
    下面来看一个案例:,先定义三个class

    class Person
    case class Teacher(name: String, sub: String) extends Person
    case class Student(name: String, cla: String) extends Person
    
    • 1
    • 2
    • 3

    再创建一个函数

    def check(p: Person) {
    p match {
    case Teacher(name, sub) => println("Teacher, name is " + name + ", sub is
    case Student(name, cla) => println("Student, name is " + name + ", cla is
    case _ => println("none")
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    执行

    scala> check(new Student("tom","class1"))
    Student, name is tom, cla is class1
    scala> check(new Person())
    none
    
    • 1
    • 2
    • 3
    • 4

    Option与模式匹配

    Scala有一种特殊的数据类型,叫做Option。
    Option有两种值,一种是Some,表示有值,一种是None,表示没有值
    Option通常会用于模式匹配中,用于判断某个变量是有值还是没有值,这比null来的更加简洁明了
    看这个案例

    val ages = Map("jack" -> 18, "tom" -> 30, "jessic" -> 27)
    def getAge(name: String) {
    val age = ages.get(name)
    age match {
    case Some(age) => println("your age is " + age)
    case None => println("none")
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    执行

    scala> getAge("jack")
    your age is 18
    scala> getAge("hehe")
    none
    
    • 1
    • 2
    • 3
    • 4

    隐式转换

    Scala的隐式转换,允许手动指定将某种类型的对象转换成其它类型的对象。
    Scala的隐式转换,最核心的就是定义隐式转换函数,即implicit conversion function 。
    隐式转换函数与普通函数唯一的语法区别是要以implicit开头而且最好要定义函数返回类型。
    隐式转换非常强大的一个功能,就是可以在不知不觉中加强现有类型的功能。也就是说,我们可以为某个普通类定义一个加强类,并定义对应的隐式转换函数,这样我们在使用加强类里面的方法的时候,Scala会自动进行隐式转换,把普通类转换为加强类,然后再调用加强类中的方法
    Scala默认会自动使用两种隐式转换
    1:源类型,或者目标类型的伴生对象里面的隐式转换函数
    2:当前程序作用域内可以用唯一标识符表示的隐式转换函数

    def check(p: Person) {
    	p match {
    	case Teacher(name, sub) => println("Teacher, name is " + name + ", sub is
    	case Student(name, cla) => println("Student, name is " + name + ", cla is
    	case _ => println("none")
    }
    
    scala> check(new Student("tom","class1"))
    Student, name is tom, cla is class1
    scala> check(new Person())
    
    val ages = Map("jack" -> 18, "tom" -> 30, "jessic" -> 27)
    def getAge(name: String) {
    	val age = ages.get(name)
    	age match {
    	case Some(age) => println("your age is " + age)
    	case None => println("none")
    	}
    }
    
    scala> getAge("jack")
    your age is 18
    scala> getAge("hehe")
    none
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用import引入对应的隐式转换函数。
    通常建议,仅仅在需要进行隐式转换的地方,比如某个函数内,用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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    执行

    scala> val d = new dog("d1")
    d: dog = dog@7f0e0db3
    scala> d.catchMouse()
    d1 catch mouse
    
    • 1
    • 2
    • 3
    • 4

    大部分的场景是我们只需要使用import。导入对应的隐式转换函数就可以了,在这个案例中我们是自己手工实现了一个隐私转换函数,因为他们都在一个作用域内,所以就不需要import了。

  • 相关阅读:
    系统分屏后录音机向左右滑动报错问题
    黑马微服务常见面试题
    Python中文分词、词频统计并制作词云图
    Java中的dozer对象转换
    我在CSDN开组会1-蒙特卡洛模拟在矿床学的应用展望
    C语言文件操作
    ArrayList#subList这四个坑,一不小心就中招
    【漏洞分析】Reflection Token 反射型代币攻击事件通用分析思路
    LeetCode 每日一题 2022/9/5-2022/9/11
    深度学习入门(7)误差反向传播计算方式及简单计算层的实现
  • 原文地址:https://blog.csdn.net/qq_40905284/article/details/134254265