一、前言
本文是Spark学习前对Scala内容学习的一个笔记,将阐述如何快速上手使用Scala,以及一些Scala的特性。
二、快速入门
工欲善其事必先利其器,在开始学习和使用Scala之前,我们需要提前准备好Scala的开发环境。
1. 安装JDK(不加赘述)
2. 安装Scala
3. 配置IDEA
2.1 Scala安装
选择需要安装的Scala版本
下载解压
配置环境变量
SCALA_HOME:
SCALA_HOME=E:\Eddie\DevTool\scala-2.13.8
PATH:
PATH=%SCALA_HOME%\bin
验证 Windows环境下,cmd -> 输入 scala,显示如图界面,则表示Scala安装完成了
2.2 配置IDEA
在正式学习Scala之前,还需要先将我们刚刚下载好的Scala SDK配置到IDEA中。下面将展示将下载到本地的Scala SDK配置到IDEA中。
首先,我们需要先创建一个Maven项目(这里就不介绍如何创建Maven项目),创建完成后的Maven项目目录结构如下:
接下来,我们需要将Scala的SDK,也就是我们刚刚下载好的Scala添加到Maven项目中
选择你需要的版本,点击 “OK”,最后点击右下角 “Apply” 生效
此时,在main目录下创建scala目录,并将scala目录设置为源码目录
选中scala目录,然后右键 -> Mark Directory as -> Sources Root
此时,就可以在scala目录下创建Scala相关类了
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 中没有类似于其他语言的break和continue关键字;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 中,当同时定义一个同名的 class 和 object,则 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 的链表是没有 foreach 和 map 方法的,我们使用隐式转换来对 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…