ScalaLearning从入门到神坑

Scala教程
Author: yanpenggong       Email: yanpenggong@163.com
Github: kungs8      CSDN: https://blog.csdn.net/yanpenggong

一、 简介

Scala是一门现代的多范式编程语言,平滑地集成了面向对象和函数式语言的特性,旨在以简练、优雅的方式来表达常用编程模式。Scala的设计吸收借鉴了许多种编程语言的思想,只有很少量特点是Scala自己独有的。Scala语言从写个小脚本到建立个大系统的编程任务均可胜任。Scala运行于Java平台(JVM,Java 虚拟机)上,并兼容现有的Java程序,Scala代码可以调用Java方法,访问Java字段,继承Java类和实现Java接口。在面向对象方面,Scala是一门非常纯粹的面向对象编程语言,也就是说,在Scala中,每个值都是对象,每个操作都是方法调用。

Spark的设计目的之一就是使程序编写更快更容易,这也是Spark选择Scala的原因所在。总体而言,Scala具有以下突出的优点:

  • Scala具备强大的并发性,支持函数式编程,可以更好地支持分布式系统;
  • Scala语法简洁,能提供优雅的API;
  • Scala兼容Java,运行速度快,且能融合到Hadoop生态圈中。

Scala是Spark的主要编程语言,但Spark还支持Java、Python、R作为编程语言,因此,若仅仅是编写Spark程序,并非一定要用Scala。Scala的优势是提供了REPL(Read-Eval-Print Loop,交互式解释器),因此,在Spark Shell中可进行交互式编程(即表达式计算完成就会输出结果,而不必等到整个程序运行完毕,因此可即时查看中间结果,并对程序进行修改),这样可以在很大程度上提升开发效率。现在的计算机都是多核CPU,想充分利用其多核处理,需要写可并行计算的代码。而函数式编程在并行操作性有着天生的优势,函数式编程没有可变变量,就不会有内存共享的问题。

二、 Scala安装

1. 在Windows操作系统中安装Scala

Scala程序需要运行在JVM(Java虚拟机)上,因此,在安装Scala之前,需要在Windows系统中安装Java,然后,再安装Scala。具体步骤如下:

  1. 安装Java

    Scala程序需要运行在JVM(Java虚拟机)上,因此,在安装Scala之前,需要在Windows系统中安装Java环境。可以到Java官网(官网)下载JDK,比如,jdk-8u111-windows-x64.exe文件,然后,运行该程序就可以完成JDK的安装。

  2. 安装Scala

    访问Scala官网(官网),下载Scala。登录后,官网会自动识别你的操作系统类型,如果是Windows操作系统,官网会自动提供.msi格式的安装包,比如,scala-2.11.8.msi,在Windows操作系统中,可以运行这个安装包,安装Scala。

2. 在Linux操作系统中安装Scala

Scala程序需要运行在JVM(Java虚拟机)上,因此,在安装Scala之前,需要在Linux系统中安装Java,然后,再安装Scala。
这里假设你已经安装好了Linux系统。请启动并进入Linux操作系统,并打开命令行终端(可以使用快捷键组合:ctrl+alt+t,打开终端窗口),进入shell环境。下面开始安装Java和Scala。具体步骤如下:

  1. 安装Java

    请确保你的Linux系统中已经安装了Java JDK1.5或更高版本,并设置了JAVA_HOME环境变量,而且已经把JDK的bin目录添加到PATH变量。

    Java环境可选择 Oracle 的 JDK,或是 OpenJDK。为图方便,这边直接通过命令安装 OpenJDK 7。

    sudo apt-get install openjdk-7-jre openjdk-7-jdk
    
  2. 安装Scala

三、Scala 基础部分

3.1 声明值和变量

Scala有两种类型的变量,

  • **val:**不可变的,在声明时就必须被初始化,而且初始化以后就不能再赋值
  • **var:**可变的,声明的时候需要进行初始化,初始化以后还可以再次对其赋值。

3.1.1 val变量

scala> val myStr = "Hello World!"
val myStr: String = Hello World!

scala> 

我们可以看到,myStr变量的类型是String类型,变量的值是Hello World! 这里需要注意的是,尽管我们在第1行代码的声明中,没有给出myStr是String类型,但是,Scala具有“类型推断”能力,可以自动推断出变量的类型。

当然,我们也可以显式声明变量的类型:

scala> val myStr2:String = "Hello World!"
val myStr2: String = Hello World!

scala> 

需要说明的是,上面的String类型全称是java.lang.String,也就是说,Scala的字符串是由Java的String类来实现的,因此,我们也可以使用java.lang.String来声明,具体如下:

scala> val myStr3:java.lang.String = "Hello World!"
val myStr3: String = Hello World!

scala> 

为什么可以不用java.lang.String,而只需要使用String就可以声明变量呢?这是因为,在每个应用程序中,Scala都会自动添加一些引用,这样,就相当于在每个程序源文件的顶端都增加了一行下面的代码:

import java.lang_

上面已经声明了一个String类型的不可变的变量,下面我们可以使用该变量,比如要打印出来:

scala> println(myStr)
Hello World!

scala> 

因为myStr是val变量,因此,一旦初始化以后,就不能再次赋值,所以,下面我们执行的再次赋值操作会报错:

scala> myStr = "Hello World!"
             ^
       error: reassignment to val

scala> 

3.1.2 var 变量

如果一些变量,需要在初始化以后还要不断修改它的值(比如商品价格),则需要声明为var变量。
下面我们把myPrice声明为var变量,并且在声明的时候需要进行初始化:

scala> var myPrice:Double = 9.9
var myPrice: Double = 9.9

scala> 

我们再次对myPrice进行赋值,并打印输出:

scala> myPrice = 18.8
// mutated myPrice

scala> println(myPrice)
18.8

scala> 

小技巧:如何在Scala解释器中输入多行代码

在Scala解释器中,当在命令提示符后面输入一个表达式并且按回车以后,代码就会被执行并显示出结果,比如下面我们输入一行表达式并回车:

scala> 3+8*2-7
val res2: Int = 12

scala> 

这是输入单行代码的情形,但是,有时候,我们需要在命令提示符后面输入多行代码。

在Java中,每个语句都是以英文的分号结束,但是,在Scala中,可以不用分号。当然,如果你想把多条语句全部写在一行上面,这时还是需要使用分号来隔开各个语句的。
通常而言,只要Scala解释器推断出你的代码还没有结束,应该延续到下一行,解释器就会在下一行显示一个竖线“|”,你可以继续输入剩余的代码,比如,我们要输入表达式val myStr4 = “Hello World!”,我们只在命令提示符后面输入“val myStr4 = ”然后就回车,显然,这个表达式还没有结束,所以,解释器会在下一行显示一个竖线“|”,你可以在第2行继续输入”Hello World!”然后回车,解释器就会得到执行结果,具体如下:

scala> val myStr4=
     | "Hello World!"
val myStr4: String = Hello World!

scala> 

如果我们在命令提示符后面输入“val myStr5 = ”然后就回车,解释器会在下一行显示一个竖线“|”,这时如果我们发现变量名称错误,想放弃本次输入,就可以在“|”后面连续敲入两个回车,结束本次输入,具体如下:

scala> val myStr5 =
       |
       |
You typed two blank lines. Starting a new command.
scala>

3.2 级别数据类型和操作

3.2.1 级别数据类型

Scala的数据类型包括:Byte、Char、Short、Int、Long、Float、Double和Boolean。和Java不同的是,在Scala中,这些类型都是“类”,并且都是包scala的成员,比如,Int的全名是scala.Int。对于字符串,Scala用java.lang.String类来表示字符串。

这里要明确什么是“字面量”?字面量包括整数字面量、浮点数字面量、布尔型字面量、字符字面量、字符串字面量、符号字面量、函数字面量和元组字面量。举例如下:

val i = 123  //123就是整数字面量
val i = 3.14 //3.14就是浮点数字面量
val i = true //true就是布尔型字面量
val i = 'A' //'A'就是字符字面量
val i = "Hello" //"Hello"就是字符串字面量

Scala允许对“字面量”直接执行方法,比如:

scala> 5.toString()
val res3: String = 5

scala> "abc".intersect("bcd")
val res4: String = bc

scala> 

上面的intersect()方法用来输出两个字符串中都存在的字符。

3.2.2 操作符

在Scala中,可以使用加(+)、减(-) 、乘(*) 、除(/) 、余数(%)等操作符,而且,这些操作符就是方法。例如,5 + 3和(5).+(3)是等价的,也就是说:

a 方法 b
a.方法(b)

上面这二者是等价的。前者是后者的简写形式,这里的+是方法名,是Int类中的一个方法。具体代码如下:

scala> val sum1 = 5 + 2  //实际上调用了 (5).+(2)
val sum1: Int = 7

scala> val sum2 = (5). + (2)  //可以发现,写成方法调用的形式,和上面得到相同的结果
val sum2: Int = 7

scala> 

需要注意的是,和Java不同,在Scala中并没有提供++和–操作符,当需要递增和递减时,类似Python形式,可以采用如下方式表达:

scala> var i = 5
var i: Int = 5

scala> i += 1

scala> println(i)
6

scala>

此外,也可以使用关系和逻辑操作,比如,大于(>)、小于(<)、大于等于(>=)和小于等于(<=),会产生Boolean类型的结果。

3.3 Range

在执行for循环时,我们经常会用到数值序列,比如,i的值从1循环到5,这时就可以采用Range来实现。Range可以支持创建不同数据类型的数值序列,包括Int、Long、Float、Double、Char、BigInt和BigDecimal等。

在创建Range时,需要给出区间的起点和终点以及步长(默认步长为1)。下面通过几个实例来介绍:

  1. 创建一个从1到5的数值序列,包含区间终点5,步长为1

    scala> 1 to 5
    res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)
    scala> 1.to(5)
    res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)
    
  2. 创建一个从1到5的数值序列,不包含区间终点5,步长为1

    scala> 1 until 5
    res1: scala.collection.immutable.Range = Range(1, 2, 3, 4)
    
  3. 创建一个从1到10的数值序列,包含区间终点10,步长为2

    scala> 1 to 10 by 2
    res2: scala.collection.immutable.Range = Range(1, 3, 5, 7, 9)
    
  4. 创建一个Float类型的数值序列,从0.5f到5.9f,步长为0.8f

    scala> 0.5f to 5.9f by 0.8f
    res3: scala.collection.immutable.NumericRange[Float] = NumericRange(0.5, 1.3, 2.1, 2.8999999, 3.6999998, 4.5, 5.3)
    

3.4 打印语句

在Scala编程中,经常需要用到打印语句。

scala> print("My name is:"); print("yanpenggong")
My name is:yanpenggong
scala> 

上述代码执行后,会得到连在一起的一行结果。

如果要每次打印后追加一个换行符,实现换行的效果,就要采用println语句:

scala> println("My name is:"); println("yanpenggong")
My name is:
yanpenggong
scala> 

如果要打印整型变量的值,可以使用下面语句:

scala> val i = 7
val i: Int = 7
scala> println(i)
7
scala> 

此外,Scala还带有C语言风格的格式化字符串的printf函数:

scala> val i = 5;
val i: Int = 5
scala> val j = 8;
val j: Int = 8
scala> printf("My name is %s. I have %d apples and %d eggs.\n", "yanpenggong", i, j)
My name is yanpenggong. I have 5 apples and 8 eggs.
scala> 

3.5 读写文件

3.5.1 写入文本文件

Scala需要使用java.io.PrintWriter实现把数据写入到文本文件。

这里使用 Vscode 进行编写,运行程序时,使用scala 3.5_读写文件.scala

import java.io.PrintWriter
import java.io.File

object ReadAndWriteFile{
   
    def main(args: Array[String]): Unit = {
   
        // 写入文本文件
        println("===Write constent to file===")
        val out = new PrintWriter("./output/3.5_output.txt")
	      // val out = new PrintWriter(new File("./output/3.5_output.txt")) // 方法2
        for (i <- 1 to 6) out.println(i)
        out.close()
    }
}

上面代码中,new PrintWriter(“output.txt”)中只给出相对文件名,并没有给出完全文件路径,采用相对路径,这时,文件就会被保存到启动Scala REPL时的当前目录下。

需要注意的是,必须要执行out.close()语句,才会看到output.txt文件被生成,如果没有执行out.close()语句,我们就无法看到生成的output.txt文件。

3.5.2 读取文本文件中的行

可以使用Scala.io.Source的getLines方法实现对文件中所有行的读取。

import scala.io.Source

object ReadAndWriteFile{
   
    def main(args: Array[String]): Unit = {
   
        // 读取文本中的行
        println("===Read file lines===")
        val inputFile = Source.fromFile("./output/3.5_output.txt")
        val lines = inputFile.getLines
        for (line <- lines) println(line)
    }
}

四、控制结构

4.1 if条件表达式

if语句是许多编程语言中都会用到的控制结构。在Scala中,执行if语句时,会首先检查if条件是否为真,如果为真,就执行对应的语句块,如果为假,就执行下一个条件分支。

object IfConditionalExpression{
   
    def main(args: Array[String]): Unit = {
   
        val x = 6
        if (x > 0) {
   
            println("This is a psitive number!")
        } else if (x == 0) {
   
            println("This is a zero")
        } else {
   
            println("This is not a positive number!")
        }
        
        // Scala中的if表达式的值可以赋值给变量
        val a = if (x >0) {
   1} else {
   -1}
        println(f"a: $a")
    }
}

输出:

(base) kungs@kungsMacPro 第4章_控制结构 % scala 4.1_if条件表达式.scala
This is a psitive number!
a: 1
(base) kungs@kungsMacPro 第4章_控制结构 % 

4.2 while循环

object WhileCycle{
   
    def main(args: Array[String]): Unit = {
   
        // 方法1 while语句
        println("方法1 while语句: ")
        var i = 9
        while (i > 0) {
   
            i -= 1
            printf(f"i is $i\n")
        }

        // 方法2 do-while语句
        println("方法2 do-while语句: ")
        var j = 0
        do {
   
            j += 1
            printf("j is %d\n", j)
        } while (j < 5)
    }
}

输出:

(base) kungs@kungsMacPro 第4章_控制结构 % scala 4.2_while循环.scala
方法1 while语句: 
i is 8
i is 7
i is 6
i is 5
i is 4
i is 3
i is 2
i is 1
i is 0
方法2 do-while语句: 
j is 1
j is 2
j is 3
j is 4
j is 5
(base) kungs@kungsMacPro 第4章_控制结构 % 

4.3 for循环

4.3.1 实例

Scala中的for循环语句格式如下:

for (变量<-表达式) 语句块

其中,“变量<-表达式”被称为“生成器(generator)”。

object ForCycle {
   
    def main(args: Array[String]): Unit = {
   
        // i 不需要提前变量声明,可以在for语句括号中的表达式直接使用,
        // `<-`表示之前的要遍历后面1到5的所有值。
        for (i <- 1 to 3) {
   println(f"遍历 i: $i")}

        // 设置步长=2
        for (i <- 1 to 5 by 2) {
   println(f"步长2对应的i: $i")}

        // 有时候不想打印出所有结果,可以通过满足制定条件进行输出,即"守卫(guard)"
        // 这里只输出1到5之间到所有偶数
        for (i <- 1 to 5 if i%2==0) {
   println(f"guard i: $i")}

        // 支持多个生成器的情形,用`;`隔开
        for (i <- 1 to 2; j <- 1 to 3) {
   println(f"$i * $j = ${
     i*j}")}
        
        // 给每个生成器添加一个`守卫(guard)`
        for (i <- 1 to 5 if i%2==0; j <- 1 to 3 if j != i) {
   println(f"guard($i * $j = ${
     i*j})")}
    }
}

输出:

(base) kungs@kungsMacPro 第4章_控制结构 % scala 4.3_for循环.scala
遍历 i: 1
遍历 i: 2
遍历 i: 3
步长2对应的i: 1
步长2对应的i: 3
步长2对应的i: 5
guard i: 2
guard i: 4
1 * 1 = 1
1 * 2 = 2
1 * 3 = 3
2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
guard(2 * 1 = 2)
guard(2 * 3 = 6)
guard(4 * 1 = 4)
guard(4 * 2 = 8)
guard(4 * 3 = 12)
(base) kungs@kungsMacPro 第4章_控制结构 % 

4.3.2 for推导式

有时候,我们需要对上述过滤后的结果进行进一步的处理,这时,就可以采用yield关键字,对过滤后的结果构建一个集合。

object ForCycle {
   
    def main(args: Array[String]): Unit = {
   
        // for推导式, 得到的会是一个vector
        val vectors = for (i <- 1 to 5 if i%2==0) yield i
        println(f"vectors: $vectors")

    }
}

输出:

(base) kungs@kungsMacPro 第4章_控制结构 % scala 4.3_for循环.scala
vectors: Vector(2, 4)
(base) kungs@kungsMacPro 第4章_控制结构 % 

五、 数据结构

5.1 数组

数组是编程中经常用到的数据结构,一般包括定长数组和变长数组。旨在快速掌握最基础和常用的知识,因此,只介绍定长数组。

定长数组,就是长度不变的数组,在Scala中使用Array进行声明,如下:

object DataStructureArray {
   
    def main(args: Array[String]): Unit = {
   
        val IntValueArr = new Array[Int](3)  // 声明一个长度为3的整数型数组,每个数组元素初始化为0
        IntValueArr(0) = 22  // 给第1个数组元素赋值为22
        IntValueArr(1) = 66  // 给第2个数组元素赋值为66
        IntValueArr(2) = 45  // 给第3个数组元素赋值为45
        IntValueArr.foreach(println)  // 遍历打印数组
    }
}

输出:

(base) kungs@kungsMacPro 第5章_数据结构 % scala 5.1_数组.scala
22
66
45
(base) kungs@kungsMacPro 第5章_数据结构 % 

需要注意的是,在Scala中,对数组元素的应用,是使用圆括号,而不是方括号,也就是使用IntValueArr(0),而不是IntValueArr[0]。
下面声明一个字符串数组:



object DataStructureArray {
   
    def main(args: Array[String]): Unit = {
   
        val StrValueArr = new Array[String](3)  // 声明一个长度为3的字符串数组,每个数组元素初始化为null
        StrValueArr(0) = "Scala"
        StrValueArr(1) = "Spsark"
        StrValueArr(2) = "Hadoop"
        StrValueArr.foreach(println)
        for (i <- 0 to 2) {
   println(f"StrValueArr(${
     i}): ${
     StrValueArr(i)}")}
    }
}

输出:

(base) kungs@kungsMacPro 第5章_数据结构 % scala 5.1_数组.scala
Scala
Spsark
Hadoop
StrValueArr(0): Scala
StrValueArr(1): Spsark
StrValueArr(2): Hadoop
(base) kungs@kungsMacPro 第5章_数据结构 % 

实际上,Scala提供了更加简洁的数组声明和初始化方法,如下:

val IntValueArr_2 = Array(22, 66, 45)
val StrValueArr_2 = Array("Scala", "Spark", "Hadoop")

从上面代码可以看出,都不需要给出数组类型,Scala会自动根据提供的初始化数据来推断出数组的类型。

5.2 列表(List)

列表有头部和尾部的概念,可以使用intList.head来获取上面定义的列表的头部,值是1,使用intList.tail来获取上面定义的列表的尾部,值是List(2,3),可以看出,头部是一个元素,而尾部则仍然是一个列表。

object DataStructureList {
   
    def main(args: Array[String]): Unit = {
   
        val intlist = List(1, 2, 3)
      	println(f"intList: ${
     intList}")
        println(f"head: ${
     intlist.head}")
        println(f"tail: ${
     intlist.tail}")
    }
}

输出:

(base) kungs@kungsMacPro 第5章_数据结构 % scala 5.2_列表\(List\).scala
intList: List(1, 2, 3)
head: 1
tail: List(2, 3)
(base) kungs@kungsMacPro 第5章_数据结构 % 

由于列表的头部是一个元素,可以使用::操作,在列表的头部增加新的元素,得到一个新的列表。

object DataStructureList {
   
    def main(args: Array[String]): Unit = {
   
        val intList = List(1, 2, 3)
        val intListOther = 0::intList
        println(f"intListOther: $intListOther")
    }
}

输出:

(base) kungs@kungsMacPro 第5章_数据结构 % scala 5.2_列表\(List\).scala 
intListOther: List(0, 1, 2, 3)
(base) kungs@kungsMacPro 第5章_数据结构 % 

注意,上面操作执行后,intList不会发生变化,依然是List(1,2,3),intListOther是一个新的列表List(0,1,2,3)

::操作符是右结合的,因此,如果要构建一个列表List(1,2,3),实际上也可以采用下面的方式:

object DataStructureList {
   
    def main(args: Array[String]): Unit = {
   
        val new_intList = 1::2::3::Nil
        println(f"new_intList: $new_intList")
    }
}

输出:

(base) kungs@kungsMacPro 第5章_数据结构 % scala 5.2_列表\(List\).scala 
new_intList: List(1, 2, 3)
(base) kungs@kungsMacPro 第5章_数据结构 % 

上面代码中,Nil表示空列表。也可以使用:::操作符对不同的列表进行连接得到新的列表:

object DataStructureList {
   
    def main(args: Array[String]): Unit = {
   
        val intList1 = List(1, 2)
        val intList2 = List(3, 4)
        val intList3 = intList1:::intList2
        println(f"intList3: $intList3")
    }
}

输出:

(base) kungs@kungsMacPro 第5章_数据结构 % scala 5.2_列表\(List\).scala 
intList3: List(1, 2, 3, 4)
(base) kungs@kungsMacPro 第5章_数据结构 % 

注意,执行上面操作后,intList1和intList2依然存在,intList3是一个全新的列表。

实际上,Scala还为列表提供了一些常用的方法,比如,如果要实现求和,可以直接调用sum方法,如下:

object DataStructureList {
   
    def main(args: Array[String]): Unit = {
   
        val intList = List(1, 2, 3)
        // 求和
        println(f"sum: ${
     intList.sum}")
    }
}

输出:

(base) kungs@kungsMacPro 第5章_数据结构 % scala 5.2_列表\(List\).scala 
sum: 6
(base) kungs@kungsMacPro 第5章_数据结构 % 

5.3 元组(Tuple)

元组是不同类型的值的聚集。元组和列表不同,列表中各个元素必须是相同类型,而元组可以包含不同类型的元素。

object DataStructureTuple {
   
    def main(args: Array[String]): Unit = {
   
        val tuple = ("Scala", 2022, 7.18)
        // 遍历元组中的元素,并打印
        tuple.productIterator.foreach(i => println(f"tuple value_i:${
     i}"))
        println(f"tuple_1:${
     tuple._1}")
        println(f"tuple_2:${
     tuple
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kungs8

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值