【Scala之旅】基础知识
本节翻译自
综述:介绍了什么是Scala,Scala的基础语法、类型层次结构,以及包和引入。
简介
欢迎光临
此旅途包含了一些精简的介绍:介绍Scala最常用的功能。它旨在帮助新手学习这门语言。
这只是一个简短的介绍,而不是一个完整的语言教程。如果可以的话,考虑买本书或查阅其他资源。
什么是Scala?
Scala是一种现代多范式编程语言,旨在以简洁,优雅和类型安全的方式表达常见的编程模式。它平滑地集成了面向对象和函数式语言的特性。
Scala是面向对象语言
Scala是一个纯面向对象语言,在某种意义上来讲所有值都是对象。对象的类型和行为是由类和特质来描述的。类通过子类化和灵活的基于混入的组合机制扩展为多重继承的完全替代。
Scala是函数式语言
Scala还是一个函数式语言,在某种意义上来讲所有函数都是值。Scala提供了轻量级的语法来定义匿名函数,它支持高阶函数,允许函数嵌套,并支持柯里化。Scala的样例类及其对模式匹配模型代数类型的内置支持,用于许多函数式编程语言。单例对象提供了一种便捷的方法来对不是类成员的函数进行分组。
此外,Scala的模式匹配概念自然延伸到借助于忽略右序列模式的XML数据处理,通过通过提取器对象进行的一般扩展。在这种情况下,for推导式有助于制定查询。这些功能使Scala成为开发Web服务等应用程序的理想选择。
Scala是静态类型语言
Scala配备了一种表达型系统,可以静态强制执行以安全和一致的方式使用抽象。具体来说,该类型系统支持:
类型推断意味着用户不需要使用冗余类型信息注释代码。终上所述,这些特性为编程抽象的安全重用和软件的类型安全扩展提供了强大的基础。
Scala是可扩展的
在实践中,特定领域应用程序的开发通常需要特定领域的语言扩展。Scala提供了独特的语言机制组合,可以方便地以库的形式顺利地添加新的语言结构。
在许多情况下,这可以在不使用宏等元编程工具的情况下完成。例如:
Scala是可互操作的
Scala旨在与流行的Java运行时环境(JRE)良好地互操作。特别是与主流面向对象的Java编程语言的交互尽可能平滑。较新的Java功能(如SAM,lambda,注解和泛型)在Scala中具有直接类似功能。
那些没有Java类似物的Scala特性,如默认参数和带名参数,尽可能接近Java地进行编译。Scala与Java一样具有相同的编译模型(单独编译,动态类加载),并允许访问数千个现有的高质量库。
基础
在浏览器中尝试Scala
你可以在浏览器中的ScalaFiddle运行Scala。
- 打开 https://scalafiddle.io
- 在左窗格中粘贴
println("Hello, world!")
- 点击“运行”按钮。 输出显示在右窗格中。
这是一个简单的,零设置的方式来测试Scala代码片段。
表达式
表达式是可计算的语句。
1 + 1
你可以使用 println
输出表达式的结果。
println(1) // 1 println(1 + 1) // 2 println("Hello!") // Hello! println("Hello," + " world!") // Hello, world!
值
你可以使用 val
关键字来命名表达式的结果。
val x = 1 + 1println(x) // 2
命名结果,比如这里的 x
,称为值。引用一个值并不会重新计算它。
值不能被重新分配。
val x = 1 + 1x = 3 // This does not compile.
编译器可以推断出类型的值,但是您也可以显式地声明类型,如下所述:
val x: Int = 1 + 1
注意,类型声明 Int
是在标识符 x
之后出现的,并且还需要在两者之间添加一个 :
。
变量
变量就像值,但你可以重新分配它们。你可以使用 var
关键字定义一个变量。
var x = 1 + 1x = 3 // This compiles because "x" is declared with the "var" keyword. println(x * x) // 9
与值一样,你可以显式地声明类型:
var x: Int = 1 + 1
代码块
你可以把周围的表达式结合起来。我们称这个为代码块。
块的最后一个表达式的结果也是整个块的结果。
println({ val x = 1 + 1 x + 1 }) // 3
函数
函数是带参数的表达式。
你可以定义一个匿名函数(也就是没有名称),它返回一个给定的整数+1:
(x: Int) => x + 1
在推出符号 =>
左边是一个参数列表。右边是一个包含参数的表达式。
你也可以命名函数。
val addOne = (x: Int) => x + 1 println(addOne(1)) // 2
函数可以使用多个参数。
val add = (x: Int, y: Int) => x + y println(add(1, 2)) // 3
或者它不需要参数。
val getTheAnswer = () => 42 println(getTheAnswer()) // 42
方法
方法看起来和函数非常相似,但是它们之间有一些关键的区别。
方法使用 def
关键字定义。def
后面是方法名、参数列表、一个返回类型和一个主体。
def add(x: Int, y: Int): Int = x + y println(add(1, 2)) // 3
注意返回类型是在参数列表和冒号 : Int
之后声明的。
方法可以使用多个参数列表。
def addThenMultiply(x: Int, y: Int)(multiplier: Int): Int = (x + y) * multiplier println(addThenMultiply(1, 2)(3)) // 9
或者根本没有参数列表。
def name: String = System.getProperty("name") println("Hello, " + name + "!")
还有一些其他的区别,但是现在,你可以把它们看作是类似于函数的东西。
方法也可以有多行表达式。
def getSquareString(input: Double): String = { val square = input * input square.toString }
body
的最后一个表达式是该方法的返回值。(Scala有一个返回关键字 return
,但很少用到。)
类
你可以使用类关键字 class
定义类,然后使用它的名称和构造函数参数。
class Greeter(prefix: String, suffix: String) { def greet(name: String): Unit = println(prefix + name + suffix) }
方法 greet
的返回类型是 Unit
,它表示返回没有任何意义。它类似于Java和c中的关键字 void
(不同之处在于,因为每个Scala表达式都必须有一定的值,所以实际上有一个类型 Unit
的单例值,写为 ()
。它不携带任何信息。)
你可以使用关键字 new
来创建一个类的实例。
val greeter = new Greeter("Hello, ", "!") greeter.greet("Scala developer") // Hello, Scala developer!
之后我们将详细介绍类。
样例类
Scala有一种特殊类型的类,称为“样例”类。默认情况下,样例类是不可变的,并按值进行比较。
你可以用 case class
关键字定义样例类。
case class Point(x: Int, y: Int)
您可以不需要使用 new
关键字来实例化样例类。
val point = Point(1, 2) val anotherPoint = Point(1, 2) val yetAnotherPoint = Point(2, 2)
它们是按值进行比较的。
if (point == anotherPoint) { println(point + " and " + anotherPoint + " are the same.") } else { println(point + " and " + anotherPoint + " are different.") } // Point(1,2) and Point(1,2) are the same. if (point == yetAnotherPoint) { println(point + " and " + yetAnotherPoint + " are the same.") } else { println(point + " and " + yetAnotherPoint + " are different.") } // Point(1,2) and Point(2,2) are different.
我们想要介绍的样例类还有很多,我们相信你会爱上它们的!之后我们将详细介绍它们。
对象
对象是它们自己定义的单个实例。你可以把他们想象成他们自己的类的单例。
你可以通过使用关键字 object
来定义对象。
object IdFactory { private var counter = 0 def create(): Int = { counter += 1 counter } }
你可以通过引用对象名来访问一个对象。
val newId: Int = IdFactory.create() println(newId) // 1 val newerId: Int = IdFactory.create() println(newerId) // 2
稍后我们将详细介绍对象。
特质
特征是包含某些字段和方法的类型。多个特征可以组合在一起。
你可以通过使用关键字 trait
来定义特质。
trait Greeter { def greet(name: String): Unit }
特征也可以有默认的实现。
trait Greeter { def greet(name: String): Unit = println("Hello, " + name + "!") }
您可以使用 extends
关键字扩展特性,并使用 override
关键字覆盖实现。
class DefaultGreeter extends Greeter class CustomizableGreeter(prefix: String, postfix: String) extends Greeter { override def greet(name: String): Unit = { println(prefix + name + postfix) } } val greeter = new DefaultGreeter() greeter.greet("Scala developer") // Hello, Scala developer! val customGreeter = new CustomizableGreeter("How are you, ", "?") customGreeter.greet("Scala developer") // How are you, Scala developer?
在这里,DefaultGreeter
只扩展了一个单一的特质,但是它可以扩展多个特质。
稍后我们将详细介绍特质。
主方法
主方法是一个程序的入口点。Java虚拟机需要一个命名为 main
的主方法,并接受一个参数,一个字符串数组。
使用对象,您可以定义一个主方法,如下:
object Main { def main(args: Array[String]): Unit = println("Hello, Scala developer!") }
统一类型
在Scala中,所有值都有一个类型,包括数值和函数。下图演示了类型层次结构的一个子集。
Scala类型层次结构
Any
类型是所有类型的父类型,也称为顶级类型。它定义了一些通用的方法,如 equals
、hashCode
和 toString
。Any
有两个直接子类:AnyVal
和 AnyRef
。
AnyVal
代表值类型。有9种预定义的值类型,它们是不可为空的:Double
、Float
、Long
、Int
、Short
、Byte
、Char
、Unit
和 Boolean
。Unit
是一个不具备任何意义的值类型。Unit
只有一个实例可以像这样声明:()
。所有函数都必须返回某个值,因此 Unit
是一个有用的返回类型。
AnyRef
表示引用类型。所有的非值类型都被定义为引用类型。Scala中的每个用户定义类型都是 AnyRef
的子类型。如果Scala是在Java运行时环境中使用的,那么 AnyRef
对应于 Java.lang.object
。
这里有一个例子,它演示了字符串、整数、字符、布尔值和函数都是与其他对象一样的对象:
val list: List[Any] = List( "a string", 732, // an integer 'c', // a character true, // a boolean value () => "an anonymous function returning a string" ) list.foreach(element => println(element))
它定义了类型 List[Any]
的变量列表。这个列表是用各种类型的元素初始化的,但是它们都是 scala.Any
的实例,所以你可以把它们都添加到列表中。
下面是程序的输出:
a string 732 c true <function>
类型转换
值类型可以通过以下方式进行转换:
例如:
val x: Long = 987654321 val y: Float = x // 9.8765434E8 (note that some precision is lost in this case) val face: Char = '☺' val number: Int = face // 9786
转换是单向的。下面最后一条语句将无法通过编译:
val x: Long = 987654321 val y: Float = x // 9.8765434E8 val z: Long = y // Does not conform
你还可以将引用类型转换为子类型。这将在之后的文章中被介绍。
Nothing和Null
Nothing
是所有类型的子类型,也称为底部类型。类型 Nothing
是没有值的。常见的用途是发出非终止信号,例如抛出异常、程序退出或无限循环(即,它是一种没有对值进行求值的表达式,或者是一种不返回正常值的方法)。
Null
是所有引用类型的子类型(即 AnyRef
的任何子类型)。它有一个由关键字 Null
标识的单一值。Null
主要用于与其他JVM语言的互操作性,并且几乎不应该在Scala代码中使用。我们将在稍后的文章中介绍 Null
的替代方案。
包和引入
Scala使用包创建命名空间,允许您对程序进行模块化。
创建package
通过 在Scala 文件的顶部声明一个或多个包名称来创建包。
package users class User
一个约定是将包命名为与包含 Scala 文件的目录相同。但是,Scala 不知道文件的布局。包用户的 sbt 项目的目录结构可能如下所示:
- ExampleProject - build.sbt - project - src - main - scala - users User.scala UserProfile.scala UserPreferences.scala - test
注意 users
目录是如何在 scala
目录中的,以及包中有多个 Scala 文件。包中的每个 Scala 文件都可以具有相同的包声明。声明包的另一种方法是使用大括号:
package users { package administrators { class NormalUser } package normalusers { class NormalUser } }
正如您所看到的,这允许封装嵌套并为范围和封装提供更好的控制。
软件包名称应全部小写,如果代码是在具有网站的组织内开发的,则应采用以下格式约定:<top-level-domain>.<domain-name>.<project-name>
。 例如,如果 Google 有一个名为 SelfDrivingCar
的项目,则包名称如下所示:
package com.google.selfdrivingcar.camera class Lens
这可以对应于以下目录结构:
SelfDrivingCar/src/main/scala/com/google/selfdrivingcar/camera/Lens.scala
引入
import
语句用于访问其他包中的成员(类,特性,函数等)。访问同一包的成员不需要 import
语句。import
语句具有选择性:
import users._ // import everything from the users package import users.User // import the class User import users.{User, UserPreferences} // Only imports selected members import users.{UserPreferences => UPrefs} // import and rename for convenience
Scala与Java不同的一个方法是可以在任何地方使用 import
:
def sqrtplus1(x: Int) = { import scala.math.sqrt sqrt(x) + 1.0 }
如果存在命名冲突,并且需要从项目的根目录导入某些内容,请在 __root__
前加上包名:
package accounts import __root__.users._
注意:默认情况下导入 scala
和 java.lang
包以及 object Predef
。