Spark基础之Scala快速入门篇

456 阅读10分钟

一、前言

  本文是Spark学习前对Scala内容学习的一个笔记,将阐述如何快速上手使用Scala,以及一些Scala的特性。

二、快速入门

  工欲善其事必先利其器,在开始学习和使用Scala之前,我们需要提前准备好Scala的开发环境。    1. 安装JDK(不加赘述)
   2. 安装Scala
   3. 配置IDEA

2.1 Scala安装

官网:www.scala-lang.org/

选择需要安装的Scala版本

下载解压

配置环境变量

SCALA_HOME:
SCALA_HOME=E:\Eddie\DevTool\scala-2.13.8

PATH:
PATH=%SCALA_HOME%\bin

验证 Windows环境下,cmd -> 输入 scala,显示如图界面,则表示Scala安装完成了

image.png

2.2 配置IDEA

  在正式学习Scala之前,还需要先将我们刚刚下载好的Scala SDK配置到IDEA中。下面将展示将下载到本地的Scala SDK配置到IDEA中。
  首先,我们需要先创建一个Maven项目(这里就不介绍如何创建Maven项目),创建完成后的Maven项目目录结构如下:

image.png
接下来,我们需要将Scala的SDK,也就是我们刚刚下载好的Scala添加到Maven项目中

image.png

image.png 选择你需要的版本,点击 “OK”,最后点击右下角 “Apply” 生效

image.png 此时,在main目录下创建scala目录,并将scala目录设置为源码目录

image.png

选中scala目录,然后右键 -> Mark Directory as -> Sources Root

image.png

此时,就可以在scala目录下创建Scala相关类了

image.png

2.3 Hello World

  • 方式一
object HelloWorld {  
    def main(args: Array[String]): Unit = {  
        println("Hello World")  
    }  
}

Scala 中 object 声明的是单例对象,object 中定义的方法和字段都是静态的,类似于Java中的 static 修饰的方法和字段。因此,object 中声明的 main 方法,就类似于 Java 中的 public static void main,作为程序的主入口,main 方法中则进行程序逻辑的编写。

  • 方式二
object HelloWorld extends App {  
    println("Hello World")  
}

上述代码中,HelloWorld 继承了名为 App 的特质,App 特质中实现了一个 main 方法,关于 Scala 的特质,我们后续内容会提到,特质就类似于 Java 中的接口或抽象类。

2.4 变量声明

  • var 关键字声明的变量为可修改变量
  • val 关键字声明的变量为不可修改变量,类似 Java 使用 final 修饰变量
    下面将演示如何声明变量:
    // Scala 自动类型推断
    var num = 10
    val str = "scala"
    
    // 声明变量时指定变量类型
    var num: Int = 10
    val str: String = "scala"

2.5 小结

  本章节讲述了 Scala 的安装配置,如何编写一个 HelloWorld 程序,以及变量的声明。对于 Scala 变量和数据类型的内容学习,有时间可以查看其他资料完整地了解一下。

三、流程控制

  在流程控制中,所有代码块可以有多行代码且最后一行为该代码块的返回值,如果代码块只有一行,则可以去掉 {}

3.1 if 分支结构

  • 常规 if 分支结构
object IfControlStruct extends App {  
// 键盘录入一个 Int 值并赋值给 num 变量  
val num: Int = StdIn.readInt()  
  
if (num >= 0 && num < 10) {  
        println("[0-10)")  
    } else if (num >= 10 && num < 50) {  
        println("[10-50)")  
    } else {  
        println("num > 50")  
    }  
}
  • if 分支结构作为返回值
    Scala 中,if 代码块中的内容可以作为返回值,并赋值给变量;{} 代码块中如果有多行代码,则代码块的最后一行为代码块的返回值
object IfControlStruct extends App {  
// 键盘录入一个 Int 值并赋值给 num 变量  
val num: Int = StdIn.readInt()  
  
val str: String = if (num >= 0 && num < 10) {  
        val x = "num is in"
        s"$x [0-10)"  
    } else if (num >= 10 && num < 50) {  
        "[10-50)"  
    } else {  
        "num > 50"  
    }  
  
    println(str)  
}

Scala中没有三目运算符,但是可以基于 if 分支结果作为返回值的特性实现相同的效果

object IfControlStruct extends App {  
    // 键盘录入一个 Int 值并赋值给 num 变量  
    val num: Int = StdIn.readInt()  
  
    val res = if (num >= 0 && num < 10) true else false  
    println(res)  
}

3.2 循环结构

  • while 结构
    while 的使用类似于其他语言,但需要注意的是:Scala 中没有类似于其他语言的 breakcontinue 关键字;break 常用于 while 循环,下面将介绍 while + break 的使用
import scala.util.control.Breaks.{break, breakable}

object LoopControlStruct extends App {  
    breakable {  
        var count = 1  
        while (true) {  
            println(count)  
            if (count == 10) break  
            count += 1  
        }  
    }  
}
  • for 结构
    fori 基础用法
object LoopControlStruct extends App {  
    // [0-10]  
    for (i <- 0 to 10) {  
        println(i)  
    }  
  
    // [0-10)  
    for (i <- 0 until 10) {  
        println(i)  
    }  
}

  for 遍历集合

object LoopControlStruct extends App {  
    // 遍历 List  
    val list = List(1, 2, 3, 4, 5)  
    for (elem <- list) {  
        println(elem)  
    }  
  
    // 遍历 Map  
    val map = Map(  
        ("zhangsan", 18),  
        ("lisi", 20),  
        ("wangwu", 25)  
    )  
    for ((k, v) <- map) {  
        println(s"${k} - ${v}")  
    }  
}

  for 守卫
  通过 for 的守卫模式可以实现 continue 的功能

object LoopControlStruct extends App {  
    // 打印偶数值  
    for (i <- 0 to 10 if i % 2 == 0) {  
        println(i)  
    }  
}

   多重 for 循环

object LoopControlStruct extends App {  
    // 打印九九乘法表  
    for (i <- 1 to 9; j <- 1 to i) {  
        print(s"$i * $j = ${i * j}\t")  
        if (i == j) println()  
    }  
}

  yield 生成新序列

object LoopControlStruct extends App {  
    val seq1 = for (i <- 0 to 10) yield i * 2  
    println(seq1)  
  
    // yield 后面可以跟代码块, 代码块的最后一行为返回值  
    val seq2 = for (i <- 0 to 10) yield {  
        // 奇数 * 2, 偶数不变  
        if (i % 2 != 0) i * 2 else i  
    }  
    println(seq2)  
}

3.3 模式匹配

Scala中,模式匹配 match expression 类似于 Java 的 switch,但是模式匹配会更加灵活

  • 常规模式匹配
object MatchExpression extends App {  
    val month = StdIn.readInt()  
  
    month match {  
        case 1 => println("January")  
        case 2 => println("February")  
        case 3 => println("March")  
        case 4 => println("April")  
        case 5 => println("May")  
        case 6 => println("June")  
        case 7 => println("July")  
        case 8 => println("August")  
        case 9 => println("September")  
        case 10 => println("October")  
        case 11 => println("November")  
        case 12 => println("December")  
        // _ 表示默认匹配, 能匹配所有内容  
        case _ => println("Invalid month")  
    }  
}
  • 带返回值的模式匹配
object MatchExpression extends App {  
    val month = StdIn.readInt()  
  
    val monthStr = month match {  
        case 1 => "January"  
        case 2 => "February"  
        case 3 => "March"  
        case 4 => "April"  
        case 5 => "May"  
        case 6 => "June"  
        case 7 => "July"  
        case 8 => "August"  
        case 9 => "September"  
        case 10 => "October"  
        case 11 => "November"  
        case 12 => "December"  
        // _ 表示默认匹配, 能匹配所有内容  
        case _ => "Invalid month"  
}  
  
println(monthStr)  
}
  • 使用模式匹配作为方法体

这里先提一下 Scala 中方法的定义方法:
def 方法名(变量名1: 参数类型1, 变量名2: 参数类型2, ...): 返回类型 = 函数体

object MatchExpression extends App {  
    def convertBooleanToStringMessage(bool: Boolean): String = bool match {  
        case true => "-- true -- "  
        case false => "-- false --"  
    }  
  
    println(convertBooleanToStringMessage(true))  
}
  • case 匹配
object MatchExpression extends App {  
    // Any 为所有数据类型的父类, 类似于 Java 中的 Object  
    def isTrue(v: Any): Boolean = v match {  
        case 0 | "" => false  
        case _ => true  
    }  
  
    println(isTrue(""))  
}
  • 模式匹配中使用守卫
object MatchExpression extends App {  
    val num = StdIn.readInt()  
  
    num match {  
        case i1: Int if i1 >= 0 && i1 < 10 => println("[0-10)")  
        case i2: Int if i2 >= 10 && i2 < 50 => println("[10-50)")  
        case _ => println("other value")  
    }  
}

3.4 异常处理

  看到这里,你或许会有疑问,为什么异常处理会放到流程控制中讲解;这是因为 Scala 采取模式匹配的方式来处理捕获的异常。

  • try-catch
object ExceptionHandle extends App {  
    try {  
        val num = 10 / 0  
        println(num)  
    } catch {  
        case ex: Exception => println(ex.getStackTrace.mkString("\n"))  
    }  
}
  • try-catch-finally
object ExceptionHandle extends App {  
    try {  
        val num = 10 / 0  
        println(num)  
    } catch {  
        case ex: Exception => println(ex.getStackTrace.mkString("\n"))  
    } finally {  
        println("close resources...")  
    }  
}

3.5 小结

  本章介绍了 Scala 流程控制中的分支结构、循环结构、模式匹配以及基于模式匹配的异常处理,这些都是日常 Scala 开发中必不可少的部分。

四、函数

  Scala 不仅是一门面向对象的语言,还是一门面向函数式编程的语言。在 Scala 中,函数是非常重要的一部分,函数就像对象一样,可以作为方法的参数、方法的返回值,同时也可以作为值传给变量。下面我们将对 Scala 函数的内容进行介绍。
  在正式对 Scala 函数进行演示之前,我们需要分清方法与函数两个概念;基于我个人的理解是:方法是属于类的,也就是类的行为属性;而函数则是对象,函数会在方法中定义和使用。

4.1 无参无返回类型函数

前面在介绍模式匹配的时候我们介绍了如何定义一个函数:
def 方法名(变量名1: 参数类型1, 变量名2: 参数类型2, ...): 返回类型 = 函数体
当函数没有形参和返回类型时,我们可以简写成下面的形式:

object ScalaFunction {  
    def func() {  
        println("func")  
    }  
  
    func()  
}

4.2 默认值函数

我们在定义函数时,可以给形参赋予默认值

object ScalaFunction extends App {  
    def func01(name: String, age: Int = 18): Unit = {  
        println(s"name: $name, age: $age")  
    }  
  
    func01("zhangsan")  // 输出:name: zhangsan, age: 18
    
    def func02(name: String = "lisi", age: Int = 20): Unit = {  
        println(s"name: $name, age: $age")  
    }  
  
    func02() // 输出:name: lisi, age: 20
    
    def func03(name: String = "wangwu", age: Int): Unit = {  
        println(s"name: $name, age: $age")  
    }  
  
    // func03(18) // 这样传参会报错  
    func03(age = 25) // 此时可以指定参数名进行传参
}

4.3 递归函数

Scala 中定义递归函数时,必须明确返回值类型

object ScalaFunction extends App {  
    def fac(num: Int): Int = {  
        if (num == 1) {  
            1;  
        } else {  
            num * fac(num - 1)  
        }  
    }  
  
    println(fac(5))  
}

4.4 匿名函数

(参数类型1, 参数类型2, ...) => 返回值类型 = (变量1: 参数类型1, 变量2: 参数类型2, ...) => 函数体

object ScalaFunction extends App {  
    // 写法一  
    val sum1: (Int, Int) => Int = (x: Int, y: Int) => x + y  
    println(sum1(1, 2))  
  
    // 写法二(省略变量的类型)  
    val sum2 = (x: Int, y: Int) => x + y  
    println(sum2(1, 2))  
}

4.5 嵌套函数

内层函数可以使用外层函数的变量

object ScalaFunction extends App {  
    def outer(str: String) = {  
        def inner(): Unit = {  
            println(str)  
        }  
        inner()  
    }  
    outer("Scala")  
}

4.6 偏应用函数

object ScalaFunction extends App {  
    def baseLog(date: Date, logType: String, msg: String) = {  
        println(s"$date\t$logType\t$msg")  
    }  
  
    baseLog(new Date(), "trace", "ok")  
  
    val info = baseLog(_: Date, "info", _: String)  
    val error = baseLog(_: Date, "error", _: String)  
  
    info(new Date(), "info...")  
    error(new Date(), "error...")  
}

4.7 可变参数

object ScalaFunction extends App {  
    def func(nums: Int*): Unit = {  
        for (elem <- nums) {  
            println(elem)  
        }  
    }  
  
    func(1, 2, 3, 4, 5)  
}

4.8 高阶函数

函数作为参数

object ScalaFunction extends App {  
    def computer(n1: Int, n2: Int, calc: (Int, Int) => Int): Int = {  
        calc(n1, n2)  
    }  
  
    // 写法一  
    println(computer(1, 2, (x, y) => x + y))  
  
    // 写法二, 如果参数列表中的参数 依次 且在函数体中仅出现一次, 则可以省略形参列表, 函数体中的参数可以用 _ 代替  
    println(computer(2, 5, _ + _))  
}

函数作为返回值

object ScalaFunction extends App {  
    def getCalc(symbol: Char): (Int, Int) => Int = symbol match {  
        case '+' => (n1: Int, n2: Int) => n1 + n2  
        case '-' => (n1: Int, n2: Int) => n1 - n2  
        case '*' => (n1: Int, n2: Int) => n1 * n2  
        case '/' => (n1: Int, n2: Int) => n1 / n2  
    }  
  
    val add = getCalc('+')  
    println(add(1, 2))  
}

4.9 柯里化

柯里化本质上就是多参数列表的函数,使用上和普通函数一样

object ScalaFunction extends App {  
    // 基本类型演示  
    def func01(name: String)(age: Int) = {  
        println(s"name: $name, age: $age")  
    }  
    func01("zhangsan")(18)  
  
    // 多个可变参数  
    def func02(nums: Int*)(strs: String*): Unit = {  
        for (elem <- nums) {  
            println(elem)  
        }  
        println("===================================")  
        for (elem <- strs) {  
            println(elem)  
        }  
    }  
    func02(1, 2, 3)("hello", "world")  
}

4.10 方法转函数

object ScalaFunction {  
    def method(): Unit = {  
        println("111")  
    }  
  
    def main(args: Array[String]): Unit = {  
        val m = method _  
        m()  
    }  
}

4.11 偏函数(PartialFunction)

  Scala 中的 PartialFunction 是一个 trait,其类型为 PatialFunction[A,B],其中接收一个类型为A的参数,返回一个类型为B的参数。

object ScalaFunction {  
    def main(args: Array[String]): Unit = {  
        def pf: PartialFunction[Any, String] = {  
            case str: String => "this is a String"  
            case int: Int => "this is an int"  
            case _ => "default"  
        }  

        println(pf("dfhauisd"))  
        println(pf(11))  
        println(pf(true))  
    }  
}

五、类与特质

5.1 类构造器

Scala 只有无参和全参构造器,且无参构造器和全参构造器默认只能存在其中一个。

  • 无参构造器
object ScalaClass {  
    class Person() {  
        private val name: String = "zhangsan"  
        private val age: Int = 18  
  
        // 方法参数列表没有参数时可以省略 ()  
        def getName: String = {  
            name  
        }  
  
        def getAge(): Int = {  
            age  
        }  
    }  
  
    def main(args: Array[String]): Unit = {  
        val person = new Person()  
        println(person.getName)  
        println(person.getAge())  
    }  
}
  • 全参构造器

  只有类构造器中定义的属性可以为 var,方法中形参列表只能为 val

object ScalaClass {  
    class Person(name: String, age: Int) {  
        // 方法参数列表没有参数时可以省略 ()  
        def getName: String = {  
            name  
        }  
  
        def getAge(): Int = {  
            age  
        }  
    }  
  
    def main(args: Array[String]): Unit = {  
        val person = new Person("zhangsan", 18)  
        println(person.getName)  
        println(person.getAge())  
    }  
}
  • 辅助构造器

这里搬运一下官网对辅助构造器的示例,实际上也是全参构造器的变形

val DefaultCrustSize = 12
val DefaultCrustType = "THIN"

// the primary constructor
class Pizza (var crustSize: Int, var crustType: String) {

    // one-arg auxiliary constructor
    def this(crustSize: Int) = {
        this(crustSize, DefaultCrustType)
    }

    // one-arg auxiliary constructor
    def this(crustType: String) = {
        this(DefaultCrustSize, crustType)
    }

    // zero-arg auxiliary constructor
    def this() = {
        this(DefaultCrustSize, DefaultCrustType)
    }

    override def toString = s"A $crustSize inch pizza with a $crustType crust"

}

def main(args: Array[String]): Unit = {  
    val p1 = new Pizza(DefaultCrustSize, DefaultCrustType)  
    val p2 = new Pizza(DefaultCrustSize)  
    val p3 = new Pizza(DefaultCrustType)  
    val p4 = new Pizza  
    println(p1)  
    println(p2)  
    println(p3)  
    println(p4)  
}
  • 带默认值的构造器

  带默认值得构造器,能实现上述辅助构造器相同得效果,并且更加简洁;因此,带默认值的构造器会更加常用

class Socket(var timeout: Int = 2000, var linger: Int = 3000) {
    override def toString = s"timeout: $timeout, linger: $linger"
}

object ScalaClass {
  class Socket(var timeout: Int = 2000, var linger: Int = 3000) {
    override def toString = s"timeout: $timeout, linger: $linger"
  }

  def main(args: Array[String]): Unit = {
    println(new Socket())
    println(new Socket(1000))
    println(new Socket(4000, 6000))
  }
}

5.2 伴生对象

  由于 Scala 中没有 static 关键字,因此,Scala 类中如果想要实现跟 Java 一样的静态属性,可以通过伴生类实现。
  Scala 中,当同时定义一个同名的 classobject,则 object 被称为伴生对象,class 被称为伴生类;object 中类的静态属性,不需要创建对象即可使用。

object ScalaClass {  
    class Animal(val name: String) {  
        def eat(): Unit = {  
        println(s"$name is eating")  
        }  
    }  

    object Animal {  
        def walk(): Unit = {  
        println("walking...")  
        }  
    }  

    def main(args: Array[String]): Unit = {  
        Animal.walk()  
        val tom = new Animal("Tom")  
        tom.eat()  
    }  
}

5.3 特质与抽象类

  Scala 中特质与抽象类类似都可以有抽象方法和带有实现的方法;但是特质可以多继承,抽象类只能单继承;抽象类可以有构造方法,特质没有抽象方法。下面例子将介绍特质和抽象类:

object ScalaClass {
  abstract class Animal(name: String) {
    // 先调用父类构造方法
    println(s"Animal's name is $name")

    def getName: String = {
      name
    }

    // 抽象方法
    def eat(): Unit
  }

  trait Wukong {
    def skill(): Unit = {
      println("72变")
    }
  }

  trait Circus {
    def perform(): Unit
  }

  class Monkey(name: String) extends Animal(name) with Wukong with Circus {
    // 再调用子类构造方法
    println(s"Monkey's name is $name")

    override def eat(): Unit = {
      println("吃香蕉")
    }

    override def perform(): Unit = {
      println("杂耍")
    }
  }

  // 当类没有有参构造和属性时, 可以简写为 class 类名
  class SunWanderer

  def main(args: Array[String]): Unit = {
    val wukong = new Monkey("悟空")
    wukong.skill()
    wukong.eat()
    wukong.perform()

    println("====================================")
    // 也可以通过创建类时, 使用 with 关键字来继承特质
    val wanderer = new SunWanderer with Wukong
    wanderer.skill()
  }
}

六、集合

Scala 中集合分为可变集合不可变集合两大类。

  • 可变集合:集合内部的元素是能动态添加的,这类集合都在 mutable 包中(需要手动导入)。
  • 不可变集合:集合内部的元素是不能动态添加的,这类集合都在 immutable 包中(默认导入)。

集合的使用需要实战中多加练习,具体操作可以参考下述资料,这里不加赘述。
参考
docs.scala-lang.org/overviews/s…
blog.csdn.net/qq_42873479…

七、隐式转换

7.1 隐式参数

方法可以具有隐式参数列表,由参数列表开头的 implicit 关键字标记。如果参数列表中的参数没有像往常一样传递,Scala 将查看它是否可以获得正确类型的隐式值,如果可以,则自动传递。

Scala 将查找这些参数的位置分为两类:

  • Scala 在调用包含隐式参数块的方法时,将首先查找可以直接访问的隐式定义和隐式参数。
  • 然后,它在所有伴生对象中查找与隐式候选类型相关的隐式标记的成员。
abstract class Monoid[A] {
  def add(x: A, y: A): A
  def unit: A
}

object ImplicitTest {
  implicit val stringMonoid: Monoid[String] = new Monoid[String] {
    def add(x: String, y: String): String = x concat y
    def unit: String = ""
  }
  
  implicit val intMonoid: Monoid[Int] = new Monoid[Int] {
    def add(x: Int, y: Int): Int = x + y
    def unit: Int = 0
  }
  
  def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
    if (xs.isEmpty) m.unit
    else m.add(xs.head, sum(xs.tail))
    
  def main(args: Array[String]): Unit = {
    println(sum(List(1, 2, 3)))       // uses IntMonoid implicitly
    println(sum(List("a", "b", "c"))) // uses StringMonoid implicitly
  }
}

7.2 隐式类

隐式类通常用于增强现有类的行为,下面例子介绍了对 java.util.LinkedList[T] 行为的增强。

  下面介绍的隐式转换,首先我们创建一个 Java 的 LinkedList,Java 的链表是没有 foreachmap 方法的,我们使用隐式转换来对 LinkedList 的方法进行增强。
  当我们使用某个对象的某个方法时,这个方法并没有在对象中声明。那么 Scala 编译器会去找是否有 implicit 声明的类 即隐式类,隐式类的构造器需要接收我们需要增强的对象的类型。

object ScalaImplicit {
  def main(args: Array[String]): Unit = {
    val list = new util.LinkedList[Int]()
    list.add(1)
    list.add(2)
    list.add(3)
    list.add(4)
    list.add(5)

    list.foreach(println)
    val stars = list.map("*" * _)
    println(stars)
  }

  implicit class RichList[T](list: java.util.LinkedList[T]) {
    def foreach(f: T => Unit): Unit = {
      val it = list.iterator()
      while(it.hasNext) f(it.next())
    }

    def map[U](f: T => U): java.util.LinkedList[U] = {
      val it = list.iterator()
      val tList = new util.LinkedList[U]
      while (it.hasNext) {
        tList.add(f(it.next()))
      }
      tList
    }
  }
}

7.3 隐式方法

隐式方法类似于隐式类,只是隐式方法在匹配到需要增强的类型时,会在方法体内 new 一个包含该方法的对象,然后就能使用该对象的方法了。

object ScalaImplicit {
  def main(args: Array[String]): Unit = {
    val list = new java.util.LinkedList[Int]()
    list.add(1)
    list.add(2)
    list.add(3)
    list.add(4)
    list.add(5)

    // 隐式方法名可以随便取, 但需要在对象使用增强方法前进行声明
    implicit def getRichList[T](list: java.util.LinkedList[T])  = {
      new RichList[T](list)
    }

    list.foreach(println)
    val list1 = list.map("*" * _)
    println(list1)

  }


  class RichList[T](list: java.util.LinkedList[T]) {
    def foreach(f: T => Unit): Unit = {
      val it = list.iterator()
      while(it.hasNext) f(it.next())
    }

    def map[U](f: T => U): java.util.LinkedList[U] = {
      val it = list.iterator()
      val tList = new util.LinkedList[U]
      while (it.hasNext) {
        tList.add(f(it.next()))
      }
      tList
    }
  }
}

相关参考

blog.csdn.net/itcast_cn/a…
docs.scala-lang.org/overviews/s…
docs.scala-lang.org/tour/tour-o…
github.com/bjmashibing…