Scala 快速指南



Scala - 概述

Scala,缩写为 Scalable Language,是一种混合函数式编程语言。它由 Martin Odersky 创建。Scala 平滑地整合了面向对象和函数式语言的特性。Scala 编译后可在 Java 虚拟机上运行。许多依赖 Java 进行业务关键应用程序的现有公司正在转向 Scala 以提高其开发效率、应用程序可扩展性和整体可靠性。

这里我们列举了一些使 Scala 成为应用程序开发人员首选的原因。

Scala 是面向对象的

Scala 是一种纯面向对象的语言,因为每个值都是一个对象。对象的类型和行为由类和特质描述,这些将在后续章节中解释。

类通过子类化扩展,并使用灵活的基于混入的组合机制作为多重继承的简洁替代。

Scala 是函数式的

Scala 也是一种函数式语言,因为每个函数都是一个值,每个值都是一个对象,所以最终每个函数都是一个对象。

Scala 提供了轻量级的语法来定义匿名函数,它支持高阶函数,允许函数嵌套,并支持柯里化。这些概念将在后续章节中解释。

Scala 是静态类型的

与其他一些静态类型语言(C、Pascal、Rust 等)不同,Scala 不需要您提供冗余的类型信息。在大多数情况下,您不必指定类型,当然也不必重复。

Scala 运行在 JVM 上

Scala 编译成 Java 字节码,由 Java 虚拟机 (JVM) 执行。这意味着 Scala 和 Java 有一个共同的运行时平台。您可以轻松地从 Java 迁移到 Scala。

Scala 编译器将您的 Scala 代码编译成 Java 字节码,然后可以通过“scala”命令执行。 “scala”命令类似于java命令,它执行您编译后的 Scala 代码。

Scala 可以执行 Java 代码

Scala 使您可以使用所有 Java SDK 类以及您自己的自定义 Java 类或您最喜欢的 Java 开源项目。

Scala 可以进行并发和同步处理

Scala 允许您以有效的方式表达一般的编程模式。它减少了代码行数,并帮助程序员以类型安全的方式编写代码。它允许您以不可变的方式编写代码,这使得应用并发和并行(同步)变得容易。

Scala vs Java

Scala 拥有一套与 Java 完全不同的特性。其中一些是:

  • 所有类型都是对象
  • 类型推断
  • 嵌套函数
  • 函数是对象
  • 领域特定语言 (DSL) 支持
  • 特质
  • 闭包
  • 受 Erlang 启发的并发支持

Scala Web 框架

Scala 被广泛应用,尤其是在企业 Web 应用程序中。您可以查看一些最流行的 Scala Web 框架:

Scala - 环境搭建

Scala 可以安装在任何类 UNIX 或基于 Windows 的系统上。在开始在您的机器上安装 Scala 之前,您必须在您的计算机上安装 Java 1.8 或更高版本。

按照以下步骤安装 Scala。

步骤 1:验证您的 Java 安装

首先,您需要在您的系统上安装 Java 软件开发工具包 (SDK)。要验证这一点,请根据您使用的平台执行以下两个命令中的任何一个。

如果 Java 安装已正确完成,则它将显示您 Java 安装的当前版本和规范。示例输出如下表所示。

平台 命令 示例输出
Windows

打开命令控制台并键入:

>java –version

Java 版本 "1.8.0_31"

Java(TM) SE 运行时环境

(build 1.8.0_31-b31)

Java HotSpot(TM) 64位服务器虚拟机

(build 25.31-b07, mixed mode)

Linux

打开命令终端并键入:

$java –version

Java 版本 "1.8.0_31"

OpenJDK 运行时环境 (rhel-2.8.10.4.el6_4-x86_64)

OpenJDK 64位服务器虚拟机 (build 25.31-b07, mixed mode)

我们假设本教程的读者在他们的系统上安装了 Java SDK 版本 1.8.0_31。

如果您没有 Java SDK,请从http://www.oracle.com/technetwork/java/javase/downloads/index.html下载其当前版本并安装它。

步骤 2:设置您的 Java 环境

设置环境变量 JAVA_HOME 以指向 Java 安装在您机器上的基目录位置。例如:

序号 平台和描述
1

Windows

将 JAVA_HOME 设置为 C:\ProgramFiles\java\jdk1.7.0_60

2

Linux

export JAVA_HOME=/usr/local/java-current

将 Java 编译器位置的完整路径添加到系统路径。

序号 平台和描述
1

Windows

将字符串 "C:\Program Files\Java\jdk1.7.0_60\bin" 附加到系统变量 PATH 的末尾。

2

Linux

export PATH=$PATH:$JAVA_HOME/bin/

如上所述,从命令提示符执行命令java -version

步骤 3:安装 Scala

您可以从https://scala-lang.org.cn/downloads下载 Scala。在撰写本教程时,我下载了“scala-2.11.5-installer.jar”。确保您拥有管理员权限才能继续。现在,在命令提示符下执行以下命令:

平台 命令和输出 描述
Windows

>java –jar scala-2.11.5-installer.jar>

此命令将显示一个安装向导,该向导将指导您在 Windows 机器上安装 Scala。在安装过程中,它将要求您接受许可协议,只需接受它,然后它将询问将安装 Scala 的路径。我选择了默认路径“C:\Program Files\Scala”,您可以根据自己的方便选择合适的路径。

Linux

命令

$java –jar scala-2.9.0.1-installer.jar

输出

欢迎安装 Scala 2.9.0.1!

主页位于:https://scala-lang.org.cn/

按 1 继续,按 2 退出,按 3 重新显示

1................................................

[ 开始解包 ]

[ 处理包:软件包安装 (1/1) ]

[ 解包完成 ]

[ 控制台安装完成 ]

在安装过程中,它将要求您接受许可协议,要接受它,请键入 1,它将询问将安装 Scala 的路径。我输入了`/usr/local/share`,您可以根据自己的方便选择合适的路径。

最后,打开一个新的命令提示符并键入scala -version然后按 Enter 键。您应该看到以下内容:

平台 命令 输出
Windows

>scala -version

Scala 代码运行器版本 2.11.5 -- 版权所有 2002-2013,LAMP/EPFL

Linux

$scala -version

Scala 代码运行器版本 2.9.0.1 – 版权所有 2002-2013,LAMP/EPFL

Scala - 基本语法

如果您对 Java 有很好的理解,那么学习 Scala 将非常容易。Scala 和 Java 之间最大的语法差异是分号 ';' 行尾字符是可选的。

当我们考虑一个 Scala 程序时,它可以定义为通过调用彼此方法进行通信的对象集合。现在让我们简要了解一下类、对象、方法和实例变量分别是什么意思。

  • 对象 - 对象具有状态和行为。对象是类的实例。例如 - 狗具有状态 - 颜色、名称、品种以及行为 - 摇尾巴、吠叫和吃东西。

  • - 类可以定义为描述与类相关的行为/状态的模板/蓝图。

  • 方法 - 方法基本上是一种行为。一个类可以包含许多方法。逻辑是在方法中编写的,数据被操作,所有操作都被执行。

  • 字段 - 每个对象都有一组唯一的实例变量,称为字段。对象的 state 是由分配给这些字段的值创建的。

  • 闭包 - 闭包是一个函数,其返回值取决于在此函数外部声明的一个或多个变量的值。

  • 特质 - 特质封装了方法和字段定义,然后可以通过将它们混入类中来重复使用它们。特质用于通过指定支持方法的签名来定义对象类型。

第一个 Scala 程序

我们可以通过两种模式执行 Scala 程序:一种是交互模式,另一种是脚本模式

交互模式

打开命令提示符并使用以下命令打开 Scala。

\>scala

如果您的系统中安装了 Scala,则将显示以下输出:

Welcome to Scala version 2.9.0.1
Type in expressions to have them evaluated.
Type :help for more information.

在 Scala 提示符的右侧键入以下文本,然后按 Enter 键:

scala> println("Hello, Scala!");

它将产生以下结果:

Hello, Scala!

脚本模式

使用以下说明以脚本模式编写 Scala 程序。打开记事本并将以下代码添加到其中。

示例

object HelloWorld {
   /* This is my first java program.  
   * This will print 'Hello World' as the output
   */
   def main(args: Array[String]) {
      println("Hello, world!") // prints Hello World
   }
}

将文件保存为 − HelloWorld.scala

打开命令提示符窗口并转到保存程序文件的目录。使用“scalac”命令编译 Scala 程序,它将在当前目录中生成一些类文件。其中一个将被称为HelloWorld.class。这是一个字节码,它将使用“scala”命令在 Java 虚拟机 (JVM) 上运行。

使用以下命令编译和执行您的 Scala 程序。

\> scalac HelloWorld.scala
\> scala HelloWorld

输出

Hello, World!

基本语法

以下是 Scala 编程中的基本语法和编码约定。

  • 大小写敏感性 − Scala 区分大小写,这意味着标识符Hellohello在 Scala 中具有不同的含义。

  • 类名 − 所有类名的第一个字母都应大写。如果使用多个单词来构成类名,则每个内部单词的第一个字母都应大写。

    示例 − class MyFirstScalaClass。

  • 方法名 − 所有方法名都应以小写字母开头。如果使用多个单词来构成方法名,则每个内部单词的第一个字母都应大写。

    示例 − def myMethodName()

  • 程序文件名 − 程序文件名应与对象名完全匹配。保存文件时,应使用对象名保存它(记住 Scala 区分大小写),并在名称末尾附加“.scala”。(如果文件名和对象名不匹配,则程序将无法编译)。

    示例 − 假设“HelloWorld”是对象名。则文件应保存为“HelloWorld.scala”。

  • def main(args: Array[String]) − Scala 程序处理从 main() 方法开始,它是每个 Scala 程序的必备部分。

Scala 标识符

所有 Scala 组件都需要名称。用于对象、类、变量和方法的名称称为标识符。关键字不能用作标识符,并且标识符区分大小写。Scala 支持四种类型的标识符。

字母数字标识符

字母数字标识符以字母或下划线开头,后面可以跟字母、数字或下划线。'$' 字符是 Scala 中的保留关键字,不应在标识符中使用。

以下是合法的字母数字标识符

age, salary, _value,  __1_value

以下是非法的标识符

$salary, 123abc, -salary

运算符标识符

运算符标识符由一个或多个运算符字符组成。运算符字符是可打印的 ASCII 字符,例如 +、:、?、~ 或 #。

以下是合法的运算符标识符 −

+ ++ ::: <?> :>

Scala 编译器将在内部“修改”运算符标识符,将其转换为包含嵌入式 $ 字符的合法 Java 标识符。例如,标识符 :-> 在内部将表示为 $colon$minus$greater。

混合标识符

混合标识符由一个字母数字标识符组成,后面跟一个下划线和一个运算符标识符。

以下是合法的混合标识符 −

unary_+,  myvar_=

这里,unary_+ 用作方法名定义一元 + 运算符,myvar_= 用作方法名定义赋值运算符(运算符重载)。

字面量标识符

字面量标识符是用反引号 (`. . . ` )括起来的任意字符串。

以下是合法的字面量标识符 −

`x` `<clinit>` `yield`

Scala 关键字

以下列表显示了 Scala 中的保留字。这些保留字不能用作常量或变量或任何其他标识符名称。

abstract case catch class
def do else extends
false final finally for
forSome if implicit import
lazy match new Null
object override package private
protected return sealed super
this throw trait Try
true type val var
while with yield  
- : = =>
<- <: <% >:
# @

Scala 中的注释

Scala 支持单行和多行注释,与 Java 非常相似。多行注释可以嵌套,但必须正确嵌套。Scala 编译器会忽略任何注释中所有可用的字符。

object HelloWorld {
   /* This is my first java program.  
    * This will print 'Hello World' as the output
    * This is an example of multi-line comments.
    */
   def main(args: Array[String]) {
      // Prints Hello World
      // This is also an example of single line comment.
      println("Hello, world!") 
   }
}

空行和空格

仅包含空格(可能带有注释)的行称为空行,Scala 会完全忽略它。标记可以用空格字符和/或注释分隔。

换行符

Scala 是一种面向行的语言,其中语句可以用分号 (;) 或换行符终止。语句末尾的分号通常是可选的。如果需要,可以输入一个,但如果语句单独出现在一行中,则不必输入。另一方面,如果在一行中编写多个语句,则需要分号。以下语法是多个语句的用法。

val s = "hello"; println(s)

Scala 包

包是代码的命名模块。例如,Lift 实用程序包是 net.liftweb.util。包声明是源文件中的第一行非注释行,如下所示 −

package com.liftcode.stuff

可以导入 Scala 包,以便可以在当前编译范围内引用它们。以下语句导入 scala.xml 包的内容 −

import scala.xml._

可以导入单个类和对象,例如,来自 scala.collection.mutable 包的 HashMap −

import scala.collection.mutable.HashMap

可以从单个包中导入多个类或对象,例如,来自 scala.collection.immutable 包的 TreeMap 和 TreeSet −

import scala.collection.immutable.{TreeMap, TreeSet}

应用动态

启用动态调用的标记特征。此特征的实例 x 允许对任意方法名 meth 和参数列表 args 进行方法调用 x.meth(args),以及对任意字段名 field 进行字段访问 x.field。此功能在 Scala-2.10 中引入。

如果 x 本身不支持调用(即,如果类型检查失败),则会根据以下规则重写它 −

foo.method("blah") ~~> foo.applyDynamic("method")("blah")
foo.method(x = "blah") ~~> foo.applyDynamicNamed("method")(("x", "blah"))
foo.method(x = 1, 2) ~~> foo.applyDynamicNamed("method")(("x", 1), ("", 2))
foo.field ~~> foo.selectDynamic("field")
foo.varia = 10 ~~> foo.updateDynamic("varia")(10)
foo.arr(10) = 13 ~~> foo.selectDynamic("arr").update(10, 13)
foo.arr(10) ~~> foo.applyDynamic("arr")(10)

Scala - 数据类型

Scala 与 Java 具有相同的全部数据类型,具有相同的内存占用量和精度。以下是表格,其中详细介绍了 Scala 中可用的所有数据类型 −

序号 数据类型和描述
1

Byte

8 位有符号值。范围从 -128 到 127

2

Short

16 位有符号值。范围 -32768 到 32767

3

Int

32 位有符号值。范围 -2147483648 到 2147483647

4

Long

64 位有符号值。-9223372036854775808 到 9223372036854775807

5

Float

32 位 IEEE 754 单精度浮点数

6

Double

64 位 IEEE 754 双精度浮点数

7

Char

16 位无符号 Unicode 字符。范围从 U+0000 到 U+FFFF

8

String

一系列 Char

9

Boolean

文字 true 或文字 false

10

Unit

对应于无值

11

Null

null 或空引用

12

Nothing

每种其他类型的子类型;不包含任何值

13

Any

任何类型的超类型;任何对象都是 Any 类型

14

AnyRef

任何引用类型的超类型

上面列出的所有数据类型都是对象。与 Java 中不同,没有基本类型。这意味着可以在 Int、Long 等上调用方法。

Scala 基本字面量

Scala 用于字面量的规则简单直观。本节解释所有基本的 Scala 字面量。

整数字面量

整数字面量通常为 Int 类型,或者在后跟 L 或 l 后缀时为 Long 类型。以下是一些整数字面量 −

0
035
21 
0xFFFFFFFF 
0777L

浮点数字面量

浮点数字面量在后跟浮点数类型后缀 F 或 f 时为 Float 类型,否则为 Double 类型。以下是一些浮点数字面量 −

0.0 
1e30f 
3.14159f 
1.0e100
.1

布尔字面量

布尔字面量truefalse是 Boolean 类型的成员。

符号字面量

符号字面量 'x' 是表达式scala.Symbol("x")的简写。Symbol 是一个 case class,定义如下。

package scala
final case class Symbol private (name: String) {
   override def toString: String = "'" + name
}

字符字面量

字符字面量是用引号括起来的单个字符。该字符或者是可打印的 Unicode 字符,或者是用转义序列描述的。以下是一些字符字面量 −

'a' 
'\u0041'
'\n'
'\t'

字符串字面量

字符串字面量是用双引号括起来的一系列字符。这些字符或者是可打印的 Unicode 字符,或者是用转义序列描述的。以下是一些字符串字面量 −

"Hello,\nWorld!"
"This string contains a \" character."

多行字符串

多行字符串字面量是用三个引号 """ ... """括起来的一系列字符。字符序列是任意的,除非它可能只在最后包含三个或更多连续的引号字符。

字符不一定是可打印的;换行符或其他控制字符也是允许的。这是一个多行字符串字面量 −

"""the present string
spans three
lines."""

空值

空值为scala.Null类型,因此与每个引用类型兼容。它表示引用一个特殊的“null”对象的引用值。

转义序列

在字符和字符串字面量中识别以下转义序列。

转义序列 Unicode 描述
\b \u0008 退格 BS
\t \u0009 水平制表符 HT
\n \u000c 换页 FF
\f \u000c 换页 FF
\r \u000d 回车 CR
\" \u0022 双引号 "
\' \u0027 单引号 .
\\ \u005c 反斜杠 \

Unicode 在 0 到 255 之间的字符也可以用八进制转义表示,即反斜杠 '\' 后跟最多三个八进制字符的序列。以下示例显示了一些转义序列字符 −

示例

object Test {
   def main(args: Array[String]) {
      println("Hello\tWorld\n\n" );
   }
} 

编译并执行上述代码后,将产生以下结果 −

输出

Hello   World

Scala - 变量

变量只不过是保留的内存位置以存储值。这意味着当创建变量时,会在内存中保留一些空间。

根据变量的数据类型,编译器会分配内存并决定可以在保留的内存中存储什么。因此,通过为变量分配不同的数据类型,可以在这些变量中存储整数、小数或字符。

变量声明

Scala 具有不同的变量声明语法。它们可以定义为值,即常量或变量。这里,myVar 使用关键字 var 声明。它是一个可以改变值的变量,这称为可变变量。以下是使用var关键字定义变量的语法 −

语法

var myVar : String = "Foo"

这里,myVal 使用关键字 val 声明。这意味着它是一个不能改变值的变量,这称为不可变变量。以下是使用 val 关键字定义变量的语法 −

语法

val myVal : String = "Foo"

变量数据类型

变量的类型在变量名之后和等号之前指定。可以通过提及其数据类型来定义任何类型的 Scala 变量,如下所示 −

语法

val or val VariableName : DataType = [Initial Value]

如果不为变量分配任何初始值,则它是有效的,如下所示 −

语法

var myVar :Int;
val myVal :String;

变量类型推断

为变量分配初始值时,Scala 编译器可以根据分配给它的值推断出变量的类型。这称为变量类型推断。因此,可以这样编写这些变量声明 −

语法

var myVar = 10;
val myVal = "Hello, Scala!";

这里,默认情况下,myVar 将为 Int 类型,myVal 将成为 String 类型变量。

多重赋值

Scala 支持多重赋值。如果代码块或方法返回一个元组(元组 − 保持不同类型的对象的集合),则元组可以赋值给 val 变量。[注意 − 我们将在后续章节中学习元组。]

语法

val (myVar1: Int, myVar2: String) = Pair(40, "Foo")

并且类型推断能够正确处理 −

语法

val (myVar1, myVar2) = Pair(40, "Foo")

示例程序

以下是一个示例程序,它解释了在 Scala 中声明变量的过程。此程序声明四个变量 — 两个变量是用类型声明定义的,其余两个没有类型声明。

示例

object Demo {
   def main(args: Array[String]) {
      var myVar :Int = 10;
      val myVal :String = "Hello Scala with datatype declaration.";
      var myVar1 = 20;
      val myVal1 = "Hello Scala new without datatype declaration.";
      
      println(myVar); println(myVal); println(myVar1); 
      println(myVal1);
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

10
Hello Scala with datatype declaration.
20
Hello Scala without datatype declaration.

变量作用域

Scala 中的变量根据其使用位置可以有三种不同的作用域。它们可以作为字段、方法参数和局部变量存在。以下是关于每种作用域类型的详细信息。

字段

字段是属于对象的变量。字段可以从对象的每个方法内部访问。字段也可以从对象外部访问,具体取决于声明字段时使用的访问修饰符。对象字段可以是可变类型和不可变类型,并且可以使用varval定义。

方法参数

方法参数是变量,用于在调用方法时将值传递到方法内部。方法参数只能从方法内部访问,但是如果从方法外部持有对象的引用,则传递的对象可能可以从外部访问。方法参数始终是不可变的,由val关键字定义。

局部变量

局部变量是在方法内部声明的变量。局部变量只能从方法内部访问,但是如果从方法中返回创建的对象,则这些对象可能会“逃逸”方法。局部变量可以是可变类型和不可变类型,并且可以使用varval定义。

Scala - 类和对象

本章将引导您学习如何在 Scala 编程中使用类和对象。类是对象的蓝图。定义类后,可以使用关键字new从类蓝图创建对象。通过对象,您可以使用已定义类所有功能。

下图以“学生”类为例演示了类和对象,该类包含成员变量(姓名和学号)和成员方法(setName() 和 setRollNo())。最终,所有这些都是类的成员。类是蓝图,对象是实际存在的实体。在下图中,“学生”是一个类,Harini、John 和 Maria 是“学生”类的对象,它们都具有姓名和学号。

Scala Classes and Objects

基本类

以下是定义 Scala 基本类的简单语法。此类定义了两个变量xy以及一个方法:move,该方法不返回值。类变量称为类的字段,方法称为类方法。

类名充当类构造函数,它可以接受多个参数。上面的代码定义了两个构造函数参数xcyc;它们在整个类的主体中都是可见的。

语法

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 ("Point x location : " + x);
      println ("Point y location : " + y);
   }
}

如本章前面所述,您可以使用关键字new创建对象,然后可以访问类字段和方法,如下面的示例所示:

示例

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 ("Point x location : " + x);
      println ("Point y location : " + y);
   }
}

object Demo {
   def main(args: Array[String]) {
      val pt = new Point(10, 20);

      // Move to a new location
      pt.move(10, 10);
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Point x location : 20
Point y location : 30

扩展类

您可以扩展基本 Scala 类,并且可以像在 Java 中一样设计继承类(使用extends关键字),但是有两个限制:方法重写需要override关键字,并且只有构造函数才能将参数传递给基构造函数。让我们扩展上面的类并添加另一个类方法。

示例

让我们以两个类为例:Point 类(与上面的示例相同)和 Location 类,Location 类使用 extends 关键字继承自 Point 类。“extends”子句有两个作用:它使 Location 类继承 Point 类所有非私有成员,并使类型Location成为类型Point类的子类型。因此,这里 Point 类称为超类,类Location称为子类。扩展类并继承父类所有特性称为继承,但是 Scala 只允许从一个类继承。

注意 - Point 类中的 move() 方法和Location 类中的 move() 方法并没有重写 move 的相应定义,因为它们是不同的定义(例如,前者接受两个参数,而后者接受三个参数)。

尝试以下示例程序来实现继承。

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 ("Point x location : " + x);
      println ("Point y location : " + 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 ("Point x location : " + x);
      println ("Point y location : " + y);
      println ("Point z location : " + z);
   }
}

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

      // Move to a new location
      loc.move(10, 10, 5);
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Point x location : 20
Point y location : 30
Point z location : 20

隐式类

隐式类允许在类在作用域内时与类的 primary 构造函数进行隐式转换。隐式类是用“implicit”关键字标记的类。此功能在 Scala 2.10 中引入。

语法 - 以下是隐式类的语法。这里的隐式类始终在对象作用域中,其中允许所有方法定义,因为隐式类不能是顶级类。

语法

object <object name> {
   implicit class <class name>(<Variable>: Data type) {
      def <method>(): Unit =
   }
}

示例

让我们以一个名为IntTimes的隐式类为例,它包含 times() 方法。这意味着 times() 包含一个循环事务,该事务将执行给定语句指定的次数。假设给定语句是“4 times println (“Hello”)”,这意味着 println (“Hello”) 语句将执行 4 次。

以下是给定示例的程序。在此示例中使用了两个对象类(Run 和 Demo),因此必须将这两个类分别保存到不同的文件中,文件名分别为 Run 和 Demo,如下所示。

Run.scala - 将以下程序保存到 Run.scala 中。

object Run {
   implicit class IntTimes(x: Int) {
      def times [A](f: =>A): Unit = {
         def loop(current: Int): Unit =
         
         if(current > 0){
            f
            loop(current - 1)
         }
         loop(x)
      }
   }
}

Demo.scala - 将以下程序保存到 Demo.scala 中。

import Run._

object Demo {
   def main(args: Array[String]) {
      4 times println("hello")
   }
}

以下命令用于编译和执行这两个程序。

命令

\>scalac Run.scala
\>scalac Demo.scala
\>scala Demo

输出

Hello
Hello
Hello
Hello

注意 -

  • 隐式类必须定义在另一个类/对象/特质内部(不能在顶级)。

  • 隐式类在其构造函数中只能接受一个非隐式参数。

  • 隐式类不能是作用域内任何名称与隐式类相同的任何方法、成员或对象。

单例对象

Scala 比 Java 更面向对象,因为在 Scala 中,我们不能有静态成员。相反,Scala 有单例对象。单例是一个只能有一个实例的类,即对象。您可以使用关键字object而不是 class 关键字来创建单例。由于您无法实例化单例对象,因此您无法将参数传递给主构造函数。您已经看到所有使用单例对象的示例,在这些示例中,您调用了 Scala 的 main 方法。

以下是实现单例的相同示例程序。

示例

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
   }
}

object Demo {
   def main(args: Array[String]) {
      val point = new Point(10, 20)
      printPoint

      def printPoint{
         println ("Point x location : " + point.x);
         println ("Point y location : " + point.y);
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Point x location : 10
Point y location : 20

Scala - 访问修饰符

本章将介绍 Scala 访问修饰符。包、类或对象的成员可以使用访问修饰符 private 和 protected 进行标记,如果我们不使用这两个关键字中的任何一个,则访问将被假定为 public。这些修饰符将对成员的访问限制在代码的某些区域。要使用访问修饰符,您需要在其成员的定义中包含其关键字,如下节所示。

私有成员

私有成员仅在其包含成员定义的类或对象内部可见。

以下是解释私有成员的示例代码片段:

示例

class Outer {
   class Inner {
      private def f() { println("f") }
      
      class InnerMost {
         f() // OK
      }
   }
   (new Inner).f() // Error: f is not accessible
}

在 Scala 中,访问 (new Inner).f() 是非法的,因为 f 在 Inner 中声明为私有,并且访问不是来自 Inner 类内部。相反,Innermost 类中对 f 的第一次访问是可以的,因为该访问包含在 Inner 类的主体中。Java 将允许这两种访问,因为它允许外部类访问其内部类的私有成员。

受保护成员

受保护成员只能从定义成员的类的子类访问。

以下是解释受保护成员的示例代码片段:

示例

package p {
   class Super {
      protected def f() { println("f") }
   }
   
   class Sub extends Super {
      f()
   }
   
   class Other {
      (new Super).f() // Error: f is not accessible
   }
}

在 Sub 类中访问 f 是可以的,因为 f 在“Super”类中声明为受保护,并且“Sub”类是 Super 的子类。相反,不允许在“Other”类中访问 f,因为“Other”类没有从“Super”类继承。在 Java 中,后者访问仍然是被允许的,因为“Other”类与“Sub”类在同一个包中。

公共成员

与私有和受保护成员不同,不需要为公共成员指定 Public 关键字。公共成员没有显式修饰符。这些成员可以从任何地方访问。

以下是解释公共成员的示例代码片段:

示例

class Outer {
   class Inner {
      def f() { println("f") }
      
      class InnerMost {
         f() // OK
      }
   }
   (new Inner).f() // OK because now f() is public
}

保护作用域

Scala 中的访问修饰符可以使用限定符进行增强。形式为 private[X] 或 protected[X] 的修饰符表示访问对 X 是私有的或受保护的,“直到”X,其中 X 指定某个封闭的包、类或单例对象。

考虑以下示例:

示例

package society {
   package professional {
      class Executive {
         private[professional] var workDetails = null
         private[society] var friends = null
         private[this] var secrets = null

         def help(another : Executive) {
            println(another.workDetails)
            println(another.secrets) //ERROR
         }
      }
   }
}

注意 - 以上示例中的以下几点:

  • 变量 workDetails 可被专业包内的任何类访问。

  • 变量 friends 可被社会包内的任何类访问。

  • 变量 secrets 只能在实例方法 (this) 内的隐式对象上访问。

Scala - 运算符

运算符是一个符号,它告诉编译器执行特定的数学或逻辑操作。Scala 拥有丰富的内置运算符,并提供以下类型的运算符:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符

本章将逐一检查算术、关系、逻辑、位、赋值和其他运算符。

算术运算符

Scala 语言支持以下算术运算符。例如,假设变量 A 为 10,变量 B 为 20,则:

示例

运算符 描述 示例
+ 将两个操作数相加 A + B 将得到 30
- 从第一个操作数中减去第二个操作数 A - B 将得到 -10
* 将两个操作数相乘 A * B 将得到 200
/ 将分子除以分母 B / A 将得到 2
% 模运算符找到一个数除以另一个数后的余数 B % A 将得到 0

关系运算符

Scala 语言支持以下关系运算符。例如,假设变量 A 为 10,变量 B 为 20,则:

示例

运算符 描述 示例
== 检查两个操作数的值是否相等,如果相等,则条件为真。 (A == B) 为假。
!= 检查两个操作数的值是否相等,如果不相等,则条件为真。 (A != B) 为真。
> 检查左侧操作数的值是否大于右侧操作数的值,如果大于,则条件为真。 (A > B) 为假。
< 检查左侧操作数的值是否小于右侧操作数的值,如果小于,则条件为真。 (A < B) 为真。
>= 检查左侧操作数的值是否大于或等于右侧操作数的值,如果大于或等于,则条件为真。 (A >= B) 为假。
<= 检查左侧操作数的值是否小于或等于右侧操作数的值,如果小于或等于,则条件为真。 (A <= B) 为真。

逻辑运算符

Scala 语言支持以下逻辑运算符。例如,假设变量 A 为 1,变量 B 为 0,则:

示例

运算符 描述 示例
&& 称为逻辑与运算符。如果两个操作数都不为零,则条件为真。 (A && B) 为假。
|| 称为逻辑或运算符。如果两个操作数中的任何一个都不为零,则条件为真。 (A || B) 为真。
! 称为逻辑非运算符。用于反转其操作数的逻辑状态。如果条件为真,则逻辑非运算符将使其为假。 !(A && B) 为真。

位运算符

位运算符作用于位并执行逐位运算。&, | 和 ^ 的真值表如下:

p q p & q p | q p ^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

假设 A = 60;B = 13;现在以二进制格式,它们将如下所示:

A = 0011 1100
B = 0000 1101
-----------------------
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
~A = 1100 0011

下表列出了 Scala 语言支持的位运算符。假设变量 A 为 60,变量 B 为 13,则:

示例

运算符 描述 示例
& 二元 AND 运算符:如果该位同时存在于两个操作数中,则将其复制到结果中。 (A & B) 将得到 12,即 0000 1100
| 二元 OR 运算符:如果该位存在于任一操作数中,则将其复制到结果中。 (A | B) 将得到 61,即 0011 1101
^ 二元 XOR 运算符:如果该位仅在一个操作数中设置,则将其复制到结果中。 (A ^ B) 将得到 49,即 0011 0001
~ 二元反码运算符:它是单目运算符,作用是“翻转”位。 (~A) 将得到 -61,由于是带符号二进制数,因此其二进制补码形式为 1100 0011。
<< 二元左移运算符:左操作数的值的位位置向左移动右操作数指定的位数。 A << 2 将得到 240,即 1111 0000
>> 二元右移运算符:左操作数的值的位位置向右移动右操作数指定的位数。 A >> 2 将得到 15,即 1111
>>> 右移零填充运算符:左操作数的值向右移动右操作数指定的位数,移出的位用零填充。 A >>>2 将得到 15,即 0000 1111

赋值运算符

Scala 语言支持以下赋值运算符:

示例

运算符 描述 示例
= 简单赋值运算符:将右操作数的值赋给左操作数。 C = A + B 将 A + B 的值赋给 C
+= 加法赋值运算符:将右操作数加到左操作数上,并将结果赋给左操作数。 C += A 等价于 C = C + A
-= 减法赋值运算符:从左操作数中减去右操作数,并将结果赋给左操作数。 C -= A 等价于 C = C - A
*= 乘法赋值运算符:将右操作数乘以左操作数,并将结果赋给左操作数。 C *= A 等价于 C = C * A
/= 除法赋值运算符:将左操作数除以右操作数,并将结果赋给左操作数。 C /= A 等价于 C = C / A
%= 取模赋值运算符:对两个操作数取模,并将结果赋给左操作数。 C %= A 等价于 C = C % A
<<= 左移赋值运算符 C <<= 2 等价于 C = C << 2
>>= 右移赋值运算符 C >>= 2 等价于 C = C >> 2
&= 按位 AND 赋值运算符 C &= 2 等价于 C = C & 2
^= 按位异或赋值运算符 C ^= 2 等价于 C = C ^ 2
|= 按位或赋值运算符 C |= 2 等价于 C = C | 2

Scala 中的运算符优先级

运算符优先级决定了表达式中项的组合方式。这会影响表达式的计算方式。某些运算符比其他运算符具有更高的优先级;例如,乘法运算符的优先级高于加法运算符:

例如,x = 7 + 3 * 2;此处,x 被赋值为 13,而不是 20,因为运算符 * 的优先级高于 +,所以它先与 3*2 相乘,然后加到 7 中。

请查看下表。优先级最高的运算符出现在表的上方,优先级最低的运算符出现在表的下方。在一个表达式中,优先级较高的运算符将首先计算。

类别 运算符 结合性
后缀 () [] 从左到右
一元 ! ~ 从右到左
乘法 * / % 从左到右
加法 + - 从左到右
移位 >> >>> << 从左到右
关系 > >= < <= 从左到右
相等 == != 从左到右
按位 AND & 从左到右
按位 XOR ^ 从左到右
按位 OR | 从左到右
逻辑 AND && 从左到右
逻辑 OR || 从左到右
赋值 = += -= *= /= %= >>= <<= &= ^= |= 从右到左
逗号 , 从左到右

Scala - IF ELSE 语句

本章将引导您学习 Scala 编程中的条件构造语句。以下是大多数编程语言中常见的典型决策 IF...ELSE 结构的一般形式。

流程图

以下是条件语句的流程图。

Scala IF...ELSE Structure

if 语句

“if”语句由一个布尔表达式和一个或多个语句组成。

语法

“if”语句的语法如下。

if(Boolean_expression) {
   // Statements will execute if the Boolean expression is true
}

如果布尔表达式计算结果为真,则将执行“if”表达式内的代码块。如果不是,则将执行“if”表达式结束后的第一组代码(右大括号之后)。

尝试以下示例程序以了解 Scala 编程语言中的条件表达式(if 表达式)。

示例

object Demo {
   def main(args: Array[String]) {
      var x = 10;

      if( x < 20 ){
         println("This is if statement");
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

This is if statement

If-else 语句

“if”语句后面可以跟一个可选的else语句,当布尔表达式为假时执行。

语法

if...else 的语法为:

if(Boolean_expression){
   //Executes when the Boolean expression is true
} else{
   //Executes when the Boolean expression is false
}

尝试以下示例程序以了解 Scala 编程语言中的条件语句(if-else 语句)。

示例

object Demo {
   def main(args: Array[String]) {
      var x = 30;

      if( x < 20 ){
         println("This is if statement");
      } else {
         println("This is else statement");
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

This is else statement

If-else-if-else 语句

“if”语句后面可以跟一个可选的“else if...else”语句,这对于使用单个 if...else if 语句测试各种条件非常有用。

使用 if、else if、else 语句时,需要记住几点。

  • 一个“if”可以有零个或一个 else,并且它必须位于任何 else if 之后。

  • 一个“if”可以有零个到多个 else if,并且它们必须位于 else 之前。

  • 一旦 else if 成功,就不会测试其余的 else if 或 else。

语法

以下是“if...else if...else”的语法:

if(Boolean_expression 1){
   //Executes when the Boolean expression 1 is true
} else if(Boolean_expression 2){
   //Executes when the Boolean expression 2 is true
} else if(Boolean_expression 3){
   //Executes when the Boolean expression 3 is true
} else {
   //Executes when the none of the above condition is true.
}

尝试以下示例程序以了解 Scala 编程语言中的条件语句(if-else-if-else 语句)。

示例

object Demo {
   def main(args: Array[String]) {
      var x = 30;

      if( x == 10 ){
         println("Value of X is 10");
      } else if( x == 20 ){
         println("Value of X is 20");
      } else if( x == 30 ){
         println("Value of X is 30");
      } else{
         println("This is else statement");
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Value of X is 30

嵌套 if-else 语句

嵌套if-else语句始终是合法的,这意味着您可以在另一个ifelse-if语句内使用一个ifelse-if语句。

语法

嵌套 if-else 的语法如下:

if(Boolean_expression 1){
   //Executes when the Boolean expression 1 is true
   
   if(Boolean_expression 2){
      //Executes when the Boolean expression 2 is true
   }
}

尝试以下示例程序以了解 Scala 编程语言中的条件语句(嵌套 if 语句)。

示例

object Demo {
   def main(args: Array[String]) {
      var x = 30;
      var y = 10;
      
      if( x == 30 ){
         if( y == 10 ){
            println("X = 30 and Y = 10");
         }
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

X = 30 and Y = 10

Scala - 循环语句

本章将引导您学习 Scala 编程语言中的循环控制结构。

可能存在需要多次执行代码块的情况。通常,语句按顺序执行:函数中的第一个语句首先执行,然后是第二个语句,依此类推。

编程语言提供各种控制结构,允许更复杂的执行路径。

循环语句允许我们多次执行语句或语句组,以下是大多数编程语言中循环语句的一般形式:

流程图

Loop Architecture

Scala 编程语言提供以下类型的循环来处理循环需求。单击表中的以下链接以检查其详细信息。

序号 循环类型和描述
1

while 循环

在给定条件为真的情况下重复语句或语句组。它在执行循环体之前测试条件。

2

do-while 循环

类似于 while 语句,不同之处在于它在循环体末尾测试条件。

3

for 循环

多次执行一系列语句,并缩写管理循环变量的代码。

循环控制语句

循环控制语句会改变其正常执行顺序。当执行离开作用域时,在该作用域中创建的所有自动对象都将被销毁。因此,Scala 不像 Java 那样支持breakcontinue语句,但从 Scala 2.8 版本开始,有一种方法可以中断循环。单击以下链接以检查详细信息。

序号 控制语句和描述
1

break 语句

终止循环语句并将执行转移到循环之后紧跟的语句。

无限循环

如果条件永不为假,则循环将变为无限循环。如果您使用的是 Scala,则while循环是实现无限循环的最佳方法。

以下程序实现了无限循环。

示例

object Demo {
   def main(args: Array[String]) {
      var a = 10;
      
      // An infinite loop.
      while( true ){
         println( "Value of a: " + a );
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

如果您执行上述代码,它将进入无限循环,您可以按 Ctrl + C 键终止。

Value of a: 10
Value of a: 10
Value of a: 10
Value of a: 10
…………….

Scala - 函数

函数是一组执行任务的语句。您可以将代码分成单独的函数。您如何将代码划分为不同的函数取决于您自己,但从逻辑上讲,划分通常是让每个函数执行特定任务。

Scala 既有函数也有方法,我们互换使用术语“方法”和“函数”,区别很小。Scala 方法是类的组成部分,它有名称、签名、可选的一些注释和一些字节码,而 Scala 中的函数是一个完整的对象,可以赋值给变量。换句话说,定义为某个对象成员的函数称为方法。

函数定义可以出现在源文件的任何位置,Scala 允许嵌套函数定义,即其他函数定义内的函数定义。需要注意的最重要一点是 Scala 函数的名称可以包含 +、++、~、&、-、--、\、/、: 等字符。

函数声明

Scala 函数声明具有以下形式:

def functionName ([list of parameters]) : [return type]

如果您不使用等号和方法体,则方法将隐式声明为抽象

函数定义

Scala 函数定义具有以下形式:

语法

def functionName ([list of parameters]) : [return type] = {
   function body
   return [expr]
}

此处,返回类型可以是任何有效的 Scala 数据类型,参数列表将是用逗号分隔的变量列表,参数列表和返回类型都是可选的。与 Java 非常相似,如果函数返回值,则可以使用return语句以及表达式。以下是将添加两个整数并返回其和的函数:

语法

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

不返回任何内容的函数可以返回一个Unit,它等价于 Java 中的void,表示函数不返回任何内容。在 Scala 中不返回任何内容的函数称为过程。

语法

语法如下:

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

调用函数

Scala 提供了许多调用方法的语法变体。以下是调用方法的标准方法:

functionName( list of parameters )

如果使用对象的实例调用函数,则可以使用类似于 Java 的点表示法,如下所示:

[instance.]functionName( list of parameters )

尝试以下示例程序来定义和调用同一个函数。

示例

object Demo {
   def main(args: Array[String]) {
      println( "Returned Value : " + addInt(5,7) );
   }
   
   def addInt( a:Int, b:Int ) : Int = {
      var sum:Int = 0
      sum = a + b

      return sum
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Returned Value : 12

Scala 函数是 Scala 编程的核心,这就是 Scala 被认为是函数式编程语言的原因。以下是 Scala 程序员应该理解的几个与 Scala 函数相关的重要的概念。

Scala - 闭包

闭包是一个函数,其返回值取决于在此函数外部声明的一个或多个变量的值。

以下代码片段使用了匿名函数。

val multiplier = (i:Int) => i * 10

这里函数体中唯一使用的变量 i * 10 是 i,它被定义为函数的参数。尝试以下代码:

val multiplier = (i:Int) => i * factor

multiplier 中有两个自由变量:ifactor。其中之一,i,是函数的形式参数。因此,每次调用 multiplier 时,它都会绑定到一个新值。但是,factor 不是形式参数,那么它是什么呢?让我们再添加一行代码。

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

现在factor引用了函数外部但在封闭作用域中的变量。函数引用factor并在每次调用时读取其当前值。如果一个函数没有外部引用,那么它就自身闭包。不需要外部上下文。

尝试以下示例程序。

示例

object Demo {
   def main(args: Array[String]) {
      println( "multiplier(1) value = " +  multiplier(1) )
      println( "multiplier(2) value = " +  multiplier(2) )
   }
   var factor = 3
   val multiplier = (i:Int) => i * factor
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

multiplier(1) value = 3
multiplier(2) value = 6

Scala - 字符串

本章将带您了解 Scala 字符串。在 Scala 中,与 Java 一样,字符串是不可变对象,即无法修改的对象。另一方面,可以修改的对象(如数组)称为可变对象。字符串是非常有用的对象,在本节的其余部分中,我们将介绍java.lang.String类的重要方法。

创建字符串

可以使用以下代码创建字符串:

var greeting = "Hello world!";

or

var greeting:String = "Hello world!";

每当编译器在代码中遇到字符串字面量时,它都会创建一个具有其值的 String 对象,在本例中为“Hello world!”。如上所示,也可以使用 String 关键字进行替代声明。

尝试以下示例程序。

示例

object Demo {
   val greeting: String = "Hello, world!"

   def main(args: Array[String]) {
      println( greeting )
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Hello, world!

如前所述,String 类是不可变的。创建 String 对象后便无法更改。如果需要对字符的字符串进行大量修改,则可以使用 Scala 中提供的 StringBuilder 类!

字符串长度

用于获取有关对象信息的方法称为访问器方法。可用于字符串的一种访问器方法是 length() 方法,它返回字符串对象中包含的字符数。

使用以下代码段查找字符串的长度:

示例

object Demo {
   def main(args: Array[String]) {
      var palindrome = "Dot saw I was Tod";
      var len = palindrome.length();
      
      println( "String Length is : " + len );
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

String Length is : 17

连接字符串

String 类包含一个用于连接两个字符串的方法:

string1.concat(string2);

这将返回一个新字符串,该字符串是 string1,其中 string2 添加到其末尾。您也可以将 concat() 方法与字符串字面量一起使用,如下所示:

"My name is ".concat("Zara");

字符串通常使用 + 运算符连接,如下所示:

"Hello," + " world" + "!"

结果为:

"Hello, world!"

以下代码行用于查找字符串长度。

示例

object Demo {
   def main(args: Array[String]) {
      var str1 = "Dot saw I was ";
      var str2 =  "Tod";
      
      println("Dot " + str1 + str2);
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Dot Dot saw I was Tod

创建格式化字符串

您可以使用 printf() 和 format() 方法以格式化的数字打印输出。String 类有一个等效的类方法 format(),它返回 String 对象而不是 PrintStream 对象。

尝试以下示例程序,它使用了 printf() 方法:

示例

object Demo {
   def main(args: Array[String]) {
      var floatVar = 12.456
      var intVar = 2000
      var stringVar = "Hello, Scala!"
      
      var fs = printf("The value of the float variable is " + "%f, while the value of the integer " + "variable is %d, and the string" + "is %s", floatVar, intVar, stringVar);
      
      println(fs)
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

The value of the float variable is 12.456000, 
while the value of the integer variable is 2000, 
and the string is Hello, Scala!()

字符串插值

字符串插值是在 Scala 编程语言中创建字符串的新方法。此功能支持 Scala-2.10 及更高版本。字符串插值:在处理字符串字面量时直接嵌入变量引用的机制。

字符串插值中有三种类型的实现(插值器)。

‘s’ 字符串插值器

字面量 ‘s’ 允许在处理字符串时直接使用变量,当您在其前面加上 ‘s’ 时。任何在作用域内的字符串变量都可以与字符串一起使用。以下是 ‘s’ 字符串插值器的不同用法。

以下示例代码片段演示了在 println 语句中将 ‘s’ 插值器用于将字符串变量 ($name) 附加到普通字符串 (Hello) 的实现。

val name = “James”
println(s “Hello, $name”) //output: Hello, James

字符串插值器还可以处理任意表达式。以下代码片段使用 ‘s’ 字符串插值器处理带有任意表达式 (${1 + 1}) 的字符串 (1 + 1)。任何任意表达式都可以嵌入在 ‘${}’ 中。

println(s “1 + 1 = ${1 + 1}”) //output: 1 + 1 = 2

尝试以下实现 ‘s’ 插值器的示例程序。

示例

object Demo {
   def main(args: Array[String]) {
      val name = "James"
      
      println(s"Hello, $name")
      println(s"1 + 1 = ${1 + 1}")
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Hello, James
1 + 1 = 2

‘f’ 插值器

字面量 ‘f’ 插值器允许创建格式化的字符串,类似于 C 语言中的 printf。使用 ‘f’ 插值器时,所有变量引用后都应跟随printf样式的格式说明符,例如 %d、%i、%f 等。

让我们以附加浮点值 (height = 1.9d) 和字符串变量 (name = “James”) 与普通字符串为例。以下是实现 ‘f’ 插值器的代码片段。这里 $name%s 用于打印(字符串变量)James,$height%2.2f 用于打印(浮点值)1.90。

val height = 1.9d
val name = "James"
println(f"$name%s is $height%2.2f meters tall") //James is 1.90 meters tall

它是类型安全的(即)变量引用和后面的格式说明符应该匹配,否则会显示错误。‘f’ 插值器使用 Java 中提供的字符串格式实用程序(格式说明符)。默认情况下,变量引用后没有 % 字符。它将假定为 %s(字符串)。

‘raw’ 插值器

‘raw’ 插值器类似于 ‘s’ 插值器,只是它不对字符串内的字面量执行任何转义。表中的以下代码片段将 ‘s’ 和 ‘raw’ 插值器的用法区分开来。在 ‘s’ 用法的输出中,‘\n’ 的效果是换行,而在 ‘raw’ 用法的输出中,‘\n’ 将不起作用。它将打印包含转义字符的完整字符串。

‘s’ 插值器用法 ‘raw’ 插值器用法

程序

object Demo {
   def main(args: Array[String]) {
      println(s"Result = \n a \n b")
   }
}

程序

object Demo {
   def main(args: Array[String]) {
      println(raw"Result = \n a \n b")
   }
}

输出

Result =
a
b

输出

Result = \n a \n b

字符串方法

以下是java.lang.String类定义的方法,可以直接在您的 Scala 程序中使用:

序号 带描述的方法
1

char charAt(int index)

返回指定索引处的字符。

2

int compareTo(Object o)

将此字符串与另一个对象进行比较。

3

int compareTo(String anotherString)

按字典顺序比较两个字符串。

4

int compareToIgnoreCase(String str)

按字典顺序比较两个字符串,忽略大小写差异。

5

String concat(String str)

将指定的字符串连接到此字符串的末尾。

6

boolean contentEquals(StringBuffer sb)

当且仅当此字符串表示与指定的 StringBuffer 相同的字符序列时返回 true。

7

static String copyValueOf(char[] data)

返回表示数组中指定字符序列的字符串。

8

static String copyValueOf(char[] data, int offset, int count)

返回表示数组中指定字符序列的字符串。

9

boolean endsWith(String suffix)

测试此字符串是否以指定的 suffix 结尾。

10

boolean equals(Object anObject)

将此字符串与指定的对象进行比较。

11

boolean equalsIgnoreCase(String anotherString)

将此字符串与另一个字符串进行比较,忽略大小写。

12

byte getBytes()

使用平台的默认字符集将此字符串编码为字节序列,并将结果存储到新的字节数组中。

13

byte[] getBytes(String charsetName)

使用指定的字符集将此字符串编码为字节序列,并将结果存储到新的字节数组中。

14

void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

将字符从此字符串复制到目标字符数组中。

15

int hashCode()

返回此字符串的哈希码。

16

int indexOf(int ch)

返回此字符串中指定字符第一次出现的索引。

17

int indexOf(int ch, int fromIndex)

返回此字符串中指定字符第一次出现的索引,从指定的索引开始搜索。

18

int indexOf(String str)

返回此字符串中指定子字符串第一次出现的索引。

19

int indexOf(String str, int fromIndex)

返回此字符串中指定子字符串第一次出现的索引,从指定的索引开始。

20

String intern()

返回字符串对象的规范表示。

21

int lastIndexOf(int ch)

返回此字符串中指定字符最后一次出现的索引。

22

int lastIndexOf(int ch, int fromIndex)

返回此字符串中指定字符最后一次出现的索引,从指定的索引向后搜索。

23

int lastIndexOf(String str)

返回此字符串中指定子字符串最右边出现的索引。

24

int lastIndexOf(String str, int fromIndex)

返回此字符串中指定子字符串最后一次出现的索引,从指定的索引向后搜索。

25

int length()

返回此字符串的长度。

26

boolean matches(String regex)

判断此字符串是否与给定的正则表达式匹配。

27

boolean regionMatches(boolean ignoreCase, int toffset, String other, int offset, int len)

测试两个字符串区域是否相等。

28

boolean regionMatches(int toffset, String other, int offset, int len)

测试两个字符串区域是否相等。

29

String replace(char oldChar, char newChar)

返回一个新字符串,该字符串是通过将此字符串中所有出现的 oldChar 替换为 newChar 得到的。

30

String replaceAll(String regex, String replacement)

将此字符串中与给定正则表达式匹配的每个子字符串替换为给定的替换字符串。

31

String replaceFirst(String regex, String replacement)

将此字符串中与给定正则表达式匹配的第一个子字符串替换为给定的替换字符串。

32

String[] split(String regex)

根据给定正则表达式的匹配拆分此字符串。

33

String[] split(String regex, int limit)

根据给定正则表达式的匹配拆分此字符串。

34

boolean startsWith(String prefix)

测试此字符串是否以指定的 prefix 开头。

35

boolean startsWith(String prefix, int toffset)

测试此字符串是否以从指定索引开始的指定 prefix 开头。

36

CharSequence subSequence(int beginIndex, int endIndex)

返回一个新的字符序列,它是此序列的子序列。

37

String substring(int beginIndex)

返回一个新的字符串,它是此字符串的子字符串。

38

String substring(int beginIndex, int endIndex)

返回一个新的字符串,它是此字符串的子字符串。

39

char[] toCharArray()

将此字符串转换为新的字符数组。

40

String toLowerCase()

使用默认语言环境的规则将此字符串中的所有字符转换为小写。

41

String toLowerCase(Locale locale)

使用给定语言环境的规则将此字符串中的所有字符转换为小写。

42

String toString()

返回此对象(它本身就是一个字符串!)。

43

String toUpperCase()

使用默认语言环境的规则将此字符串中的所有字符转换为大写。

44

String toUpperCase(Locale locale)

使用给定语言环境的规则将此字符串中的所有字符转换为大写。

45

String trim()

返回字符串的副本,其中省略了开头和结尾的空格。

46

static String valueOf(primitive data type x)

返回传递的数据类型参数的字符串表示形式。

Scala - 数组

Scala 提供了一种数据结构——数组,它存储固定大小的相同类型元素的顺序集合。数组用于存储数据集合,但通常将数组视为相同类型的变量集合更有用。

与其声明单独的变量,例如 number0、number1……number99,不如声明一个数组变量,例如 numbers,并使用 numbers[0]、numbers[1]……numbers[99] 来表示各个单独的变量。本教程介绍如何声明数组变量、创建数组以及使用索引变量处理数组。数组第一个元素的索引是数字零,最后一个元素的索引是元素总数减一。

声明数组变量

要在程序中使用数组,必须声明一个变量来引用数组,并且必须指定该变量可以引用的数组类型。

以下是声明数组变量的语法:

语法

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

or

var z = new Array[String](3)

这里,z 被声明为一个字符串数组,最多可以容纳三个元素。可以为各个元素赋值,或者访问各个元素,可以使用以下命令:

命令

z(0) = "Zara"; z(1) = "Nuha"; z(4/2) = "Ayan"

这里,最后一个例子表明,一般来说,索引可以是任何产生整数的表达式。还有一种定义数组的方法:

var z = Array("Zara", "Nuha", "Ayan")

下图表示一个数组 **myList**。这里,**myList** 包含十个双精度值,索引从 0 到 9。

Scala Array

处理数组

处理数组元素时,我们经常使用循环控制结构,因为数组中的所有元素都是相同类型,并且数组的大小是已知的。

下面是一个示例程序,演示如何创建、初始化和处理数组:

示例

object Demo {
   def main(args: Array[String]) {
      var myList = Array(1.9, 2.9, 3.4, 3.5)
      
      // Print all the array elements
      for ( x <- myList ) {
         println( x )
      }

      // Summing all elements
      var total = 0.0;
      
      for ( i <- 0 to (myList.length - 1)) {
         total += myList(i);
      }
      println("Total is " + total);

      // Finding the largest element
      var max = myList(0);
      
      for ( i <- 1 to (myList.length - 1) ) {
         if (myList(i) > max) max = myList(i);
      }
      
      println("Max is " + max);
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

1.9
2.9
3.4
3.5
Total is 11.7
Max is 3.5

Scala 不直接支持各种数组操作,并提供各种方法来处理任何维度的数组。如果要使用不同的方法,则需要导入 **Array._** 包。

多维数组

在许多情况下,您需要定义和使用多维数组(即元素为数组的数组)。例如,矩阵和表格是可以实现为二维数组的结构示例。

以下是定义二维数组的示例:

var myMatrix = ofDim[Int](3,3)

这是一个数组,它有三个元素,每个元素都是一个包含三个整数元素的数组。

尝试以下示例程序来处理多维数组:

示例

import Array._

object Demo {
   def main(args: Array[String]) {
      var myMatrix = ofDim[Int](3,3)
      
      // build a matrix
      for (i <- 0 to 2) {
         for ( j <- 0 to 2) {
            myMatrix(i)(j) = j;
         }
      }
      
      // Print two dimensional array
      for (i <- 0 to 2) {
         for ( j <- 0 to 2) {
            print(" " + myMatrix(i)(j));
         }
         println();
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

0 1 2
0 1 2
0 1 2

连接数组

尝试以下示例,它使用 concat() 方法连接两个数组。您可以将多个数组作为参数传递给 concat() 方法。

示例

import Array._

object Demo {
   def main(args: Array[String]) {
      var myList1 = Array(1.9, 2.9, 3.4, 3.5)
      var myList2 = Array(8.9, 7.9, 0.4, 1.5)

      var myList3 =  concat( myList1, myList2)
      
      // Print all the array elements
      for ( x <- myList3 ) {
         println( x )
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

1.9
2.9
3.4
3.5
8.9
7.9
0.4
1.5

使用范围创建数组

使用 range() 方法生成一个包含给定范围内递增整数序列的数组。您可以使用最终参数作为步长来创建序列;如果您不使用最终参数,则步长将假定为 1。

让我们以创建范围 (10, 20, 2) 的数组为例:这意味着创建一个元素在 10 和 20 之间且范围差为 2 的数组。数组中的元素为 10、12、14、16 和 18。

另一个示例:range(10, 20)。这里没有给出范围差,因此默认情况下它假设为 1。它创建一个数组,其中包含 10 和 20 之间的元素,范围差为 1。数组中的元素为 10、11、12、13……和 19。

以下示例程序演示如何创建具有范围的数组。

示例

import Array._

object Demo {
   def main(args: Array[String]) {
      var myList1 = range(10, 20, 2)
      var myList2 = range(10,20)

      // Print all the array elements
      for ( x <- myList1 ) {
         print( " " + x )
      }
      
      println()
      for ( x <- myList2 ) {
         print( " " + x )
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

10 12 14 16 18
10 11 12 13 14 15 16 17 18 19

Scala 数组方法

以下是您可以玩转数组时可以使用的重要方法。如上所示,在使用任何提到的方法之前,您必须导入 **Array._** 包。有关可用方法的完整列表,请查看 Scala 的官方文档。

序号 带描述的方法
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 的 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]

返回一个数组,其中包含将函数重复应用于起始值的计算结果。

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 iterate[T](start: T, len: Int)(f: (T) => T): Array[T]

返回一个数组,其中包含将函数重复应用于起始值的计算结果。

9

def ofDim[T](n1: Int): Array[T]

创建具有给定维度的数组。

10

def ofDim[T](n1: Int, n2: Int): Array[Array[T]]

创建一个二维数组

11

def ofDim[T](n1: Int, n2: Int, n3: Int): Array[Array[Array[T]]]

创建一个三维数组

12

def range(start: Int, end: Int, step: Int): Array[Int]

返回一个数组,其中包含某个整数区间内等间距的值。

13

def range(start: Int, end: Int): Array[Int]

返回一个数组,其中包含一个范围内递增整数的序列。

14

def tabulate[T](n: Int)(f: (Int) => T): Array[T]

返回一个数组,其中包含给定函数在一系列从 0 开始的整数值上的值。

15

def tabulate[T](n1: Int, n2: Int)(f: (Int, Int) => T): Array[Array[T]]

返回一个二维数组,其中包含给定函数在一系列从 0 开始的整数值上的值。

Scala - 集合

Scala 拥有丰富的集合库。集合是事物的容器。这些容器可以是有序的,线性集合的项目,例如列表、元组、选项、映射等。集合可以有任意数量的元素,也可以限制为零个或一个元素(例如,Option)。

集合可以是 **严格的** 或 **惰性的**。惰性集合的元素在访问之前可能不会占用内存,例如 **Ranges**。此外,集合可以是 **可变的**(引用的内容可以更改)或 **不可变的**(引用引用的内容永远不会更改)。请注意,不可变集合可以包含可变项目。

对于某些问题,可变集合效果更好,而对于其他问题,不可变集合效果更好。如有疑问,最好从不可变集合开始,如果需要可变集合,则稍后更改。

本章阐述了最常用的集合类型和这些集合上最常用的操作。

序号 带说明的集合
1

Scala 列表

Scala 的 List[T] 是类型为 T 的链表。

2

Scala 集合

集合是相同类型成对不同元素的集合。

3

Scala 映射

映射是键/值对的集合。可以根据其键检索任何值。

4

Scala 元组

与数组或列表不同,元组可以保存不同类型的对象。

5

Scala 选项

Option[T] 为给定类型的一个或零个元素提供一个容器。

6

Scala 迭代器

迭代器不是集合,而是一种一次一个地访问集合元素的方式。

Scala - 特质

特征封装了方法和字段定义,然后可以通过将它们混合到类中来重用。与类继承不同,在类继承中,每个类必须从一个超类继承,类可以混合任意数量的特征。

特征用于通过指定支持方法的签名来定义对象类型。Scala 还允许部分实现特征,但特征可能没有构造函数参数。

特征定义看起来就像类定义一样,只是它使用了关键字 **trait**。以下是特征的基本示例语法。

语法

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

此特征包含两个方法 **isEqual** 和 **isNotEqual**。在这里,我们没有为 isEqual 提供任何实现,而另一个方法有其实现。扩展特征的子类可以为未实现的方法提供实现。因此,特征与我们在 Java 中使用的 **抽象类** 非常相似。

让我们假设一个包含两个方法 **isEqual()** 和 **isNotEqual()** 的特征 **Equal** 的示例。特征 **Equal** 包含一个已实现的方法 **isEqual()**,因此当用户定义的类 **Point** 扩展特征 **Equal** 时,应该提供 **Point** 类中 **isEqual()** 方法的实现。

这里需要了解 Scala 的两个重要方法,它们在以下示例中使用。

  • **obj.isInstanceOf[Point]** 用于检查 obj 类型和 Point 类型是否相同。

  • **obj.asInstanceOf[Point]** 表示通过获取对象 obj 类型并将其作为 Point 类型返回来进行精确转换。

尝试以下示例程序来实现特征。

示例

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 == y
}

object Demo {
   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))
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

true
false
true

值类和通用特征

值类是 Scala 中一种新的机制,用于避免分配运行时对象。它包含一个只有一个 **val** 参数的主构造函数。它只包含方法 (def),不允许使用 var、val、嵌套类、特征或对象。值类不能被另一个类扩展。这可以通过使用 AnyVal 扩展您的值类来实现。在没有运行时开销的情况下实现自定义数据类型的类型安全。

让我们以值类 Weight、Height、Email、Age 等为例。对于所有这些示例,不需要在应用程序中分配内存。

值类不允许扩展特征。为了允许值类扩展特征,引入了扩展 **Any** 的 **通用特征**。

示例

trait Printable extends Any {
   def print(): Unit = println(this)
}
class Wrapper(val underlying: Int) extends AnyVal with Printable

object Demo {
   def main(args: Array[String]) {
      val w = new Wrapper(3)
      w.print() // actually requires instantiating a Wrapper instance
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

它将为您提供包装类的哈希码。

Wrapper@13

何时使用特征?

没有明确的规则,但这里有一些需要考虑的指导原则:

  • 如果行为不会被重用,则将其设为具体类。毕竟它不是可重用的行为。

  • 如果它可能在多个不相关的类中重用,则将其设为特征。只有特征才能混合到类层次结构的不同部分。

  • 如果要在 Java 代码中从中 **继承**,请使用抽象类。

  • 如果您计划以编译形式分发它,并且您期望外部组编写从中继承的类,您可能会倾向于使用抽象类。

  • 如果效率非常重要,则倾向于使用类。

Scala - 模式匹配

模式匹配是 Scala 中继函数值和闭包之后第二常用的特性。Scala 在处理消息时为模式匹配提供了强大的支持。

模式匹配包括一系列备选方案,每个备选方案都以关键字 **case** 开头。每个备选方案都包含一个 **模式** 和一个或多个 **表达式**,如果模式匹配,则将对这些表达式求值。箭头符号 => 将模式与表达式分开。

尝试以下示例程序,该程序演示如何与整数值匹配。

示例

object Demo {
   def main(args: Array[String]) {
      println(matchTest(3))
   }
   
   def matchTest(x: Int): String = x match {
      case 1 => "one"
      case 2 => "two"
      case _ => "many"
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

many

包含 case 语句的代码块定义了一个函数,该函数将整数映射到字符串。match 关键字提供了一种方便的方法来将函数(如上面的模式匹配函数)应用于对象。

尝试以下示例程序,该程序将值与不同类型的模式匹配。

示例

object Demo {
   def main(args: Array[String]) {
      println(matchTest("two"))
      println(matchTest("test"))
      println(matchTest(1))
   }
   
   def matchTest(x: Any): Any = x match {
      case 1 => "one"
      case "two" => 2
      case y: Int => "scala.Int"
      case _ => "many"
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

2
many
one

使用案例类进行匹配

**案例类** 是在模式匹配中与 case 表达式一起使用的特殊类。从语法上讲,这些是带有特殊修饰符的标准类:**case**。

尝试以下操作,这是一个使用案例类的简单模式匹配示例。

示例

object Demo {
   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)
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Hi Alice!
Hi Bob!
Age: 32 year, name: Charlie?

添加 case 关键字会导致编译器自动添加许多有用的功能。该关键字建议与模式匹配中的 case 表达式相关联。

首先,编译器会自动将构造函数参数转换为不可变字段 (vals)。val 关键字是可选的。如果您想要可变字段,请使用 var 关键字。因此,我们的构造函数参数列表现在更短了。

其次,编译器会自动为类实现equals、hashCodetoString方法,这些方法使用指定为构造函数参数的字段。因此,我们不再需要自己的toString()方法了。

最后,Person类的正文也为空,因为没有我们需要定义的方法!

Scala - 正则表达式

本章解释Scala如何通过scala.util.matching包中提供的Regex类支持正则表达式。

尝试以下示例程序,我们将尝试从语句中找出单词Scala

示例

import scala.util.matching.Regex

object Demo {
   def main(args: Array[String]) {
      val pattern = "Scala".r
      val str = "Scala is Scalable and cool"
      
      println(pattern findFirstIn str)
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Some(Scala)

我们创建一个字符串,并调用其上的r()方法。Scala隐式地将字符串转换为RichString并调用该方法以获取Regex实例。要查找正则表达式的第一个匹配项,只需调用findFirstIn()方法。如果我们想查找匹配单词的所有出现,而不是只查找第一个出现,我们可以使用findAllIn()方法,如果目标字符串中有多个Scala单词,这将返回所有匹配单词的集合。

您可以使用mkString()方法连接结果列表,并且可以使用管道符号(|)搜索Scala的小写和大写形式,并且可以使用Regex构造函数代替r()方法来创建模式。

尝试以下示例程序。

示例

import scala.util.matching.Regex

object Demo {
   def main(args: Array[String]) {
      val pattern = new Regex("(S|s)cala")
      val str = "Scala is scalable and cool"
      
      println((pattern findAllIn str).mkString(","))
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Scala,scala

如果要替换匹配的文本,我们可以使用replaceFirstIn()替换第一个匹配项,或者使用replaceAllIn()替换所有匹配项。

示例

object Demo {
   def main(args: Array[String]) {
      val pattern = "(S|s)cala".r
      val str = "Scala is scalable and cool"
      
      println(pattern replaceFirstIn(str, "Java"))
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Java is scalable and cool

正则表达式的构成

Scala继承了Java的正则表达式语法,而Java又继承了Perl的大部分功能。这里只是一些示例,应该足以作为复习——

下表列出了Java中可用的所有正则表达式元字符语法。

子表达式 匹配
^ 匹配行首。
$ 匹配行尾。
. 匹配除换行符以外的任何单个字符。使用m选项允许它匹配换行符。
[...] 匹配括号中的任何单个字符。
[^...] 匹配括号中不存在的任何单个字符
\\A 整个字符串的开头
\\z 整个字符串的结尾
\\Z 整个字符串的结尾,除了允许的最终换行符。
re* 匹配前面表达式的0个或多个出现。
re+ 匹配前面表达式的1个或多个出现
re? 匹配前面表达式的0个或1个出现。
re{ n} 精确匹配前面表达式的n个出现。
re{ n,} 匹配n个或更多前面表达式的出现。
re{ n, m} 匹配前面表达式的至少n个和最多m个出现。
a|b 匹配a或b。
(re) 对正则表达式进行分组并记住匹配的文本。
(?: re) 对正则表达式进行分组而不记住匹配的文本。
(?> re) 匹配不回溯的独立模式。
\\w 匹配单词字符。
\\W 匹配非单词字符。
\\s 匹配空格。相当于[\t\n\r\f]。
\\S 匹配非空格。
\\d 匹配数字。相当于[0-9]。
\\D 匹配非数字。
\\A 匹配字符串的开头。
\\Z 匹配字符串的结尾。如果存在换行符,则匹配换行符之前的部分。
\\z 匹配字符串的结尾。
\\G 匹配上次匹配结束的位置。
\\n 反向引用到捕获组编号“n”
\\b 在括号之外匹配单词边界。在括号内匹配退格符 (0x08)。
\\B 匹配非单词边界。
\\n, \\t, etc. 匹配换行符、回车符、制表符等。
\\Q 转义(引用)直到\\E的所有字符
\\E 结束由\\Q开始的引用

正则表达式示例

示例 描述
. 匹配除换行符之外的任何字符
[Rr]uby 匹配“Ruby”或“ruby”
rub[ye] 匹配“ruby”或“rube”
[aeiou] 匹配任何一个小写元音
[0-9] 匹配任何数字;与[0123456789]相同
[a-z] 匹配任何小写ASCII字母
[A-Z] 匹配任何大写ASCII字母
[a-zA-Z0-9] 匹配以上任何一个
[^aeiou] 匹配除小写元音之外的任何字符
[^0-9] 匹配除数字以外的任何字符
\\d 匹配一个数字:[0-9]
\\D 匹配一个非数字:[ ^0-9]
\\s 匹配一个空格字符:[ \t\r\n\f]
\\S 匹配非空格:[ ^\t\r\n\f]
\\w 匹配单个单词字符:[A-Za-z0-9_]
\\W 匹配一个非单词字符:[ ^A-Za-z0-9_]
ruby? 匹配“rub”或“ruby”:y是可选的
ruby* 匹配“rub”加上0个或多个y
ruby+ 匹配“rub”加上1个或多个y
\\d{3} 精确匹配3个数字
\\d{3,} 匹配3个或更多数字
\\d{3,5} 匹配3、4或5个数字
\\D\\d+ 无分组:+ 重复 \\d
(\\D\\d)+/ 分组:+ 重复 \\D\d 对
([Rr]uby(, )?)+ 匹配“Ruby”、“Ruby, ruby, ruby”等。

注意——上述字符串中每个反斜杠都出现了两次。这是因为在Java和Scala中,单个反斜杠是字符串文字中的转义字符,而不是出现在字符串中的常规字符。因此,您需要编写“\\”而不是“\”才能在字符串中获得单个反斜杠。

尝试以下示例程序。

示例

import scala.util.matching.Regex

object Demo {
   def main(args: Array[String]) {
      val pattern = new Regex("abl[ae]\\d+")
      val str = "ablaw is able1 and cool"
      
      println((pattern findAllIn str).mkString(","))
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

able1

Scala - 异常处理

Scala的异常与Java等许多其他语言中的异常一样。方法不会以通常的方式返回值,而是通过抛出异常来终止。但是,Scala实际上没有检查异常。

当您要处理异常时,您可以像在Java中一样使用try{...}catch{...}块,只是catch块使用匹配来识别和处理异常。

抛出异常

抛出异常看起来与Java中一样。您创建一个异常对象,然后使用throw关键字抛出它,如下所示。

throw new IllegalArgumentException

捕获异常

Scala允许您在一个块中try/catch任何异常,然后使用case块对其进行模式匹配。尝试以下示例程序来处理异常。

示例

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

object Demo {
   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")
         }
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Missing file exception

try-catch表达式的行为与其他具有异常的语言相同。执行主体,如果它抛出异常,则依次尝试每个catch子句。

finally子句

如果要强制执行某些代码,无论表达式如何终止,都可以使用finally子句包装表达式。尝试以下程序。

示例

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

object Demo {
   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...")
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Missing file exception
Exiting finally...

Scala - 提取器

Scala中的提取器是一个对象,它具有名为unapply的方法作为其成员之一。unapply方法的目的是匹配一个值并将其分解。通常,提取器对象还定义了一个用于构建值的双重方法apply,但这并非必需。

示例

让我们以一个同时定义applyunapply方法的对象为例。apply方法的含义与往常一样:它将Test转换为可以像方法一样应用于括号中参数的对象。因此,您可以编写Test("Zara", "gmail.com")来构造字符串"[email protected]"。

unapply方法将Test类转换为提取器,它反转了apply的构造过程。apply接收两个字符串并从中形成电子邮件地址字符串,而unapply接收一个电子邮件地址并返回可能两个字符串:地址的用户

unapply还必须处理给定字符串不是电子邮件地址的情况。这就是为什么unapply返回字符串对的Option类型。其结果是,如果字符串str是具有给定用户和域部分的电子邮件地址,则为Some(user, domain),如果str不是电子邮件地址,则为None。以下是一些示例。

语法

unapply("[email protected]") equals Some("Zara", "gmail.com")
unapply("Zara Ali") equals None

以下示例程序显示了电子邮件地址的提取器对象。

示例

object Demo {
   def main(args: Array[String]) {
      println ("Apply method : " + apply("Zara", "gmail.com"));
      println ("Unapply method : " + unapply("[email protected]"));
      println ("Unapply method : " + unapply("Zara Ali"));
   }
   
   // The injection method (optional)
   def apply(user: String, domain: String) = {
      user +"@"+ domain
   }

   // The extraction method (mandatory)
   def unapply(str: String): Option[(String, String)] = {
      val parts = str split "@"
      
      if (parts.length == 2){
         Some(parts(0), parts(1)) 
      } else {
         None
      }
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Apply method : [email protected]
Unapply method : Some((Zara,gmail.com))
Unapply method : None

使用提取器的模式匹配

当类实例后跟带有零个或多个参数的列表的括号时,编译器会在该实例上调用apply方法。我们可以在对象和类中定义apply。

如上所述,unapply方法的目的是提取我们正在寻找的特定值。它执行与apply相反的操作。当使用match语句比较提取器对象时,将自动执行unapply方法。

尝试以下示例程序。

示例

object Demo {
   def main(args: Array[String]) {
      val x = Demo(5)
      println(x)

      x match {
         case Demo(num) => println(x+" is bigger two times than "+num)
         
         //unapply is invoked
         case _ => println("i cannot calculate")
      }
   }
   def apply(x: Int) = x*2
   def unapply(z: Int): Option[Int] = if (z%2==0) Some(z/2) else None
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

10
10 is bigger two times than 5

Scala - 文件 I/O

Scala可以自由使用任何Java对象,而java.io.File是可以用于Scala编程中读取和写入文件的对象之一。

以下是一个写入文件的示例程序。

示例

import java.io._

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

      writer.write("Hello Scala")
      writer.close()
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

它将在程序所在位置的当前目录中创建一个名为Demo.txt的文件。以下是该文件的内容。

输出

Hello Scala

从命令行读取一行

有时您需要从屏幕读取用户输入,然后继续进行一些进一步的处理。以下示例程序向您展示如何从命令行读取输入。

示例

object Demo {
   def main(args: Array[String]) {
      print("Please enter your input : " )
      val line = Console.readLine
      
      println("Thanks, you just typed: " + line)
   }
}

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Please enter your input : Scala is great
Thanks, you just typed: Scala is great

读取文件内容

从文件读取非常简单。您可以使用Scala的Source类及其伴生对象来读取文件。以下是向您展示如何从我们之前创建的"Demo.txt"文件读取的示例。

示例

import scala.io.Source

object Demo {
   def main(args: Array[String]) {
      println("Following is the content read:" )

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

将上述程序保存在Demo.scala中。以下命令用于编译和执行此程序。

命令

\>scalac Demo.scala
\>scala Demo

输出

Following is the content read:
Hello Scala
广告