scala introduce

这篇文档给予了scala语言及其编译器的快速介绍。假设阅读者具有面向对象编程,尤其是在java上的经验。

使用hello world作为第一个例子
object HelloWorld {
  def main(args: Array[String]) {
    println("Hello, world!")
  }
}


Java程序员应该熟悉这个程序的结构:它包含一个名为main的方法,它接受命令行参数,一个字符串数组作为参数; 
此方法的主体包含对预定义方法println的单个调用,其中友好问候语作为参数。
 main方法不返回值(它是一个过程方法)。 因此,没有必要声明返回类型。

Java程序员不太熟悉的是包含main方法的object声明。 这样的声明引入了通常所说的单例对象,即具有单个实例的类。
 因此,上面的声明声明了一个名为HelloWorld的类和该类的实例,也称为HelloWorld。 该实例是在第一次使用时按需创建的。

精明的读者可能已经注意到main方法在这里没有声明为静态。 这是因为Scala中不存在静态成员(方法或字段)。
 Scala程序员不是定义静态成员,而是在单例对象中声明这些成员。

编译这个示例
为了编译示例,我们使用Scalac,即Scala编译器。 scalac与大多数编译器一样工作:它将源文件作为参数,可能是一些选项,并生成一个或多个目标文件。 
它生成的目标文件是标准的Java类文件。
如果我们将上述程序保存在一个名为HelloWorld.scala的文件中,我们可以通过发出以下命令来编译它(大于号>代表shell提示符,不应该键入):
> scalac HelloWorld.scala

这将在当前目录中生成一些类文件。 其中一个将被称为HelloWorld.class,并包含一个可以使用scala命令直接执行的类,如下节所示。

运行这个示例
编译完成后,可以使用scala命令运行Scala程序。 它的用法与用于运行Java程序的java命令非常相似,并且接受相同的选项。
 上面的例子可以使用以下命令执行,该命令产生预期的输出:
scala -classpath . HelloWorld

Hello, world!

与Java的整合
Scala的优势之一是它可以很容易地与Java代码进行交互。 默认情况下会导入java.lang包中的所有类,而其他类需要显式导入。
让我们看一个证明这一点的例子。 我们希望根据特定国家/地区使用的惯例获取并格式化当前日期,例如法国。 (瑞士法语区等其他地区使用相同的惯例。)
Java的类库定义了强大的实用程序类,例如Date和DateFormat。 由于Scala与Java无缝地互操作,因此不需要在Scala类库中实现等效类 - 我们可以简单地导入相应Java包的类:
import java.util.{Date, Locale}
import java.text.DateFormat
import java.text.DateFormat._

object FrenchDate {
  def main(args: Array[String]) {
    val now = new Date
    val df = getDateInstance(LONG, Locale.FRANCE)
    println(df format now)
  }
}

Scala的import语句看起来与Java相当,但它更强大。 可以从同一个包中导入多个类,方法是将它们用大括号括起来,就像在第一行一样。
 另一个区别是,在导入包或类的所有名称时,使用下划线字符(_)而不是星号(*)。 
这是因为星号是有效的Scala标识符(例如方法名称),我们稍后会看到。

因此,第三行上的import语句将导入DateFormat类的所有成员。 这使静态方法getDateInstance和静态字段LONG直接可见。

在main方法中,我们首先创建一个Java的Date类实例,它默认包含当前日期。 接下来,我们使用之前导入的静态getDateInstance方法定义日期格式。
 最后,我们打印根据本地化的DateFormat实例格式化的当前日期。 最后一行显示了Scala语法的一个有趣属性。 
采用一个参数的方法可以与中缀语法一起使用。 也就是说,表达式
df format now
可以当作这个略显冗长的表达式
df.format(now)
这似乎是一个较小的句法细节,但它有重要的后果,其中一个将在下一节中探讨。

在结束本节关于与Java集成的部分时,应该注意的是,也可以从Java类继承并直接在Scala中实现Java接口。

一切都是对象
Scala是一种纯粹的面向对象语言,因为一切都是对象,包括数字或函数
 它在这方面与Java不同,因为Java将原始类型(例如boolean和int)与引用类型区分开来,并且不允许将函数作为值来操作。

Number是对象
由于数字是对象,因此它们也有方法。 事实上,算术表达式如下:
1 + 2 * 3 / x
由方法调用组成,因为它等同于下面的表达式,正如我们在上一节中看到的那样:
(1).+(((2).*(3))./(x))
这也意味着+,*等是Scala中的有效标识符。

第二个版本中数字的括号是必要的,因为Scala的词法分析器使用最长的匹配规则作为标记。 因此,它会破坏以下表达式:
1.+(2)
选择此标记化的原因是因为1.是比1更长的有效匹配。标记1.被解释为文字1.0,使其成为Double而不是Int。 将表达式写为:
(1).+(2)
防止1被解释为Double。

函数是对象
也许Java程序员更令人惊讶,函数也是Scala中的对象。 因此,可以将函数作为参数传递,将它们存储在变量中,并从其他函数返回它们。 
这种将函数作为值进行操作的能力是一种非常有趣的编程范式(称为函数式编程)的基石之一。

作为将函数用作值的有用原因的一个非常简单的例子,让我们考虑一个计时器函数,其目的是每秒执行一些操作。 
我们如何将动作传递给它? 在逻辑上,作为一种功能。 这种非常简单的函数传递应该为许多程序员所熟悉:
它通常用在用户界面代码中,用于注册在某些事件发生时调用的回调函数。

在下面的程序中,timer对象的oncePerSecond函数,它使用一个回调函数作为参数。 
这个函数的类型是写的()=>unit,是所有函数的类型,它们不带参数并且什么都不返回(类型unit类似于C / C ++中的void)。 
该程序的主要功能是通过回调调用此定时器功能,该回调在终端上打印一个句子。 
换句话说,这个程序每秒钟无休止地打印句子“时间飞得像箭头”。

object Timer {
  def oncePerSecond(callback: () => Unit) {
    while (true) { callback(); Thread sleep 1000 }
  }
  def timeFlies() {
    println("time flies like an arrow...")
  }
  def main(args: Array[String]) {
    oncePerSecond(timeFlies)
  }
}

请注意,为了打印字符串,我们使用预定义的方法println而不是使用System.out中的方法。

匿名函数
虽然这个程序很容易理解,但可以稍微改进一下。 首先,请注意函数timeFlies仅定义为稍后传递给oncePerSecond函数。 
必须将该函数命名,但该函数仅使用一次,这似乎是不必要的,如果能够在传递给oncePerSecond时再去构造此函数实际上是很好的。
 这在Scala中可以使用匿名函数,这正是:没有名称的函数。 
使用匿名函数而不是timeFlies,我们的计时器程序的修订版看起来像这样:
object TimerAnonymous {
  def oncePerSecond(callback: () => Unit) {
    while (true) { callback(); Thread sleep 1000 }
  }
  def main(args: Array[String]) {
    oncePerSecond(() =>
      println("time flies like an arrow..."))
  }
}

右箭头=>显示了此示例中匿名函数的存在,它将函数的参数列表与其正文分开
在此示例中,参数列表为空,如箭头左侧的空对括号所示。 函数体与上面的timeFlies相同。


正如我们上面所看到的,Scala是一种面向对象的语言,因此它具有类的概念。 
(为了完整起见,应该注意一些面向对象的语言不具有类的概念,但Scala不是其中之一。)
Scala中的类是使用接近Java语法的语法声明的。 一个重要的区别是Scala中的类可以有参数。 这在复数的以下定义中说明。

class Complex(real: Double, imaginary: Double) {
  def re() = real
  def im() = imaginary
}

这个Complex类有两个参数-real和imaginary,它们代表复数的实数和虚数部分。
创建类Complex的实例时必须传递这些参数,如下所示:new Complex(1.5,2.3)。
 该类包含两个方法,称为re和im,它们可以访问这两个部分。

应该注意,这两种方法的返回类型没有明确给出。 
它将由编译器自动推断,编译器查看这些方法的右侧并推断它们都返回Double类型的值。

编译器并不总是像它在这里那样推断类型,并且遗憾的是没有简单的规则来确切知道它何时会发生,何时不会。
 在实践中,这通常不是问题,因为编译器在无法推断未明确给出的类型时会抱怨。 
作为一个简单的规则,初学者Scala程序员应该尝试省略类似的声明,这些声明似乎很容易从上下文中推断出来,看看编译器是否同意。 
一段时间后,程序员应该很好地了解何时省略类型,何时明确指定它们。

没有参数的方法
方法re和im的一个小问题是,为了调用它们,必须在它们的名称后面加上一对空括号,如下例所示:
object ComplexNumbers {
  def main(args: Array[String]) {
    val c = new Complex(1.2, 3.4)
    println("imaginary part: " + c.im())
  }
}

如果它们是字段,那么能够访问实数和虚数部分会更好,而不需要放置空的括号对。
 这在Scala中是完全可行的,只需将它们定义为没有参数的方法即可。 
这些方法与零参数的方法不同,因为它们的名称后面没有括号,无论是在定义中还是在它们的使用中。 
我们的Complex类可以重写如下:
class Complex(real: Double, imaginary: Double) {
  def re = real
  def im = imaginary
}

继承和重写
Scala中的所有类都继承自超类。 如果未指定超类,则在上一节的Complex示例中,隐式使用scala.AnyRef

可以覆盖从Scala中的超类继承的方法。 但是,必须明确指定方法使用override修饰符覆盖另一个方法,以避免意外覆盖。
 作为示例,我们的Complex类可以通过重新定义从Object继承的toString方法来扩充。
class Complex(real: Double, imaginary: Double) {
  def re = real
  def im = imaginary
  override def toString() =
    "" + re + (if (im < 0) "" else "+") + im + "i"
}

case类和模式匹配
通常出现在程序中的一种数据结构是树。 例如,解释器和编译器通常将程序内部表示为树; XML文档是树;
 几种容器都是以树木为基础,如红黑树。

我们现在将研究如何通过一个小型计算器程序在Scala中表示和操作这些树。 
该程序的目的是操纵由和,整数常量和变量组成的非常简单的算术表达式。 
这种表达式的两个例子是1 + 2和(x + x)+(7 + y)。

我们首先必须决定这种表达的表示。
 最自然的是树,其中节点是操作(这里是添加),叶子是值(这里是常量或变量)。

在Java中,这样的树将使用树的抽象超类来表示,并且每个节点或叶使用一个具体的子类。 
在函数式编程语言中,可以使用代数数据类型来实现相同的目的。 
Scala提供了case类的概念,它们介于两者之间。 以下是它们如何用于为我们的示例定义树的类型:
abstract class Tree
case class Sum(l: Tree, r: Tree) extends Tree
case class Var(n: String) extends Tree
case class Const(v: Int) extends Tree

类Sum,Var和Const被声明为case类的事实意味着它们在几个方面与标准类不同:
new关键字不是创建这些类的实例必须的(即,可以编写Const(5)而不是new Const(5)),

为构造函数参数自动定义getter函数(即,可以通过编写c.v来获取类Const的某个实例c的v构造函数参数的值),

提供了equals和hashCode方法的默认定义,它们对实例的结构起作用,而不是对它们的身份,

提供了方法toString的默认定义,并以“源表单”打印该值(例如,表达式x + 1的树打印为Sum(Var(x),Const(1))),

这些类的实例可以通过模式匹配进行分解,如下所示。

现在我们已经定义了数据类型来表示我们的算术表达式,我们可以开始定义操作来操作它们。 
我们将从一个函数开始,在某些环境中评估表达式。 环境的目的是为变量赋值。 
例如,在将值5与变量x相关联的环境中评估表达式x + 1,写为{x - > 5},结果为6。

因此,我们必须找到一种表示环境的方法。 我们当然可以使用一些像哈希表这样的关联数据结构,
但我们也可以直接使用函数! 环境实际上只是一个将值与(变量)名称相关联的函数。 
上面给出的环境{x - > 5}可以简单地在Scala中编写如下:
{ case "x" => 5 }

此表示法定义了一个函数,当给定字符串“x”作为参数时,返回整数5,否则将失败。

在编写评估函数之前,让我们给出环境类型的名称。 
我们当然可以在环境中使用String => Int类型,但如果我们为这种类型引入一个名称,它会简化程序,并使未来的更改更容易。 
这是在Scala中使用以下符号完成的:
type Environment = String => Int

从那时起,类型Environment可以用作从String到Int的函数类型的别名。

我们现在可以给出评估函数的定义。 从概念上讲,它非常简单:两个表达式之和的值只是这些表达式的值的总和; 
变量的值直接从环境中获得; 而常数的值本身就是常数。 在Scala中表达这一点并不困难:
def eval(t: Tree, env: Environment): Int = t match {
  case Sum(l, r) => eval(l, env) + eval(r, env)
  case Var(n)    => env(n)
  case Const(v)  => v
}

该评估函数通过在树t上执行模式匹配来工作。 直观地说,上述定义的含义应该清楚:

它首先检查树t是否为Sum,如果是,则将左子树绑定到名为l的新变量,将右子树绑定到名为r的变量,然后继续评估表达式 按照箭头; 
这个表达式可以(并且确实)使用由箭头左边出现的模式绑定的变量,即l和r,

Traits
除了从超类继承代码之外,Scala类还可以从一个或多个特征导入代码。

也许Java程序员理解特征的最简单方法是将它们视为可以包含代码的接口。 在
Scala中,当一个类继承自trait时,它实现了该trait的接口,并继承了trait中包含的所有代码。

为了看到特征的有用性,让我们看一个经典的例子:有序对象。
 它能够比较给定类之间的对象,从而能对它们进行排序。 在Java中,可比较的对象实现了Comparable接口。 
在Scala中,我们可以通过将Comparable的等价物定义为特征来比Java更好一些,我们称之为Ord。

比较对象时,六个不同的谓词可能很有用:更小,更小或相等,相等,不相等,相等或更大,以及更大。
 然而,定义所有这些都是挑剔的,特别是因为这六个中的四个可以使用剩下的两个来表达。
 也就是说,给定相等和较小的谓词(例如),可以表达其他谓词。 
在Scala中,以下Trait声明可以很好地捕获所有这些观察结果:
trait Ord {
  def < (that: Any): Boolean
  def <=(that: Any): Boolean =  (this < that) || (this == that)
  def > (that: Any): Boolean = !(this <= that)
  def >=(that: Any): Boolean = !(this < that)
}

这个定义都创建了一个名为Ord的新类型,它与Java的Comparable接口扮演相同的角色。 
平等和不平等的谓词不会出现在此处,因为它们默认存在于所有对象中。

上面使用的Any类型是Scala中所有其他类型的超类型。 它可以看作是Java的Object类型的更通用版本,因为它也是Int,Float等基本类型的超类型。

为了使类的对象具有可比性,因此足以定义测试相等性和劣等性的谓词,并在上面的Ord类中进行混合。
 例如,让我们定义一个表示公历中日期的Date类。 
这些日期由一天,一个月和一年组成,我们都将整数表示为整数。 因此,我们开始定义Date类,如下所示:
class Date(y: Int, m: Int, d: Int) extends Ord {
  def year = y
  def month = m
  def day = d
  override def toString(): String = year + "-" + month + "-" + day

这里的重要部分是扩展Ord声明,它遵循类名和参数。 它声明Date类继承自Ord Trait。
然后,我们重新定义从Object继承的equals方法,以便通过比较各个字段来正确地比较日期。
 equals的默认实现是不可用的,因为在Java中它会物理地比较对象。 我们得出以下定义:
override def equals(that: Any): Boolean =
  that.isInstanceOf[Date] && {
    val o = that.asInstanceOf[Date]
    o.day == day && o.month == month && o.year == year
  }

此方法使用预定义方法isInstanceOf和asInstanceOf。 
第一个isInstanceOf对应于Java的instanceof运算符,当且仅当应用它的对象是给定类型的实例时才返回true。 
第二个,asInstanceOf,对应于Java的强制转换操作符:如果对象是给定类型的实例,则将其视为此类,否则抛出ClassCastException。

最后,定义的最后一个方法是测试劣势的谓词,如下所示。 它使用另一个方法,来自包对象scala.sys的error,它会抛出给定错误消息的异常。

def <(that: Any): Boolean = {
  if (!that.isInstanceOf[Date])
    sys.error("cannot compare " + that + " and a Date")

  val o = that.asInstanceOf[Date]
  (year < o.year) ||
  (year == o.year && (month < o.month ||
                     (month == o.month && day < o.day)))
}
这样就完成了Date类的定义。 可以将此类的实例视为日期或类似对象。 
而且,它们都定义了上面提到的六个比较谓词:equals和<因为它们直接出现在Date类的定义中,而其他因为它们是从Ord Trait继承的。


泛型
我们将在本教程中探讨的Scala的最后一个特性是泛型。 Java程序员应该清楚地知道他们的语言缺乏泛型所带来的问题,这是Java 1.5中解决的一个缺点。
泛型是编写按类型参数化的代码的能力。 例如,为链表编写库的程序员面临着决定给列表元素赋予哪种类型的问题。 
由于此列表旨在用于许多不同的上下文中,因此无法确定元素的类型必须是Int。 这将完全是武断的,而且过于严格。

Java程序员诉诸于使用Object,它是所有对象的超类型。 
然而,这个解决方案远非理想,因为它不适用于基本类型(int,long,float等),并且它意味着程序员必须插入许多动态类型转换。

Scala可以定义泛型类(和方法)来解决这个问题。 
让我们用一个最简单的容器类的例子来检查这个:引用,它可以是空的,也可以指向某种类型的对象。
class Reference[T] {
  private var contents: T = _
  def set(value: T) { contents = value }
  def get: T = contents
}

类Reference由一个名为T的类型参数化,该类型是其元素的类型。 
此类型在类的主体中用作contents变量的类型,set方法的参数以及get方法的返回类型。

上面的代码示例在Scala中引入了变量,不需要进一步解释。 然而,有趣的是,给该变量的初始值是_,它表示默认值。 
对于数字类型,此默认值为0,对于布尔类型,为false;对于Unit类型,为(),对于所有对象类型,为null。

要使用此Reference类,需要指定要用于参数T的类型,例如下面代码:
object IntegerReference {
  def main(args: Array[String]) {
    val cell = new Reference[Int]
    cell.set(13)
    println("Reference contains the half of " + (cell.get * 2))
  }
}
从该示例中可以看出,在将其用作整数之前,不必转换get方法返回的值。 也不可能在该cell中存储除整数之外的任何内容,因为它被声明为包含整数。

本文档简要概述了Scala语言并提供了一些基本示例。 感兴趣的读者可以继续,例如,阅读文档Scala By Example,其中包含更多高级示例,并在需要时参考Scala语言规范。


译自:https://docs.scala-lang.org/tutorials/scala-for-java-programmers.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值