ScalaQuickIN

快速入门 Scala

[TOC]

1. 学习目标

2. Scala 介绍

2.1 Scala 简介

Scala 创始人为 Martin Odersky马丁·奥德斯基。JDK5 和 JDK8 版本的Java编译器 javac 都是由他和他的团队编写的。

2.2 Scala 的六大特征

一句话总结:Scala 是一门以 JVM 为运行环境的静态类型编程语言,具备面向对象及函数是编程的特性。下面是官网对Scala特性的介绍:

SEAMLESS JAVA INTEROP 无缝与 JAVA互操作

Java 和 Scala 可以混编,在写 Scala 代码时可以引用 Java 的类。

Scala 运行在 JVM 上,所以 Java 和 Scala 可以自由的混合使用。这也就是在日常编写代码的过程中,经常看到Scala引用了很多Java包的原因。

TYPE INFERENCE 类型推断(自动推测类型)

使类型系统不是那么的死板(静态)。不要为了类型系统工作,而是让类型系统为你工作。

Scala是一种弱类型语言,即它在编写代码时,不需要像强类型语言(如 Java、C++等)定义变量的类别。

Scala有IntLongShortByteFloatDoubleCharBoolean这些基础数据类型。但与 Java 不同,在定义变量时,Scala用 var/val 来定义,让其自主判断数据类型。

其中val是常量,var是变量,一般来说,用val比较多(涉及资源的利用)。

1
2
3
4
5
// Java 中定义一个变量
String name = "ZhangSan";

// Scala 中定义一个变量
var name = "ZhangSan";

CONCURRENCY & DISTRIBUTION 并发和分布式(Actor)

Scala 在操作集合时使用了数据并行操作。并且使用Actor(Actor是一种不共享数据,依赖于消息传递的并发编程模式, 有效的避免了死锁、资源争夺等情况)来解决并发和分布式中的一些问题。或者使用 futures 类进行异步编程。

使用 Scala 进行并发以及分布式场景下的开发时有得天独厚的优势。

TRAITS 特性

Java 风格接口的灵活性类的强大功能相结合。认为是一种多重继承。

其实这就相当于 Java 的接口,但实际上它比接口还功能强大。 与接口不同的是,它还可以定义属性和方法的实现。

简单来说:TRAITS = Interface + Abstract

PATTERN MATCHING 模式匹配

更强大的 Switch,它可以匹配类的层级结构、序列以及更多。

HIGHER-ORDER FUNCTIONS 高阶函数

函数是一级对象。在保证类型安全的情况下编写它们。在任何地方使用它们,传递给任何东西,即函数可以作为参数传递到函数中。

2.3 具体应用

  • Kafka:分布式消息队列,内部代码经常用来处理并发的问题,用 Scala 可以大大简化其代码。
  • Spark:方便处理多线程场景,另外Spark主要用作内存计算,经常要用来实现复杂的算法,利用Scala这种函数式编程语言可以大大简化代码。

3. Scala 的安装和使用

3.1 Windows 下安装 Scala

Scala下载地址:The Scala Programming Language (scala-lang.org)

① 进入下载地址后,选择合适的Scala版本进行安装。记住安装的路径,方便配置环境变量。

② 配置环境变量 SCALA_HOME

③ 验证是否安装成功:进入命令行CMD,输入 scala -version,显示版本号及表明配置成功。

3.2 IDEA 2022 中配置 Scala 插件

① 进入IDEA,Files→Settings→Plugins

② 进入Marketolace界面,直接搜索Scala,安装即可。

③ 在编写 Scala 代码时,双击 Shift,选择 Add Framework 后,添加 Scala 框架,即可编写 Scala 代码。

4. Scala 基础

4.1 数据类型

数据类型 描述
Byte 8-bit 的有符号数字,范围在 -128 ~ 127
Short 16-bit 的有符号数字,范围在 -32768 ~ 32767
Int 32-bit 的有符号数字,范围在 -2147483648 ~ 2147483647
Long 64-bit 的有符号数字,范围在 -9223372036854775808 ~ 9223372036854775807
Float 32-bit IEEE 754 单精度浮点数
Double 64-bit IEEE 754 双精度浮点数
Char 16-bit Unicode 字符,范围 U+0000 ~ U+FFFF
String 字符串
Boolean 布尔类型
Unit 表示空值,与其他语言中的 void 相同
Null 空值或者空引用
Nothing 所有其他类型的子类型,表示没有值
Any 所有类型的超类,任何类型都属于Any
AnyRef 所有引用类型的超类
AnyVal 所有值类型的超类
Nil 表示长度为0的List

比较特殊的 None,是Option的两个子类之一,另一个是Some,用于安全的函数返回值。

Scala 推荐在可能返回空的方法使用 Option[X] 作为返回类型。如果有值就返回 Some[X],否则返回 None

1
2
3
4
5
6
def get(key: A): option[B] = {
if (contains(key))
Some(getValue(key))
else
None
}

4.2 变量和常量的声明

  • 变量用 var 定义,可修改

  • 常量用 val 定义,不可修改

  • 定义变量或常量的时候,也可以写上返回值的类型,一般省略,如 val a: Int = 10

  • 常量不可以再赋值

1
2
3
4
5
6
7
var name = "zhangsan"
println(name) // zhangsan
name = "lisi"
println(name) // lisi

val gender = "m"
gender = "m" // [Error] 不可以给常量赋值

4.3 键盘标准输入

编程中可以通过键盘输入语句来接收用户输入的数据(也就是 Java 中的 Scanner 对象)。
在 Scala 中只需要导入对应的包,比 Java 还要简单,不需要实例化对象。

1
2
3
4
5
6
7
8
9
10
11
import scala.io.StdIn

object Test {
def main(args: Array[String]): Unit = {
println("Please Input Your Name: ")
val name = StdIn.readLine()
println("Please Input Your Age: ")
val age = StdIn.readInt()
printf("Your Name is %s, Your Age is %d", name, age)
}
}

如上述代码一样,printf() 的用法和 Java 中一样,为格式化输出。注意使用规范即可。

符号 含义
%d 十进制数字
%s 字符串
%c 字符
%e 指数浮点数
%f 浮点数

4.4 类Class 和 对象Object

  • 在编写 Scala 代码时,一般不加分号;,如果一行中有多条语句,则可以用分号隔开,如:var a = 10; var b = 20
  • class 默认实现了 getter/setter 方法。
  • class 中如果有参数传入,那么这个构造器就是这个类的默认构造器。
  • class 在被 new 新建对象的时候,除了方法内部不执行,其他地方的代码都会执行,类似于Java中的工具类。
  • Object 里面不能传递参数,Object 里面的属性和方法都是静态的,类似于 Java 中 static 修饰的东西,类似于 Java 中的工具类。
  • 伴生类和伴生对象,在一个 Scala 文件中,如果 ClassObject 的名字一样,则互为伴生类和伴生对象。他们可以互相访问到互相的私有成员变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Person(xname:String, xage: Int) {
val name = xname
val age = xage
var money = 100
/**
* 重写构造器, 必须调用类的默认构造器
* @param xname
* @param xage
* @param xmoney
*/
def this(xname: String, xage: Int, xmoney: Int) {
this(xname, xage)
money = xmoney
}

println("Checkpoint") // `class` 在被 `new` 新建对象的时候,除了方法内部不执行,其他地方的代码都会执行

def test: Unit = {
println("Inside Function") // 方法内部不会执行
}
println("Checkpoint") // `class` 在被 `new` 新建对象的时候,除了方法内部不执行,其他地方的代码都会执行
}

object Test {
def main(args: Array[String]): Unit = {
val person1 = new Person("zhangsan", 20)
val person2 = new Person("lisi", 20, 1000)
println(person.name + ":" + person.age) // 默认实现了 getter/setter 方法
}
}

4.5 IF-ELSE 语句

Scala 中的条件判断语句同 Java 中的条件判断语句,语法结构基本一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import scala.io.StdIn

object IfDemo {
def main(args: Array[String]): Unit = {
println("请输入年龄")
val age = StdIn.readInt()
if (age >= 18 && age <= 100) {
println("成年")
} else if (age >= 0 && age < 18) {
println("未成年")
} else {
println("请输入 0 ~ 100 的数字")
}
}
}

4.6 Loop 循环语句

  • tountil 语句的区别
1
2
3
4
5
6
7
object ToUntilDemo {
def main(args: Array[String]): Unit = {
println(1 to 10) // to 表示 [1, 10], 输出: Range(1, 2, 3, ..., 10)
println(1 until 10) // until 表示 [1, 10), 输出: Range(1, 2, 3, ..., 9)
println(1 to (10, 2)) // 表示按照步长为`2`来输出数据, 输出: Range(1, 3, 5, 7, 9)
}
}
  • for 循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
object ForDemo {
def main(args: Array[String]): Unit = {
for (i <- 1 to 10) {
print(i + ' ') // 输出: 1 2 3 4 5 6 7 8 9 10
}

// Scala 中的 `for` 循环可以将 if 语句直接写在 for 循环中
for (i <- 1 to 10; if i > 5; if i % 2 == 0) {
printf("%d ", i) // 输出: 6 8 10
}

// 上面的写法可以等同于下面的写法
for (i <- 1 to 10) {
if (i > 5 && i % 2 == 0) {
printf("%d ", i)
}
}

// 双重 for 循环
for (i <- 1 to 10; j <- 1 to 5) {
println(i + ":" + j)
}

// 上面的写法可以等同于下面的写法
for (i <- 1 to 10) {
for (j <- 1 to 5) {
println(i + ":" + j)
}
}
}
}
  • Scala 中不能使用类似于 x++x-- 的操作,需要使用 x += 1 或者 x -= 1 来完成自增或者自减操作。
  • for 循环使用 yield 关键字返回一个集合,for { 子句 } yield {变量或表达式}for 循环中的 yield 会把当前的元素记下来,保存在集合中,循环结束后将返回该集合。Scala 中 for 循环是有返回值的。如果被循环的是 Map,返回的就是 Map,被循环的是 List,返回的就是 List,以此类推。
1
2
3
4
5
6
7
8
9
10
11
12
13
object Loop {
def main (args: Array[String]): Unit = {
val range = 1 to 10;
for (num <- range if num % 2 == 0 if num > 5) {
printf("%d", num); // 6 8 10
}

val result_1 = for (num<-range if num % 2 == 0 if num > 5) yield num // yield 变量
println(result_1) // Vector(6, 8, 10)
val result_2 = for (num<-range if num > 5) yield num % 2 // yield 表达式
println(result_2) // Vector(0, 1, 0, 1, 0)
}
}
  • whiledo...while
1
2
3
4
5
6
7
8
9
10
11
12
13
14
object Loop {
def main (args: Array[String]): Unit = {
var index = 0
while (index < 10) {
println("第" + index + "次循环")
index += 1
}

do {
index += 1
println("第" + index + "次循环")
} while(index < 20)
}
}

5. Scala 函数(方法)

5.1 函数的定义

1
2
3
4
5
6
7
8
// 函数(方法) 的定义
// def 函数名(参数x: 参数类型, 参数y: 参数类型): 返回类型 = {
// 函数体
// }

def function_name(x: Int, y: String): Unit = {
function_body
}

注意事项:

  1. Scala 使用 def 关键字告诉编译器这是一个函数(方法)
  2. 我们可以通过在参数列表后面加一个冒号:类型来显式地指定返回类型。
  3. 函数可以写返回类型,也可以不写,会自动推断(最后一行是什么类型,就被推断成什么类型)。有时候不能省略,必须写,比如在递归函数中或者函数的返回值是函数类型的时候。
  4. Scala 中函数有返回值时,可以写 return,也可以不写 return,不写 return 时会把函数中最后一行当做结果返回。当写 return 时,必须要写函数的返回类型
  5. 传递给方法的参数可以在方法中使用,并且 Scala 规定:方法的传过来的参数为常量 val 而不是变量 var
  6. 如果去掉函数体前面的等号=,那么这个函数返回类型必定是 Unit。这种说法无论函数体里面什么逻辑都成立,Scala 可以把任意类型转换为 Unit。假设,函数里面的逻辑最后返回了一个 String,那么这个返回值会被转换成 Unit,原本逻辑的值会被丢弃。这种方法往往适用于无返回值的函数中。

5.2 递归函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
object FunctionDemo {
/**
* 递归函数:
* 关键点在于递归的定义, 终止条件(避免无休止的递归, 导致栈溢出问题)
*/
def f1(num: Int): Int = {
if (num == 1) {
return num
}
num * f1(num-1) // f1(5) = 5*f1(4) = 5*4*f1(3) = 5*4*3*f1(2) = 5*4*3*2*f1(1)
}

def main (args: Array[String]): Unit = {
println(f1(5))
}
}

5.3 包含参数默认值的函数

  • 和其他语言没有任何区别
  • 默认值的函数中,如果传入的参数个数与函数定义相同,则传入的数值会覆盖默认值。
  • 如果不想覆盖默认值,且传入的参数个数小于定义的函数的参数,则需要指定参数名称。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
object FunctionDemo {
/**
* 包含参数默认值的函数
*/
def f2(x: Int=5, y: Int=10): Int = {
a + b
}

def main (args: Array[String]): Unit = {
println(f2()) // 输出: 15
println(f2(10, 20)) // 输出: 30
println(f2(10)) // 输出: 20
println(f2(y=30)) // 输出: 35
}
}

5.4 可变参数个数的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
object FunctionDemo {
/**
* 可变参数个数的函数
* 传入多个参数时, 多个参数之间用逗号分隔
* 传入的参数其实就是不定长数组
*/
def f3(elements: Int*): Int = {
var sum = 0
for (element <- elements) {
sum += element
}
sum
}

def main (args: Array[String]): Unit = {
println(f3(1, 2, 3, 4, 5)) // 输出
}
}

5.5 匿名函数

匿名函数有以下几种:

  • 有参匿名函数
  • 无参匿名函数
  • 有返回值的匿名函数

在定义和使用匿名函数时:

  • 可以将匿名函数返回给 val 定义的值
  • 匿名函数不能显示声明函数的返回类型

在 Scala 中 ,大多数情况=> 是匿名函数的显著标志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
object FunctionDemo {
/**
* 匿名函数: 经常和高阶函数一起使用
* 1. 有参匿名函数
* 2. 无参匿名函数
* 3. 有返回值的匿名函数
*/
def main (args: Array[String]): Unit = {
// 有参数匿名函数
val value1 = (a: Int) => {
println(a)
}
value1(1)

// 无参数匿名函数
val value2 = () => {
println("无参数匿名函数")
}
value2()

// 有返回值的匿名函数
val value3 = (a: Int, b: Int) => {
a + b
}
println(value3(4, 4))
}
}

5.6 嵌套函数

嵌套函数其实就是函数里套了函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
object FunctionDemo {
def f5 (num: Int): Int = {
@tailrec
def f6(a: Int, b: Int): Int = {
if (a == 1) {
b
} else {
f6(a-1, a*b)
}
}
f6(num, 1)
}

def main (args: Array[String]): Unit = {
f5(5)
}
}

5.7 偏应用函数

偏应用函数是一种表达式,不需要提供函数需要的所有参数,只需要提供部分,或不提供所需参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
object FunctionDemo {    
def main (args: Array[String]): Unit = {
// 这里只是一个普通的函数
def log(date: Date, log: String) {
println("Date is " + date + ", Log is " + log)
}

// 按照普通函数的方法使用
val date = new Date() // 与 Java 混编
log(date, "log1")
log(date, "log2")
log(date, "log3")

// 我们发现, 上面的程序除了 log 参数在改变, date 参数没有变化
// 此时我们可以使用偏应用函数来优化
// _ 下划线可以理解为一个变化的参数, 而 date 可以理解为一个固定的参数(他本身可能是变化的)
val logWithDate = log(date, _:String)
logWithDate("log_11")
logWithDate("log_12")
logWithDate("log_13")
}
}

5.8 高阶函数

高阶函数:函数的参数是函数,或者函数的返回类型是函数,或者函数的参数和函数的返回类型是函数的函数。

函数的格式为 (A)=>B, 后面没有函数体, 此函数接收类型 A 的参数, 返回类型 B 的函数。

  • 函数的参数是函数:其实就是定义一个传入参数返回类型模板参数,但是这个模板需要从其他地方实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 函数的参数是函数
def f7(a: Int, func: (Int, Int) => Int): Int {
val result = func(1, 2) // 这里将函数作为参数传入 f7, 而 result 使用了这个函数
// 这里我理解为模板, result 使用了一个 参数为(Int, Int), 返回类型为 Int 的函数模板
// 那么这个模板的具体实现是什么, 则需要从其他地方实现
a * result
}

def f8(x: Int, y: Int): Int = { // 这里就是对于模板的一种实现
x + y
}

println(f7(5, f8)) // 使用这种方式将 f8 传入, 并且打印输出, 结果为: 15

// 当然我们也可以结合匿名函数一起实现
println(f7(5, (x: Int, y: Int) => {x + y}))

  • 函数的返回类型是函数
1
2
3
4
5
6
7
8
9
10
// 函数的返回类型是函数
def f9(a: Int, b: Int): (String, String) => String = {
def f10(c: String, d: String): String = {
a + " " + b + " " + c + " " + d
}
f10 // 返回一个函数
}

// 像这种括号连着括号的`()()`, 往往代表着出现了函数的返回类型是函数的函数
println(f9(1, 2)("3", "4")) // 输出: 1 2 3 4
  • 函数的参数和函数的返回类型是函数
1
2
3
4
5
6
7
8
def f11(x: Int, f: (Int, Int) => Int): (Int, Int) => Int = {
f
}

println(f11(1, (a: Int, b: Int) => { a + b } ) (100, 200)) // 输出: 300

// _ 类似于 Java 中的 *. 通配符, 变量只使用一次的时候可以简写如下:
println(f11(1, (_ + _))(100, 200)) // 输出: 300

5.9 柯里化函数

柯里化函数,或称颗粒化函数,将参数变成颗粒散落简而言之就是将参数不断拆分。柯里化函数基本是在做这么一件事情:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。如果写成公式文字就是这样:

1
2
3
4
fn(a, b, c, d) => fn(a)(b)(c)(d)
fn(a, b, c, d) => fn(a)(b, c, d)
fn(a, b, c, d) => fn(a, b)(c)(d)
fn(a, b, c, d) => fn(a, b, c)(d)

可以理解为高阶函数的简化,类似于返回类型为函数的函数。

1
2
3
4
5
6
7
8
def f12(a: Int, b: Int, c: Int, d: Int) = {
a + b + c + d
}

// 柯里化函数: 可以理解为高阶函数的简化
def f13(a: Int, b: Int)(c: Int, d: Int) = {
a + b + c + d
}

6. Scala 字符串

Scala 中的字符串和 Java 中的字符串用法几乎完全相同。

StringStringBuilder的区别:String 不可修改,StringBuilder 可修改。

6.1 String

1
2
3
4
5
6
7
8
9
10
11
12
package com.szy.inspur.subowen.rdd.base

object StringDemo {
def main(args: Array[String]): Unit = {
val str_1: String = "aabbccdd"
println(str_1.indexOf("a")) // 输出: 0
println(str_1.indexOf(98)) // 输出: 2, 这里输入的整数是 ASCII 码
val str_2: String = "AABBCCDD"
println(str_1 == str_2) // 输出: false
println(str_1.compareToIgnoreCase(str_2)) // 输出: 0, 如果 str != str_2, 则输出 `-1`
}
}

6.2 StringBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.szy.inspur.subowen.rdd.base

import scala.collection.mutable

object StringDemo {
def main(args: Array[String]): Unit = {
val stringBuilder = new mutable.StringBuilder
stringBuilder.append("abc")
println(stringBuilder)

val result = 1 .+ (2) // Scala 中的运算操作其实都是调用函数
// 因此, `result 1 .+ (2)` 其实和 result = 1 + 2 一样

// StringBuilder 可以 `+=` Char 类型, 而不能 `+=` String 类型
// 普通的 `+` 只有 Char 类型追加在后面
stringBuilder ++= "c"; println(stringBuilder) // 输出: abcc
stringBuilder += 'd'; println(stringBuilder) // 输出: abccd
// stringBuilder += "e"; println(stringBuilder) // [ERROR]
// stringBuilder ++= 'f'; println(stringBuilder) // [ERROR]
stringBuilder + 'e'; println(stringBuilder) // 输出: abccde
stringBuilder + "f"; println(stringBuilder) // 输出: abccde, 可以看到 "f" 没有被添加到字符串后面
}
}

6.3 String 的操作方法

万金油appendappend()不受类型的限制。

7. 集合

7.1 数组

7.1.1 创建数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 创建数组的两种方式
* 1. new Array[String](3)
* 2. 直接 Array
*/

object ArrayDemo {
def main(args: Array[String]): Unit = {
// 方式 1
// 创建一个 Array
val array = Array(1, 2, 3)
println(array(0))

// 用 for 循环遍历一个 Array
for(x <- array) {
println(x)
}

// 用 foreach 遍历一个 array
// foreach( function )
array.foreach(i => {println(i)} )
array.foreach(println(_))
array.foreach(println)

println()
// 方式 2
val array_use_length = new Array[Int](3)
array_use_length.foreach(i => {
println(i)
}) // 0, 0, 0
println(array_use_length.isEmpty) // 输出: false
println(array_use_length.length) // 输出: 3
}
}

7.2 集合 Set

7.3 集合 Map

7.4 元组 Tuple

7.4.1 元组的定义
作者

NilEra

发布于

2024-06-10

更新于

2024-06-30

许可协议

评论