Scala 教程
Scala 教程总结
本 Scala 教程涵盖了 Scala 的所有方面和主题。您将从头开始学习所有基础知识,例如什么是 Scala、Scala 的安装过程、Scala 程序、Scala 函数、惰性求值、类型推断、类和对象、继承、抽象、Java 和 Scala 的区别等。
什么是 Scala?
Scala 是一种静态类型编程语言,它结合了函数式编程和面向对象编程,以提高应用程序的可扩展性。Scala 主要运行在 JVM 平台上,它也可以用于通过 Scala-Native 和 JavaScript 运行时通过 ScalaJs 编写原生平台软件。
Scala 是一种可扩展的语言,用于为多个平台编写软件。因此,它得名“Scala”。这种语言旨在解决 Java 的问题,同时更加简洁。它最初由 Martin Odersky 设计,并于 2003 年发布。
为什么要学习 Scala
以下是学习 Scala 编程语言的主要原因
- Scala 对于面向对象程序员、Java 开发人员来说很容易学习。近年来,它正成为一种流行的语言。
- Scala 为用户提供一流的函数
- Scala 可以在 JVM 上执行,从而为与其他语言的互操作性铺平道路。
- 它专为并发、分布式和弹性消息驱动的应用程序而设计。它是这十年中最受欢迎的语言之一。
- 它简洁、强大,并且可以根据用户的需求快速发展。
- 它面向对象,并具有许多函数式编程特性,为开发人员提供了很大的灵活性,可以按照他们想要的方式编写代码。
- Scala 提供了许多 Duck Types
- 如果您来自 Java,它的样板代码更少
- 用 Scala 编写的 Lift 和 Play 框架正处于增长曲线中。
如何安装 Scala
要开始编写 Scala 程序,您需要将其安装到您的计算机上。为此,您需要访问其网站 https://scala-lang.org.cn/download/ 以下载最新版本的 Scala。
点击链接后,我们将看到两个选项,我们可以选择将 Scala 安装到我们的机器上。对于本 Scala 教程,我们将下载 IntelliJ IDEA。
一旦您访问下载链接,您会发现两个版本的 IntelliJ IDE。
对于本 Scala 教程,我们将下载社区版,它是免费的,并附带编写 Scala 程序所需的一切。
步骤 1) 选择社区版
在页面上,点击社区版上的下拉菜单。
它为我们提供了一个选项,可以下载 IntelliJ IDE 以及包含 Scala 编译和运行代码所需的 JDK 实现 (Java Development Kit) OpenJDK 的 JBR。
步骤 2) 运行安装
下载 IntelliJ 后,双击它以运行安装向导并按照对话框进行操作。
步骤 3) 选择一个位置
选择一个位置来安装 IDE。
如果碰巧您没有下载带有 JDK 的版本,我们仍然会收到一个提示,我们可以通过选中复选框来下载它。
步骤 4) 点击下一步
保留其他默认设置,然后点击下一步。
步骤 5) 点击启动图标
安装完成后,像普通应用程序一样,点击启动菜单中的启动图标运行 IntelliJ IDE。
您仍然需要额外一步,将 Scala 插件添加到 IntelliJ;您可以通过点击屏幕右下角的配置菜单上的下拉菜单,然后选择插件选项来完成此操作。
在 Marketplace 选项卡上,搜索 Scala 会将该插件作为 Languages 标签下的第一个结果显示。
步骤 6) 安装插件
点击安装,插件将开始下载。
步骤 7) 重启 IDE
下载完成后,系统会提示您重新启动 IDE,以便安装的插件可以开始工作。
重启后,您会发现自己回到了我们运行 IDE 之前的同一页面,但这次我们已经安装了 Scala 插件。
Scala Hello World 程序
步骤 1) 选择“创建项目”选项,这将引导我们到一个页面,我们可以在其中选择项目将使用的语言。
步骤 2) 勾选 Scala 复选框,选择 Scala,然后单击下一步。
步骤 3) 选择保存项目文件的位置并给项目命名。
如果目录不存在,IntelliJ 将提示我们请求创建文件夹的权限。接受并点击完成。您将被带到您的 Scala 项目,该项目目前没有任何 Scala 代码。
加载一些索引需要一些时间,所以如果您的 IDE 底部有进度条时无法立即进行任何操作,请不要担心,这只是意味着您的 IDE 正在加载运行 Scala 和帮助 IDE 自动完成所需的一些文件。
步骤 4) 接下来,我们将点击 IDE 左侧的项目选项卡并展开,以便我们可以看到项目的内容。
目前项目是空的,只包含一个 .idea 文件夹和 IDE 生成的 hello-world.iml 文件。我们感兴趣的是 src 文件夹。Src 是我们存储项目源代码的地方。我们将在那里创建第一个 Scala 文件。
步骤 5) 右键单击 src 以打开一个菜单来创建新的 Scala 文件。
然后我们将为文件创建一个名称,在这个 Scala 教程中我们将使用 hello,然后从下拉菜单中选择要作为 Scala 文件内容的内容。选择“Object”
一旦我们这样做,我们将有一个 Scala 文件,其中包含一个单例对象,我们将使用它来运行我们的代码。
现在您有了一个包含 Hello 对象的 Scala 文件。您将通过使用 App 关键字扩展您创建的对象来编写您的第一个程序。
用 App 扩展我们的对象告诉编译器在程序启动时运行哪个代码。扩展 App 后,左侧立即出现一个绿色箭头,表示您现在可以运行程序了。
在 Hello 对象内部,我们编写了一个函数 println(),它用于将内部文本打印到控制台。我们将通过点击绿色箭头来运行代码。
点击箭头会显示“运行,hello”选项,点击它后,我们的代码将开始编译,几秒钟后,我们将在 IntelliJ IDE 内置的控制台中看到程序的结果。
至此,我们已经成功安装了 Scala 并运行了我们的第一个程序。
Scala 的用途
- 使用 ScalaJS 进行前端 Web 开发
- 移动开发,包括 Android 开发和 iOS – 使用 Scala Native
- 服务器端库,如 HTTP4S、Akka-Http、Play Framework
- 使用物联网
- 游戏开发
- NLP - 自然语言处理,使用 ScalaNLP 库套件
- 测试高级编程技术,例如函数式编程和面向对象编程
- 使用 Akka(一个受 Erlang 启发的 JVM 库)的 Actors 构建高度并发的通信应用程序
- 将其用于机器学习,使用 Figaro 等库进行概率编程和 Apache Spark
匿名函数
Scala 语言有匿名函数,也称为函数字面量。Scala 作为一种函数式语言,通常意味着开发人员将大问题分解成许多小任务,并创建许多函数来解决这些问题。为了方便创建函数,Scala 包含这些可以无需命名即可实例化的函数。我们可以直接将它们赋值给变量或定义 'def',如下面的 Scala 示例所示
val multiplyByTwo = (n:Int) => n * 2 def multiplyByThree = (n:Int) => n *3
然后我们可以像使用函数一样使用它们,通过传递参数如下。
multiplyByTwo(3) //6 multiplyByThree(4) //12
当我们需要编写清晰简洁的代码时,这些方法非常有用。我们可以在定义不大型且不需要大量代码的方法时使用匿名函数。它们非常简单,不需要繁琐的创建过程。
这些方法不仅限于带参数的函数,还可以用于实例化不带任何参数的方法。
val sayHello = ()=>{ println("hello") }
这些匿名函数大部分用于我们代码的其他部分,需要就地快速创建函数。
这些函数也被称为内联函数的另一个原因。使用匿名函数是一种常见的模式,在集合库中广泛使用,用于对集合执行快速操作。
例如,我们有一个 filter 方法,它接受一个内联函数/匿名函数来创建另一个集合,其中只包含符合我们在匿名函数中定义的条件的元素。
val myList = List(1,2,3,4,5,6,7) val myEvenList = myList.filter((n: Int) => n % 2 == 0) //List(2,4,6) val myOddList = myList.filter((n:Int) => n % 2 != 0) //List(1,3,5,7)
这里我们作为匿名函数的方法是检查我们从列表中获取的值是奇数还是偶数,并返回该项。
//the one checking that the value is even (n: Int) => n % 2 == 0 //the one checking that the value is odd (n:Int) => n % 2 != 0
在 Scala 中,也可以使用通配符,其中匿名函数的参数未命名。例如
var timesTwo = (_:Int)*2 timesTwo(5) //10
在这种情况下,我们不命名传入的参数。我们只使用下划线来表示它。
惰性求值
大多数语言按顺序评估变量和函数参数,一个接一个。在 Scala 中,我们有一个名为 lazy 的关键字,它有助于处理我们不希望在引用之前进行评估的值。
标记为惰性的变量在其定义处不会被评估,这通常称为急切评估,它只会在代码稍后被引用时才会被评估。
这在评估一个值可能是一个昂贵的计算时很有用,如果这个值并不总是需要,我们可以通过使我们的变量惰性来避免运行一个昂贵的计算,这可能会减慢我们的软件。
lazy val myExpensiveValue = expensiveComputation def runMethod()={ if(settings == true){ use(myExpensiveValue) }else{ use(otherValue) } }
这并不是惰性变量的唯一用例。它们还有助于处理代码中循环依赖的问题。
如果设置是假的,我们可能不需要使用 myExpensiveValue,这可以节省我们进行昂贵的计算,这有助于确保用户在使用我们的应用程序时有一个愉快的体验,因为他们的其他需求可以在不占用过多内存的情况下得到适当的计算。
如果设置是假的,我们可能不需要使用 myExpensiveValue,这可以节省我们进行昂贵的计算,这有助于确保用户在使用我们的应用程序时有一个愉快的体验,因为他们的其他需求可以在不占用过多内存的情况下得到适当的计算。
惰性还对函数参数有帮助,其中参数只在函数内部被引用时才使用。这个概念称为按名称调用参数。
def sometimesUsedString(someValue:String, defaultValue:=> String)={ if(someValue != null){ use(defaultValue) }else{ use(someValue) } }
许多语言使用按值调用方式评估参数。通过按名称调用传递的参数只在函数体中需要时才会被评估,在此之前不会被评估。一旦该值被评估,它就会被存储,以后可以重复使用而无需重新评估。这个概念被称为记忆化。
类型推断
在 Scala 中,您不必为您创建的每个变量声明类型。这是因为 Scala 编译器可以根据右侧的评估进行类型推断。这使得您的代码更加简洁——它使我们免于编写样板代码,因为预期类型是显而易见的
var first:String = "Hello, " var second:String = "World" var third = first + second //the compile infers that third is of type String
高阶函数
高阶函数是一个可以接受函数作为参数并可以返回函数作为返回类型的函数。在 Scala 中,函数被视为一等公民。以这种方式使用这些函数使我们能够非常灵活地创建各种程序。我们可以动态创建函数,并动态地将功能输入到其他函数中。
def doMathToInt(n:Int, myMathFunction:Int=>Int): Int ={ myMathFunction(n) }
在上面的函数中,我们传入一个 int 和一个接受 int 并返回 int 的函数。我们可以传入任何具有该签名的函数。签名是指函数的输入和输出。Int=>Int 的签名意味着一个函数接受一个 Int 作为输入并返回一个 Int 作为其输出。
()=>Int 的签名表示一个函数不接受任何输入,但返回一个 Int 作为其输出。例如,一个为我们生成随机整数的函数。
def generateRandomInt()={ return scala.util.Random.nextInt() }
上面的函数签名为 ()=>Int
我们可以有一个 Scala 函数,其签名为 ()=>Unit。这意味着函数不接受任何输入,也不返回任何类型。该函数可以执行某种计算,例如更改某些内容或执行预定操作。
然而,不鼓励使用这类方法,因为它们似乎是一个可能以未知方式影响系统的黑箱。它们也无法测试。拥有明确的输入和输出类型使我们能够推断函数的作用。
高阶函数也可以返回一个函数。
例如,我们可以创建一个将创建幂函数的函数,即,接受一个数字并对其应用幂。
def powerByFunction(n:Int):Int=>Int = { return (x:Int)=> scala.math.pow(x,n).toInt }
上面的函数接受一个整数。我们的返回类型是一个匿名函数,它接受一个整数 x,*我们使用整数 x 作为幂函数的参数。
柯里化
在 Scala 中,我们可以将一个接受两个参数的函数转换为一个一次接受一个参数的函数。当我们传入一个参数时,我们部分应用它,最终得到一个接受一个参数以完成该函数的函数。柯里化使我们能够通过部分添加一些参数来创建函数。
这对于在拥有完整参数集之前动态创建函数非常有用
def multiply two numbers(n:Int)(m:Int): Unit ={ return n * m }
如果我们需要创建一个乘以某个特定数字的函数,我们不需要创建另一个乘法方法。
我们可以简单地调用上面函数上的 .curried,得到一个首先接受一个参数并返回一个部分应用函数的函数
def multiplyTwoNumbers(n:Int)(m:Int): Unit ={ return n * m } var multiplyByFive = multiplyTwoNumbers(5) multiplyByFive(4) //returns 20
模式匹配
Scala 拥有强大的内置机制,可帮助我们检查变量是否符合特定条件,就像我们在 Java 中的 switch 语句或一系列 if/else 语句中所做的那样。该语言具有模式匹配功能,可用于检查变量是否为特定类型。Scala 中的模式匹配功能强大,可用于解构具有 unapply 方法的组件,以便直接从我们匹配的变量中获取我们感兴趣的字段。
Scala 的模式匹配还提供了比 switch 语句更令人愉悦的语法。
myItem match { case true => //do something case false => //do something else case _ => //if none of the above do this by default }
我们将变量与一组选项进行比较,当匹配的变量满足条件时,胖箭头 (=>) 右侧的表达式会被评估并作为匹配结果返回。
我们使用下划线来捕获代码中未匹配的情况。它反映了处理 switch 语句时默认情况的行为。
class Animal(var legs:Int,var sound:String) class Furniture(var legs:Int, var color:Int, var woodType:String) myItem match { case myItem:Animal => //do something case myItem:Furniture => //do something else case _ => //case we have a type we don't recognize do sth else }
在上面的代码中,您能够找出 myItem 变量的类型,并根据该类型分支到一些特定代码。
模式匹配检查变量是否匹配
下划线充当占位符,匹配在上面 case 语句中未匹配的任何其他条件。我们取一个变量 myItem 并调用 match 方法。
- 我们使用并检查 myItem 是否为 true,并在胖箭头“=>”的右侧执行一些逻辑。
- 我们使用下划线来匹配代码中定义的任何 case 语句不匹配的任何内容。
使用 Case 类,我们甚至可以进一步解构类以获取对象内部的字段。
通过使用密封(sealed)关键字来定义我们的类,我们可以获得编译器详尽检查我们尝试匹配的案例的优势,并在我们忘记处理特定案例时发出警告。
不变性
在 Scala 中,可以使用 val 关键字创建不能被其他函数更改的值。这在 Java 中通过使用 final 关键字实现。在 Scala 中,我们通过在创建变量时使用 val 关键字而不是 var 来实现,var 是我们用于创建可变变量的替代方法。
使用 val 关键字定义的变量是只读的,而使用 var 定义的变量可以在代码中由其他函数或用户任意读取和更改。
var changeableVariable = 8 changeableVariable =10 //the compiler doesn't complain, and the code compiles successfully println(changeableVariable) //10 val myNumber = 7 myNumber = 4 //if we try this the code won't compile
在我们将其声明为 val 后,尝试将值赋给 myNumber 会引发编译时错误或“重新赋值给 val”。
为什么要使用不变性?
不变性有助于我们防止代码和其他程序员意外更改我们的值,如果他们打算使用我们存储的值,这会导致意外结果,他们可以复制它。这样,可以防止由多个参与者更改同一变量引起的错误。
类和对象
我们都知道对象是现实世界的实体,类是定义对象的模板。类既有状态也有行为。状态可以是值也可以是变量。行为是 Scala 中的方法。
让我们看看如何在 Scala 中定义类、实例化类以及使用类。
这里,名为 Rectangle 的类有两个变量和两个函数。您也可以直接在程序中将参数 l 和 b 用作字段。您有一个包含 main 方法的对象,并用两个值实例化了该类。
示例
class Rectangle( l: Int, b: Int) { val length: Int = l val breadth: Int = b def getArea: Int = l * b override def toString = s"This is rectangle with length as $length and breadth as $breadth" } object RectObject { def main(args: Array[String]) { val rect = new Rectangle(4, 5) println(rect.toString) println(rect.getArea) } }
在 Scala 中,所有字段和方法默认都是 public 的。必须使用 override,因为 toString 方法是在 Scala 的 Object 中定义的。
继承
Scala 有多种类型的继承(如单继承、多层继承、多重继承、层次继承、混合继承),它们与 Java 中传统的继承形式有很多共同之处。您可以从类和特质(trait)继承。您可以使用关键字“extends”将一个类的成员继承到另一个类中。这实现了重用性。
可以从一个类或多个类继承。也可以从本身具有其超类的子类继承,在此过程中创建继承层次结构。
在下面的 Scala 示例中,基类是 Circle,派生类是 Sphere。Circle 有一个名为 radius 的值,它在 Sphere 类中被继承。calcArea 方法使用 override 关键字被重写。
示例
class Circle { val radius = 5; def calcArea = { println(radius * radius ) } } class Sphere extends Circle{ override def calcArea = { println(radius * radius * radius ) } } object SphereObject{ def main(args : Array[String]){ new Sphere().calcArea } }
抽象
在 Scala 中,我们可以使用抽象类和特征(trait)来创建抽象方法和成员字段。在抽象类和特征内部,我们可以定义抽象字段而无需实现它们。
示例
trait MakesSound{ var nameOfSound:String def sound():String } abstract class HasLegs(var legs:Int){ val creatureName:String def printLegs():String={ return s"$creatureName has this number of legs: $legs" } }
这些字段由扩展该特质或抽象类的类实现。您可以使用特质来创建应用程序应具备的功能契约,然后稍后实现这些方法。
trait DatabaseService{ def addItemName(itemName:String) def removeItem(itemId:Int) def updateItem(itemId:Int, newItemName:String) }
通过这种方式,我们可以规划我们的应用程序将如何设计,而无需实现这些方法,这可以帮助我们设想各种方法将如何工作。它遵循一种称为“面向抽象编程而非具体实现”的模式。
以关键字 abstract 开头的类可以包含抽象方法和非抽象方法。但是,抽象类不支持多重继承。因此,您最多只能扩展一个抽象类。
单例对象
单例是一种在程序中只实例化一次的类。它源自一种流行且有用的编程模式,称为“单例模式”。它在创建旨在长期存在且将在整个程序中普遍访问的实例时非常有用,其状态对于协调系统事件至关重要。在 Scala 中创建这样的类很容易,因为 Scala 为我们提供了使用 object 关键字创建单例的简单方法。
object UserProfile{ var userName="" var isLoggedIn:Boolean = false }
然后我们可以在整个程序中引用这个对象,并保证程序的所有部分都将看到相同的数据,因为只有一个实例。
def getLoggedInStatus():Boolean={ return UserProfile.isLoggedIn } def changeLoggedInStatus():Boolean={ UserProfile.isLoggedIn = !UserProfile.isLoggedIn return UserProfile.isLoggedIn }
Scala 中没有静态成员的概念,这就是你需要使用单例对象的原因,它就像类的静态成员一样。
隐式类
隐式类是 2.1 版本之后新增的功能。它主要用于为封闭类添加新功能。
implicit 关键字应定义在类、对象或特质中。隐式类的主要构造函数在其第一个参数列表中应只有一个参数。它也可以包含一个额外的隐式参数列表。
在下面的 Scala 示例中,添加了用 * 替换字符串元音的新功能。
object StringUtil { implicit class StringEnhancer(str: String) { def replaceVowelWithStar: String = str.replaceAll("[aeiou]", "*") } }
您需要在您使用它的类中导入它。
import StringUtil.StringEnhancer object ImplicitEx extends App { val msg = "This is Guru99!" println(msg.replaceVowelWithStar) }
面向对象编程 (OOP) 与函数式编程 (FP)
在 OOP 中,程序是通过将数据和操作该数据的函数分组到高度连接的单元中来构建的。对象在字段中携带数据,并对其进行操作的方法。在这种编程风格中,主要的抽象是数据,因为创建的方法旨在操作数据。
函数式编程,另一方面,将数据和操作数据的函数分开。这使得开发人员能够将函数视为抽象和建模程序时的驱动力。
Scala 通过将函数作为一等公民,允许它们作为值传递给其他函数并作为值返回,从而实现函数式编程。这两种范式的结合使 Scala 成为在各种行业(例如数据科学)中构建复杂软件的绝佳选择。
Scala 上的重要框架
以下是 Scala 的一些重要框架
- Play 是一个开源 Web 应用程序框架,使用 MVC 架构。它于 2007 年发布,现在在 Apache 许可下,并于 2013 年成为 GitHub 上最受欢迎的框架。LinkedIn、Walmart、Samsung、Eero 等公司都使用该框架。
- Lift 是另一个用 Scala 编写的免费 Web 框架,于 2007 年推出。Foursquare 使用 Lift 框架。它是一个高性能、构建速度更快的框架。
- Akka
- Cats
- Spark
并发支持
- Scala 中的值默认是不可变的。这使其非常适合并发环境。
- Scala 中有很多特性使其最适合并发应用程序。
- Future 和 Promise 使异步处理数据变得更容易,从而支持并行性。
- Akka – 使用 Actor 并发模型的工具包。有许多 Actor 在收到消息时会采取行动。
- Scala 也可以支持使用 Java 线程的并发。
- 流处理是另一个很棒的功能,它支持持续的实时数据处理。
Scala 拥有 Java 生态系统中一些最好的并发库。
- 原生 Java 线程
- 来自 Vertex 等库的 Fiber
- ZIO – 一个包含原始类型以帮助我们处理并发和异步计算的库
- STM – 事务
- Future – Scala 语言内置
Java vs. Scala
以下是 Java 和 Scala 之间的主要区别。
Scala | Java |
---|---|
更紧凑简洁 | 代码块相对较大 |
被设计和开发为面向对象和函数式编程语言。 支持多种函数式编程特性,如并发、不变性。 |
最初开发为面向对象语言,近年来开始支持函数式编程特性。但作为函数式编程语言仍然不够强大。 |
使用 Actor 模型支持并发,这是现代的 | 使用传统的基于线程的并发模型。 |
支持框架 – Play、Lift | 支持 Spring、Grails 等更多框架 |
支持惰性求值 | 不支持惰性求值 |
无静态成员 | 包含静态成员 |
支持运算符重载 | 不支持运算符重载 |
源代码编译速度相对较慢 | 源代码编译速度比 Scala 快 |
特质(Traits)——作用类似于 Java 8 接口 | Java 8 接口试图弥合类和接口之间的鸿沟 |
需要重写 | 不需要重写 |
无法保证代码无 bug | 完全保证缺陷较少 |
支持向后兼容。 | Scala 不支持向后兼容。 |
运算符在 Java 中被区别对待,不是方法调用。 | 在 Scala 中,对条目的所有操作都通过方法调用进行。 |
支持使用类而不是抽象类实现多重继承 | 不支持使用类,但支持使用接口进行多重继承 |
代码以紧凑的形式编写。 | 代码以长格式编写。 |
Scala 不包含 static 关键字。 | Java 包含 static 关键字。 |
摘要
在本教程中,您学习了如何开始使用 Scala。您还学习了函数式和面向对象特性。您还发现了 Java 和 Scala 之间的异同。本教程应该通过各种示例为您提供了很好的演示。