avatar

目录
Scala学习笔记

Scala学习笔记

java语言的脚本化。

【菜鸟教程】https://www.runoob.com/scala/scala-tutorial.html

一、安装

  1. 下载 https://www.scala-lang.org/download/

  2. 安装【推荐下载2.11.12,后面spark2.4.3匹配这个版本】

  3. 配置环境变量(菜鸟上有教程)

  4. CMD中运行

    Code
    >scala
    Welcome to Scala 2.13.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_251).
    Type in expressions for evaluation. Or try :help.

    scala> println("Hello World!")
    Hello World!

注意 JDK版本,可能会报错

Code
>java -version
java version "1.8.0_251"

二、IDEA中的Scala插件

  1. 安装插件

    avatar

  2. 创建项目/模块

    avatar

    avatar

  3. 创建一个对象

    avatar

  4. 编写代码

    scala
    object Hello {
    def main(args: Array[String]): Unit = {
    print("Hello World!")
    }
    }
  5. 运行

    avatar

    avatar

三、变量

3.1 分类

  • val:常量,不可变
  • var:变量
Code
scala> var a = 1
a: Int = 1

scala> val b = 2
b: Int = 2

scala> b = 5
^
error: reassignment to val

scala> val str:String = "kkk"
str: String = kkk

3.2 类型

avatar

Scala 与 Java有着相同的数据类型,下表列出了 Scala 支持的数据类型:

数据类型 描述
Byte 8位有符号补码整数。数值区间为 -128 到 127
Short 16位有符号补码整数。数值区间为 -32768 到 32767
Int 32位有符号补码整数。数值区间为 -2147483648 到 2147483647
Long 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807
Float 32 位, IEEE 754 标准的单精度浮点数
Double 64 位 IEEE 754 标准的双精度浮点数
Char 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF
String 字符序列
Boolean true或false
Unit 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。
Null null 或空引用
Nothing Nothing类型在Scala的类层级的最底端;它是任何其他类型的子类型。
Any Any是所有其他类的超类
AnyRef AnyRef类是Scala里所有引用类(reference class)的基类

上表中列出的数据类型都是对象,也就是说scala没有java中的原生类型。在scala是可以对数字等基础类型调用方法的。

3.3 类型转换

Code
scala> var a = 111
a: Int = 111

scala> var b = a.toString
b: String = 111

自定义类型检查和转换

Code
$scala>class Animal{}
$scala>class Dog extends Animal{}
$scala>val d = new Dog();
$scala>d.isInstanceOf[Animal] //true,===> instanceOf
$scala>val a = d.asInstanceOf[Animal] //强转,===> (Animal)d

//得带对象的类
$scala>d.getClass //d.getClass();
$scala>d.getClass == classOf[Dog] //精确匹配

3.4 lazy 延迟计算

有时候可以按需加载,即需要的时候再进行计算。

Code
scala> lazy val x = scala.io.Source.fromFile("d:/tmp")
x: scala.io.BufferedSource = <lazy>

// 当我们使用x的时候,才会进行计算
scala> x
java.io.FileNotFoundException: d:\tmp (拒绝访问。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at scala.io.Source$.fromFile(Source.scala:92)
at scala.io.Source$.fromFile(Source.scala:77)
at scala.io.Source$.fromFile(Source.scala:55)
at .x$lzycompute(<console>:1)
at .x(<console>:1)
... 28 elided

3.5 表达式

任何一条表达式都有返回值。如果一个{}中,有多个表达式,其值为最后一个表达式的值。

如果只有一个表达式可以不加分号(;),多个就要用分号隔开。

四、条件判断

4.1 if-else

Code
scala> if (1 > 2) {
| print("if-1")
| } else if (1 == 2) {
| print("if-2")
| } else print("if-3")
if-3
scala>

注意:如果要换行,要用{}包起来,不然会报错!

五、循环语句

5.1 while

Code
scala> var a = 1
a: Int = 1

scala> while(a < 5) {println(a); a += 1;}
1
2
3
4

还有 do … while

5.2 for

闭区间

Code
scala> for (i <- 1 to 5) println(i)
1
2
3
4
5

左闭右开区间

Code
scala> for (i <- 1 until 5) println(i)
1
2
3
4

5.3 循环中断

scala中没有break和continue来中止循环。但是提供了一个break语句来实现。

Code
scala> var a = 1
a: Int = 1

scala> while(true){
| println(a);
| a += 1;
| if(a >= 3) break();
| }
1
2
scala.util.control.BreakControl

scala>

5.4 for 循环过滤

可以在for中对不满足条件的进行过滤操作。

打印出偶数:

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

5.4 for 双重循环

Code
scala> for (i <- 1 to 3; j <- 1 to 2) {
| printf("i: %d, j: %d\n", i, j);
| }
i: 1, j: 1
i: 1, j: 2
i: 2, j: 1
i: 2, j: 2
i: 3, j: 1
i: 3, j: 2

5.5 for 使用 yield

你可以将 for 循环的返回值作为一个变量存储。语法格式如下:

Code
scala> for (x <- 1 to 10) yield x
res18: IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

循环中的 yield 会把当前的元素记下来,保存在集合中,循环结束后将返回该集合。

六、访问修饰符

Scala 访问修饰符基本和Java的一样,分别有:privateprotectedpublic

如果没有指定访问修饰符,默认情况下,Scala 对象的访问级别都是 public。

Scala 中的 private 限定符,比 Java 更严格,在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员。

作用域保护

Scala中,访问修饰符可以通过使用限定词强调。格式为:

Code
private[x] 



protected[x]

这里的x指代某个所属的包、类或单例对象。如果写成private[x],读作”这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对像可见外,对其它所有类都是private。

这种技巧在横跨了若干包的大型项目中非常有用,它允许你定义一些在你项目的若干子包中可见但对于项目外部的客户却始终不可见的东西。

七、方法与函数

Scala 有方法与函数,二者在语义上的区别很小。Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。换句话来说在类中定义的函数即是方法。

Scala 中的方法跟 Java 的类似,方法是组成类的一部分。

Scala 中的函数则是一个完整的对象,Scala 中的函数其实就是继承了 Trait 的类的对象。

Scala 中使用 val 语句可以定义函数def 语句定义方法

Code
class Test{
def m(x: Int) = x + 3
val f = (x: Int) => x + 3
}

7.1 方法定义

方法定义由一个 def 关键字开始,紧接着是可选的参数列表,一个冒号 : 和方法的返回类型,一个等于号 = ,最后是方法的主体。

Scala 方法定义格式如下:

Code
def functionName ([参数列表]) : [return type] = {
function body
return [expr]
}
  • 可以不用写return,每个表达式都有返回值 {}最后会有一个值给函数返回;

  • 没有参数的话,可以不加();

例子:

Code
def addInt( a:Int, b:Int ) : Int = {
var sum:Int = 0
sum = a + b
return sum
}

如果返回类型是空(即JAVA中的void),那么返回类型Unit可以省略;如果参数为空,那么()可以省略:

Code
def printMe( ) : Unit = {
println("Hello, Scala!")
}

等价于:

Code
scala> def printMe = println("Hello, Scala!")
printMe: Unit

scala> printMe
Hello, Scala!

7.2 递归函数

递归函数必须显式定义返回类型!

打印阶乘:

Code
scala> def fac(n:Int):Int = if(n == 1) 1 else n * fac(n - 1);
fac: (n: Int)Int

scala> fac(3)
res24: Int = 6

7.3 函数参数默认值

以下是定义了3个参数,其中1.3位带默认参数。的各种情况。

Code
scala> def decorate(prefix:String = "[[",str:String,suffix:String = "]]") = {
| prefix + str + suffix
| }
decorate: (prefix: String, str: String, suffix: String)String

// 可以指定参数名赋值
scala> decorate(str="hello")
res26: String = [[hello]]

// 参数不够
scala> decorate("11")
^
error: not enough arguments for method decorate: (prefix: String, str: String, suffix: String)String.
Unspecified value parameter str.

// 部分参数
scala> decorate("11","22")
res30: String = 1122]]

// 全部参数
scala> decorate("<<","22",">>")
res32: String = <<22>>

// 指定参数名,但不按定义顺序
scala> decorate(str="hello",prefix="<<")
res34: String = <<hello]]

7.4 可变参数

可变参数带个星号(*):

Code
scala> def sum(a:Int*) = {
| var s = 0;
| for (x <- a) s += x;
| s
| }
sum: (a: Int*)Int

// 错误!scala.collection.immutable.Range.Inclusive = Range 1 to 10
scala> sum(1 to 10)
^
error: type mismatch;
found : scala.collection.immutable.Range.Inclusive
required: Int

// 正确!将1 to 10当做序列处理
scala> sum(1 to 10:_*)
res36: Int = 55

7.5 apply() 和 update()

Array(100)            //Array.apply(100);
Array(100) = 200    //Array.update(200)

7.6 闭包

可以简单的理解为:一个函数中,使用了函数外部的一个变量。

Code
var factor = 3  
val multiplier = (i:Int) => i * factor

七、异常

Scala 的异常处理和其它语言比如 Java 类似。

Scala 的方法可以通过抛出异常的方法的方式来终止相关代码的运行,不必通过返回值。

7.1 抛出异常

Scala 抛出异常的方法和 Java一样,使用 throw 方法,例如,抛出一个新的参数异常:

Code
throw new IllegalArgumentException

7.2 try | catch | finally

异常捕捉的机制与其他语言中一样,如果有异常发生,catch字句是按次序捕捉的。因此,在catch字句中,越具体的异常越要靠前,越普遍的异常越靠后。 如果抛出的异常不在catch字句中,该异常则无法处理,会被升级到调用者处。

捕捉异常的catch子句,语法与其他语言中不太一样。在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case字句。

finally 语句用于执行不管是正常处理还是有异常发生时都需要执行的步骤。

例子:

scala
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException

object Test {
def main(args: Array[String]) {
try {
val f = new FileReader("input.txt")
} catch {
case ex: FileNotFoundException => {
println("Missing file exception")
}
case ex: IOException => {
println("IO Exception")
}
} finally {
println("Exiting finally...")
}
}
}

执行以上代码,输出结果为:

Code
$ scalac Test.scala 
$ scala Test
Missing file exception
Exiting finally...

八、字符串

在 Scala 中,字符串的类型实际上是 Java String,它本身没有 String 类。

在 Scala 中,String 是一个不可变的对象,所以该对象不可被修改。这就意味着你如果修改字符串就会产生一个新的字符串对象。

8.1 创建字符串

不可变

Code
scala> var str = "Hello World!"
str: String = Hello World!
Code
scala> var str:String = "Hello World!"
str: String = Hello World!

可变

我们前面提到过 String 对象是不可变的,如果你需要创建一个可以修改的字符串,可以使用 String Builder 类,如下实例:

Code
object Test {
def main(args: Array[String]) {
val buf = new StringBuilder;
buf += 'a'
buf ++= "bcdef"
println( "buf is : " + buf.toString );
}
}

执行以上代码,输出结果为:

Code
$ scalac Test.scala
$ scala Test
buf is : abcdef

8.2 字符串长度

Code
scala> str.length()
res43: Int = 12

8.3 字符串连接

Code
scala> str.concat("I'm Tom.")
res45: String = Hello World!I'm Tom.
Code
scala> "Hello," + "Scala"
res46: String = Hello,Scala

8.4 String 其他方法

可以像使用JAVA中的String方法到Scala中来使用!

【菜鸟示例】https://www.runoob.com/scala/scala-strings.html

八、数组

8.1 定长数组

可以理解[]相当于JAVA中<>的泛型:

Code
var z = new Array[String](3)

以上语法中,z 声明一个字符串类型的数组,数组长度为 3 ,可存储 3 个元素。我们可以为每个元素设置值,并通过索引来访问每个元素,如下所示:

Code
scala> var z = new Array[String](3)
z: Array[String] = Array(null, null, null)

// 根据下表赋值
scala> z(0) = "hello"

scala> z
res48: Array[String] = Array(hello, null, null)

// 根据下表获取数据
scala> z(0)
res49: String = hello

注意:

Code
scala> var z = Array(1,2,3)
z: Array[Int] = Array(1, 2, 3)

这里同样可以创建数组!但是这里代表的意思是:创建一个数组,并赋予3个初始值!而上面那个是创建一个长度为3的数组

8.2 变长数组

类似C++中的Vector。

创建数组缓冲区对象

Code
scala> import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer

scala> val bufArray = ArrayBuffer[Int]()
bufArray: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()

末尾追加一个元素

Code
scala> bufArray += 1
res53: bufArray.type = ArrayBuffer(1)

末尾追加一个数组

Code
scala> bufArray ++= Array(2,3,4)
res55: bufArray.type = ArrayBuffer(1, 2, 3, 4)

删除末尾N个元素

Code
scala> bufArray.trimEnd(2)

scala> bufArray
res59: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2)

在索引处插入元素

Code
scala> bufArray.insert(0,0)

scala> bufArray
res61: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(0, 1, 2)

删除索引处元素

Code
scala> bufArray.remove(1)
res62: Int = 1

scala> bufArray
res63: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(0, 2)

删除索引处开始N个元素

Code
scala> bufArray.remove(0, 2)

scala> bufArray
res66: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()

转为普通数组

Code
bufArray.toArray

8.3 多维数组

多维数组一个数组中的值可以是另一个数组,另一个数组的值也可以是一个数组。矩阵与表格是我们常见的二维数组。

创建矩阵数组

Code
scala> var a = Array.ofDim[Int](3,3)
a: Array[Array[Int]] = Array(Array(0, 0, 0), Array(0, 0, 0), Array(0, 0, 0))

8.4 数组常用方法

排序

Code
scala> var a = Array(1,5,3,2,4)
a: Array[Int] = Array(1, 5, 3, 2, 4)

scala> import scala.util.Sorting._
import scala.util.Sorting._

scala> quickSort(a)

scala> a
res71: Array[Int] = Array(1, 2, 3, 4, 5)

求和

Code
scala> a.sum
res67: Int = 15

最大(小)值

Code
scala> a.min
res68: Int = 1

scala> a.max
res69: Int = 5

格式输出

以什么字符串开头,什么作为分隔符,什么作为结束。

Code
scala> a.mkString("[",",","]")
res72: String = [1,2,3,4,5]

数组合并

Code
scala> var b = Array(6,7,8)
b: Array[Int] = Array(6, 7, 8)

scala> a ++ b
res76: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8)



scala> a.concat(b)
res77: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8)

创建区间数组

第三个参数是步长

Code
scala> var c = Array.range(1, 10, 2)
c: Array[Int] = Array(1, 3, 5, 7, 9)

Scala数组所有方法

下表中为 Scala 语言中处理数组的重要方法,使用它前我们需要使用 import Array._ 引入包。

序号 方法和描述
1 def apply( x: T, xs: T* ): Array[T]创建指定对象 T 的数组, T 的值可以是 Unit, Double, Float, Long, Int, Char, Short, Byte, Boolean。
2 def concat[T]( xss: Array[T]* ): Array[T]合并数组
3 def copy( src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int ): Unit复制一个数组到另一个数组上。相等于 Java’s System.arraycopy(src, srcPos, dest, destPos, length)。
4 def empty[T]: Array[T]返回长度为 0 的数组
5 def iterate[T]( start: T, len: Int )( f: (T) => T ): Array[T]返回指定长度数组,每个数组元素为指定函数的返回值。以上实例数组初始值为 0,长度为 3,计算函数为a=>a+1scala> Array.iterate(0,3)(a=>a+1) res1: Array[Int] = Array(0, 1, 2)
6 def fill[T]( n: Int )(elem: => T): Array[T]返回数组,长度为第一个参数指定,同时每个元素使用第二个参数进行填充。
7 def fill[T]( n1: Int, n2: Int )( elem: => T ): Array[Array[T]]返回二数组,长度为第一个参数指定,同时每个元素使用第二个参数进行填充。
8 def ofDim[T]( n1: Int ): Array[T]创建指定长度的数组
9 def ofDim[T]( n1: Int, n2: Int ): Array[Array[T]]创建二维数组
10 def ofDim[T]( n1: Int, n2: Int, n3: Int ): Array[Array[Array[T]]]创建三维数组
11 def range( start: Int, end: Int, step: Int ): Array[Int]创建指定区间内的数组,step 为每个元素间的步长
12 def range( start: Int, end: Int ): Array[Int]创建指定区间内的数组
13 def tabulate[T]( n: Int )(f: (Int)=> T): Array[T]返回指定长度数组,每个数组元素为指定函数的返回值,默认从 0 开始。以上实例返回 3 个元素:scala> Array.tabulate(3)(a => a + 5) res0: Array[Int] = Array(5, 6, 7)
14 def tabulate[T]( n1: Int, n2: Int )( f: (Int, Int ) => T): Array[Array[T]]返回指定长度的二维数组,每个数组元素为指定函数的返回值,默认从 0 开始。

九、集合

【菜鸟集合文档】https://www.runoob.com/scala/scala-collections.html

9.1 Map 映射

9.1.1 创建

可变 Map

Code
// 空哈希表,键为字符串,值为整型
var A:Map[Char,Int] = Map()

// Map 键值对演示
scala> var map = Map(1->"hello",2->"world")
map: scala.collection.immutable.Map[Int,String] = Map(1 -> hello, 2 -> world)

不可变 Map

Code
scala.collection.immutable.Map[Int,String]

9.1.2 添加与删除

添加

Code
scala> map + (3->"Scala")
res81: scala.collection.immutable.Map[Int,String] = Map(1 -> hello, 2 -> world, 3 -> Scala)
Code
scala> map += (3->"Scala")

scala> map
res84: scala.collection.immutable.Map[Int,String] = Map(1 -> hello, 2 -> world, 3 -> Scala)

合并

注意!重复的Key会被后面覆盖

Code
scala> var map1 = Map(1->"one",2->"two")
map1: scala.collection.immutable.Map[Int,String] = Map(1 -> one, 2 -> two)

scala> var map2 = Map(2->"towTmp",3->"three")
map2: scala.collection.immutable.Map[Int,String] = Map(2 -> towTmp, 3 -> three)

scala> map1 ++ map2
res93: scala.collection.immutable.Map[Int,String] = Map(1 -> one, 2 -> towTmp, 3 -> three)

删除

Code
scala> map -= 1

scala> map
res90: scala.collection.immutable.Map[Int,String] = Map(2 -> world, 3 -> Scala)

9.1.3 Key-Value 基本操作

获取元素

Code
scala> map(2)
res101: String = world

返回所有Key

Code
scala> map.keys
res85: Iterable[Int] = Set(1, 2, 3)

返回所有Value

Code
scala> map.values
res87: Iterable[String] = Iterable(hello, world, Scala)

判断空

Code
scala> map.isEmpty
res88: Boolean = false

查看Map中是否存在指定Key

Code
scala> map.contains(3)
res105: Boolean = true

9.1.4 遍历

Code
scala> map1
res99: scala.collection.immutable.Map[Int,String] = Map(1 -> one, 2 -> two, 3 -> three)

scala> map1.keys.foreach{i => printf("Key: %d, Value: %s\n", i, map1(i))}
Key: 1, Value: one
Key: 2, Value: two
Key: 3, Value: three
Code
scala> for((k, v) <- map1) printf("Key: %d, Value: %s\n", k, v)
Key: 1, Value: one
Key: 2, Value: two
Key: 3, Value: three

9.2 Tuple 元组

元组:一组数据的集合

9.2.1 创建

以一个()返回

Code
scala> var tuple = (1,"2",true)
tuple: (Int, String, Boolean) = (1,2,true)

9.2.2 获取元素

Code
scala> tuple._1
res109: Int = 1

注意:下标是1开始

9.2.3 zip 拉链操作

数组进行ZIP,两两组合成一个元组,多余无法匹配的被丢弃。

Code
scala> var array1 = Array(1, 2, 3, 4)
array1: Array[Int] = Array(1, 2, 3, 4)

scala> var array2 = Array("One","Two","Three")
array2: Array[String] = Array(One, Two, Three)

scala> array1.zip(array2)
res111: Array[(Int, String)] = Array((1,One), (2,Two), (3,Three))

9.3 List 列表

Scala 列表类似于数组,它们所有元素的类型都相同,但是它们也有所不同:列表是不可变的,值一旦被定义了就不能改变,其次列表 具有递归的结构(也就是链接表结构)而数组不是。。

9.3.1 创建

scala
// 字符串列表
val site: List[String] = List("Runoob", "Google", "Baidu")

// 整型列表
val nums: List[Int] = List(1, 2, 3, 4)

// 空列表
val empty: List[Nothing] = List()

// 二维列表
val dim: List[List[Int]] =
List(
List(1, 0, 0),
List(0, 1, 0),
List(0, 0, 1)
)

构造列表的两个基本单位是 Nil:: (:: 是右结合)

Nil 也可以表示为一个空列表。

以上实例我们可以写成如下所示:

scala
// 字符串列表
val site = "Runoob" :: ("Google" :: ("Baidu" :: Nil))

// 整型列表
val nums = 1 :: (2 :: (3 :: (4 :: Nil)))

// 空列表
val empty = Nil

// 二维列表
val dim = (1 :: (0 :: (0 :: Nil))) ::
(0 :: (1 :: (0 :: Nil))) ::
(0 :: (0 :: (1 :: Nil))) :: Nil

fill

我们可以使用 List.fill() 方法来创建一个指定重复数量的元素列表:

scala
object Test {
def main(args: Array[String]) {
val site = List.fill(3)("Runoob") // 重复 Runoob 3次
println( "site : " + site )

val num = List.fill(10)(2) // 重复元素 2, 10 次
println( "num : " + num )
}
}

9.3.2 基本操作

  • head 返回列表第一个元素

  • tail 返回一个列表,包含除了第一元素之外的其他元素

  • isEmpty 在列表为空时返回true

  • reverse 用于将列表的顺序反转

9.3.3 连接列表

你可以使用 ::: 运算符或 List.:::() 方法或 List.concat() 方法或ListA ++ ListB来连接两个或多个列表。

scala
object Test {
def main(args: Array[String]) {
val site1 = "Runoob" :: ("Google" :: ("Baidu" :: Nil))
val site2 = "Facebook" :: ("Taobao" :: Nil)

// 使用 ::: 运算符
var fruit = site1 ::: site2
println( "site1 ::: site2 : " + fruit )

// 使用 List.:::() 方法
fruit = site1.:::(site2)
println( "site1.:::(site2) : " + fruit )

// 使用 concat 方法
fruit = List.concat(site1, site2)
println( "List.concat(site1, site2) : " + fruit )
}
}

9.4 Set 集合

Scala Set(集合)是没有重复的对象集合,所有的元素都是唯一的。

Scala 集合分为可变的和不可变的集合。

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

9.4.1 创建、添加、删除

默认引用 scala.collection.immutable.Set,不可变集合实例如下:

scala
val set = Set(1,2,3)
println(set.getClass.getName) //

println(set.exists(_ % 2 == 0)) //true
println(set.drop(1)) //Set(2,3)

如果需要使用可变集合需要引入 scala.collection.mutable.Set:

scala
import scala.collection.mutable.Set // 可以在任何地方引入 可变集合

val mutableSet = Set(1,2,3)
println(mutableSet.getClass.getName) // scala.collection.mutable.HashSet

mutableSet.add(4) // 添加
mutableSet.remove(1) // 删除
mutableSet += 5
mutableSet -= 2

println(mutableSet) // Set(5, 3, 4)

val another = mutableSet.toSet
println(another.getClass.getName) // scala.collection.immutable.Set

注意: 虽然可变Set和不可变Set都有添加或删除元素的操作,但是有一个非常大的差别。对不可变Set进行操作,会产生一个新的set,原来的set并没有改变,这与List一样。 而对可变Set进行操作,改变的是该Set本身,与ListBuffer类似。

9.4.2 连接集合

你可以使用 ++ 运算符或 Set.++() 方法来连接两个集合。如果元素有重复的就会移除重复的元素。实例如下:

scala
object Test {
def main(args: Array[String]) {
val site1 = Set("Runoob", "Google", "Baidu")
val site2 = Set("Faceboook", "Taobao")

// ++ 作为运算符使用
var site = site1 ++ site2
println( "site1 ++ site2 : " + site )

// ++ 作为方法使用
site = site1.++(site2)
println( "site1.++(site2) : " + site )
}
}

执行以上代码,输出结果为:

Code
$ vim Test.scala 
$ scala Test.scala
site1 ++ site2 : Set(Faceboook, Taobao, Google, Baidu, Runoob)
site1.++(site2) : Set(Faceboook, Taobao, Google, Baidu, Runoob)

9.4.3 交集

你可以使用 Set.& 方法或 Set.intersect 方法来查看两个集合的交集元素。实例如下:

scala
object Test {
def main(args: Array[String]) {
val num1 = Set(5,6,9,20,30,45)
val num2 = Set(50,60,9,20,35,55)

// 交集
println( "num1.&(num2) : " + num1.&(num2) )
println( "num1.intersect(num2) : " + num1.intersect(num2) )
}
}

执行以上代码,输出结果为:

Code
$ vim Test.scala 
$ scala Test.scala
num1.&(num2) : Set(20, 9)
num1.intersect(num2) : Set(20, 9)

十、迭代器

Scala Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法。

迭代器 it 的两个基本操作是 nexthasNext

调用 it.next() 会返回迭代器的下一个元素,并且更新迭代器的状态。

调用 it.hasNext() 用于检测集合中是否还有元素。

让迭代器 it 逐个返回所有元素最简单的方法是使用 while 循环:

Code
object Test {
def main(args: Array[String]) {
val it = Iterator("Baidu", "Google", "Runoob", "Taobao")

while (it.hasNext){
println(it.next())
}
}
}

执行以上代码,输出结果为:

Code
$ scalac Test.scala 
$ scala Test
Baidu
Google
Runoob
Taobao

最大最小值

你可以使用 it.minit.max 方法从迭代器中查找最大与最小元素,实例如下:

Code
object Test {
def main(args: Array[String]) {
val ita = Iterator(20,40,2,50,69, 90)
val itb = Iterator(20,40,2,50,69, 90)

println("最大元素是:" + ita.max )
println("最小元素是:" + itb.min )

}
}

执行以上代码,输出结果为:

Code
$ scalac Test.scala 
$ scala Test
最大元素是:90
最小元素是:2

获取迭代器的长度

你可以使用 it.sizeit.length 方法来查看迭代器中的元素个数。实例如下:

Code
object Test {
def main(args: Array[String]) {
val ita = Iterator(20,40,2,50,69, 90)
val itb = Iterator(20,40,2,50,69, 90)

println("ita.size 的值: " + ita.size )
println("itb.length 的值: " + itb.length )

}
}

执行以上代码,输出结果为:

Code
$ scalac Test.scala 
$ scala Test
ita.size 的值: 6
itb.length 的值: 6

【其他常用方法】https://www.runoob.com/scala/scala-iterators.html

十一、类 与 对象

类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。

如果不什声明修饰符,那么默认是public

11.1 定义类

示例

scala
object Hello {
class Point(val xc: Int,var yc: Int) {
// 编译后生成getter和setter方法:
// public int x();
// public void x_$eq(int);
var x: Int = xc
var y: Int = yc
// 常量只有getter方法,没有setter方法
val name = "P1"
// 私有变量必须赋初始值,getter和setter方法也是私有
// 同时限定了id只能在本对象中使用,不加的话,在函数参数中其他Point对象也能用
private[this] var id = 0


def move(dx: Int, dy: Int) {
x = x + dx
y = y + dy
println ("x 的坐标点: " + x)
println ("y 的坐标点: " + y)
}
}

def main(args: Array[String]): Unit = {
var p = new Point(1, 2)
// public int x(); ==> p.x 因为没有参数()可以省略
println(p.x)
// public void x_$eq(int); ==> p.x_=(5)
p.x_=(5)
println(p.x)
// public void x_$eq(int); 相当于C++里的运算符重载,可以直接用 =
p.x = 4
println(p.x)
}
}

运行结果

Code
1
5
4

11.2 BeanProperty 注解

为了能更好的配合JAVA的一些工具类,提供了注解的方式,来生成JAVA风格的getter和setter。

scala
import scala.beans.BeanProperty

// 编译后生成JAVA风格的getter和setter方法:
// public int getY();
// public void setY(int);
@BeanProperty
var y: Int = yc

11.3 构造函数

11.3.1 主构造

scala
class Person(val name:String,var age:Int , id :Int){
def hello() = println(id)
}

编译 scalac test.scalajavap Person.class

java
public class Person {
public java.lang.String name();
public int age();
public void age_$eq(int);
public void hello();
public Person(java.lang.String, int, int);
}

可以看到如果在类参数中加上var\val或者不加,也会生成相应的属性。

  • val:只读,即只有get

  • var:有get和set

  • 无类型:虽然编译后没有该属性,但如果有用到该属性的时候,也会创建一个。

    scala
    var p = new Person("tom", 15, 5)
    p.hello()
    p.id // 错误!无法调用

    输出结果:

    Code
    5

11.3.2 辅助构造

注意:使用辅助构造的时候,第一行必须有this()或this(…)

scala
class Person{
var id = 1 ;
var name = "tom" ;
var age = 12;

//辅助构造
def this(name:String){
this();
this.name = name ;
}
//辅助构造
def this(name:String,age:Int){
//调用辅助构造!
this(name) ;
this.age = age ;
}
}

11.4 object 对象

11.4.1 单例对象

说明:

scala没有静态的概念,如果需要定义静态成员,可以通过object实现。object对象不能带参数。

编译完成后,会生成对应的类,方法都是静态方法,非静态成员对应到单例类中

单例类以Util$作为类名称。

scala
object Util{
//单例类中.(Util$)
private var brand = "benz" ;
//静态方法.
def hello() = println("hello world");
}
scala
def main(args: Array[String]): Unit = {
Util.hello()
}

运行结果:

Code
hello world

11.4.2 伴生对象

当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象:companion object。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类:companion class。类和它的伴生对象可以互相访问其私有成员。

scala
class Car{
def stop() = {
Car.run() // 可以调用!因为是伴生
println("stop....")
}
}

object Car{
private def run() = println("run...")
}

def main(args: Array[String]): Unit = {
new Car().stop()
//Car.run() 错误!不能调用私有静态
}

输出结果

Code
run...
stop....

11.5 抽象类 与 继承

一个简单的例子

scala
//抽象类
abstract class Animal(val name:String){
//抽象字段,没有初始化。
val id:Int ;
//抽象方法,没有方法体,不需要抽象关键字修饰。
def run() ;
}

Scala继承一个基类跟Java很相似, 但我们需要注意以下几点:

  • 1、重写一个非抽象方法必须使用override修饰符。
  • 2、只有主构造函数才可以往基类的构造函数里写参数。
  • 3、在子类中重写超类的抽象方法时,你不需要使用override关键字。

接下来让我们来看个实例:

scala
class Point(xc: Int, yc: Int) {
var x: Int = xc
var y: Int = yc

def move(dx: Int, dy: Int) {
x = x + dx
y = y + dy
println ("x 的坐标点: " + x);
println ("y 的坐标点: " + y);
}
}

class Location(override val xc: Int, override val yc: Int,
val zc :Int) extends Point(xc, yc){
var z: Int = zc

def move(dx: Int, dy: Int, dz: Int) {
x = x + dx
y = y + dy
z = z + dz
println ("x 的坐标点 : " + x);
println ("y 的坐标点 : " + y);
println ("z 的坐标点 : " + z);
}
}

Scala 使用 extends 关键字来继承一个类。实例中 Location 类继承了 Point 类。Point 称为父类(基类),Location 称为子类。

override val xc 为重写了父类的字段。

继承会继承父类的所有属性和方法,Scala 只允许继承一个父类(单继承)

实例如下:

scala
import java.io._

class Point(val xc: Int, val yc: Int) {
var x: Int = xc
var y: Int = yc
def move(dx: Int, dy: Int) {
x = x + dx
y = y + dy
println ("x 的坐标点 : " + x);
println ("y 的坐标点 : " + y);
}
}

class Location(override val xc: Int, override val yc: Int,
val zc :Int) extends Point(xc, yc){
var z: Int = zc

def move(dx: Int, dy: Int, dz: Int) {
x = x + dx
y = y + dy
z = z + dz
println ("x 的坐标点 : " + x);
println ("y 的坐标点 : " + y);
println ("z 的坐标点 : " + z);
}
}

object Test {
def main(args: Array[String]) {
val loc = new Location(10, 20, 15);

// 移到一个新的位置
loc.move(10, 10, 5);
}
}

执行以上代码,输出结果为:

Code
$ scalac Test.scala 
$ scala Test
x 的坐标点 : 20
y 的坐标点 : 30
z 的坐标点 : 20

Scala重写一个非抽象方法,必须用override修饰符。

scala
class Person {
var name = ""
override def toString = getClass.getName + "[name=" + name + "]"
}

class Employee extends Person {
var salary = 0.0
override def toString = super.toString + "[salary=" + salary + "]"
}

object Test extends App {
val fred = new Employee
fred.name = "Fred"
fred.salary = 50000
println(fred)
}

执行以上代码,输出结果为:

Code
$ scalac Test.scala 
$ scala Test
Employee[name=Fred][salary=50000.0]

十二、包

包对象,编译完之后生成以xxx为package,下面含有类package.class + package.class

scala
package object xxxx{
}
a.a1.aa1.xxxx

限定符

scala
private[package|this]

导包

Code
import java.io.Exception
import java.io.{A,B,C}
import java.io.{A => A0} //别名
import a._ 相当于Java的.*

十三、文件IO

13.1 写文件

Scala 进行文件写操作,直接用的都是 java中 的 I/O 类 (java.io.File):

scala
import java.io._

object Test {
def main(args: Array[String]) {
val writer = new PrintWriter(new File("test.txt" ))

writer.write("菜鸟教程")
writer.close()
}
}

执行以上代码,会在你的当前目录下生产一个 test.txt 文件,文件内容为”菜鸟教程”:

Code
$ scalac Test.scala 
$ scala Test
$ cat test.txt
菜鸟教程

13.2 读文件

从文件读取内容非常简单。我们可以使用 Scala 的 Source 类及伴生对象来读取文件。以下实例演示了从 “test.txt”(之前已创建过) 文件中读取内容:

scala
import scala.io.Source

object Test {
def main(args: Array[String]) {
println("文件内容为:" )

Source.fromFile("test.txt" ).foreach{
print
}
}
}

执行以上代码,输出结果为:

Code
$ scalac Test.scala 
$ scala Test
文件内容为:
菜鸟教程

13.3 从控制台读取

scala
import scala.io.StdIn

object IO {
def main(args: Array[String]): Unit = {
val line = StdIn.readLine("请输入: ")
println("你输入的是: " + line)
}
}

十四、正则*

14.1 查找

14.2 替换

14.3 正则表达式

十五、Trait 特征

Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。

与接口不同的是,它还可以定义属性和方法的实现。

一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。

如果只有一个trait使用extends进行扩展,如果多个,使用with对剩余的trait进行扩展。

Trait(特征) 定义的方式与类类似,但它使用的关键字是 trait,如下所示:

scala
trait Equal {
def isEqual(x: Any): Boolean
def isNotEqual(x: Any): Boolean = !isEqual(x)
}

以上Trait(特征)由两个方法组成:isEqualisNotEqual。isEqual 方法没有定义方法的实现,isNotEqual定义了方法的实现。子类继承特征可以实现未被实现的方法。所以其实 Scala Trait(特征)更像 Java 的抽象类。

以下演示了特征的完整实例:

scala
/* 文件名:Test.scala
* author:菜鸟教程
* url:www.runoob.com
*/
trait Equal {
def isEqual(x: Any): Boolean
def isNotEqual(x: Any): Boolean = !isEqual(x)
}

class Point(xc: Int, yc: Int) extends Equal {
var x: Int = xc
var y: Int = yc
def isEqual(obj: Any) =
obj.isInstanceOf[Point] &&
obj.asInstanceOf[Point].x == x
}

object Test {
def main(args: Array[String]) {
val p1 = new Point(2, 3)
val p2 = new Point(2, 4)
val p3 = new Point(3, 3)

println(p1.isNotEqual(p2))
println(p1.isNotEqual(p3))
println(p1.isNotEqual(2))
}
}

执行以上代码,输出结果为:

Code
$ scalac Test.scala 
$ scala Test
false
true
true

特征构造顺序

特征也可以有构造器,由字段的初始化和其他特征体中的语句构成。这些语句在任何混入该特征的对象在构造时都会被执行。

构造器的执行顺序:

  • 调用超类的构造器;
  • 特征构造器在超类构造器之后、类构造器之前执行;
  • 特征由左到右被构造;
  • 每个特征当中,父特征先被构造;
  • 如果多个特征共有一个父特征,父特征不会被重复构造
  • 所有特征被构造完毕,子类被构造。

构造器的顺序是类的线性化的反向。线性化是描述某个类型的所有超类型的一种技术规格。

十六、高级函数

16.1 函数变量

变量类型是函数类型。就有点C++的函数指针的意思。

Code
// 定义一个函数
scala> def add(a:Int, b:Int) = a + b
add: (a: Int, b: Int)Int

// 将该函数赋值给一个变量(_ 表示取出函数本身)
scala> val f = add _
f: (Int, Int) => Int = $$Lambda$1165/1200355641@3e6d80f5

// 通过变量调用
scala> f(1, 2)
res123: Int = 3

16.2 匿名函数

没有函数名的函数。一般配合一些参数是函数的函数。

Code
scala> val f = (n: Double) => 3 * n
f: Double => Double = $$Lambda$1166/920699871@73c43130

scala> f(5)
res124: Double = 15.0

有点C++中的lambda表达式的意思。

Code
scala> Array(1,2,3,4).map(x => x * 2)
res128: Array[Int] = Array(2, 4, 6, 8)

// 还可以用更简洁的方式
scala> Array(1,2,3,4).map(_ * 2)
res127: Array[Int] = Array(2, 4, 6, 8)

16.3 参数是函数的函数

f: (参数类型1[, 参数类型N])=>(结果类型)

Code
scala> def fun(a: Int, b: Int, f: (Int, Int) => Int) = f(a, b)
fun: (a: Int, b: Int, f: (Int, Int) => Int)Int

scala> def add(a:Int, b:Int) = a + b
add: (a: Int, b: Int)Int

scala> fun(1, 2, add)
res2: Int = 3

16.4 返回值是函数的函数

可以在返回值后加一个()调用

Code
scala> def fun(a: Int, b: Int) = (x: Int) => (a + b) * x
fun: (a: Int, b: Int)Int => Int

scala> fun(1, 2)(3)
res3: Int = 9

其中fun(1, 2)返回了一个新的函数,后面的(3)是新的函数的参数。

十七、控制抽象(分线程)

return挺有用的有时候,可以中途返回。

scala
object Thread {
def myThread(fun: ()=> Unit) = {
new Thread(){
fun()
}.start()
}

def main(args: Array[String]): Unit = {
myThread(()=> (1 to 5).foreach(println))
}
}

输出结果:

Code
1
2
3
4
5

这里提供一种更简洁的写法:

scala
object Thread {
def myThread(fun: => Unit) = { // 将参数中的()去掉
new Thread(){
fun // 这里不能带()
}.start()
}

def main(args: Array[String]): Unit = {
myThread{(1 to 5).foreach(println)} // 调用的时候可以用{}也可以用()
}
}

十八、模式匹配

18.1 模式匹配

值匹配

Scala 提供了强大的模式匹配机制,应用也非常广泛。

一个模式匹配包含了一系列备选项,每个都开始于关键字 case。每个备选项都包含了一个模式及一到多个表达式。箭头符号 => 隔开了模式和表达式。

scala
val ch = '9'
ch match {
case '+' => println("加号")
case '-' => println("减号")
case _ => println("数字")
}

输出结果:

Code
数字

有点类似switch语句,但是不同的是,执行完case后的语句会直接结束,而不用使用break

类型匹配

scala
val x:Any = "123";
x match{
case b:Int => print("is Int") ;
case a:String => print("is String") ;
case _ => print("is Int") ;
}

输出结果:

Code
is String

18.2 样例类

使用了case关键字的类定义就是就是样例类(case classes),样例类是种特殊的类,经过优化以用于模式匹配

在声明样例类时,下面的过程自动发生了:

  • 构造器的每个参数都成为val,除非显式被声明为var,但是并不推荐这么做;
  • 在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象;
  • 提供unapply方法使模式匹配可以工作;
  • 生成toString、equals、hashCode和copy方法,除非显示给出这些方法的定义。
scala
object Test {
def main(args: Array[String]) {
val alice = new Person("Alice", 25)
val bob = new Person("Bob", 32)
val charlie = new Person("Charlie", 32)

for (person <- List(alice, bob, charlie)) {
person match {
case Person("Alice", 25) => println("Hi Alice!")
case Person("Bob", 32) => println("Hi Bob!")
case Person(name, age) =>
println("Age: " + age + " year, name: " + name + "?")
}
}
}
// 样例类
case class Person(name: String, age: Int)
}

执行以上代码,输出结果为:

Code
$ scalac Test.scala 
$ scala Test
Hi Alice!
Hi Bob!
Age: 32 year, name: Charlie?

sealed 密封样例类

子类和父类必须定义在同一文件中。

scala
sealed abstract class Dog{}
case class Jing8(name:String) extends Dog{}
case class Shapi(age:Int) extends Dog{}

18.3 PartialFunction 偏函数

scala
val f:PartialFunction[Char,Int] = {
case '+' => 1 ;
case '-' => -1
case _ => 0
}

val x = 'a'
f(x) // 输出0

十九、泛型

19.1 类泛型

scala
class Pair[T,S](one:T,second:S);		//定义泛型类
val p = new Pair[String,Int]("tom",12); //

val p2 = new Pair("tom",12); //类型推断

19.2 方法泛型

scala
def getMiddle[T](arr:Array[T]) = arr(arr.length / 2);

19.3 泛型的界限

scala
def run[T <: Dog](d:T) = println("hello")
def run2[T >: Shapi](d:T) = println("hello")

<: //上界,必须是Dog,及其子类
>: //下界,必须是Shapi,及其父类(有问题?)
<% // A <% B,A能够隐式转换成B

T <:Dog >:Cat //约束多个条件。
文章作者: IT小王
文章链接: https://wangbowen.cn/2020/04/21/Scala%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 IT小王

评论