Scala基础(一)

这个章节的内容包含

关于这个教程

在最初的几个星期里,我们会讲解Scala的基本的语法和概念。然后我们会通过更多的练习来展示这些概念。有些例子是通过解释器里的几行代码来展示的,有些则是通过源码的形式进行展示。

你最好安装一个scala解释器,它可以方便你探索问题。

为什么用Scala?

  • 富有表现力
    • 一等函数
    • 闭包
  • 简洁
    • 类型接口
    • 支持函数创建的语法
  • 可以和Java进行互操作
    • 可以复用Java的类库
    • 可以复用Java的工具
    • 没有额外的性能问题

Scala 怎么运行?

  • Scala代码会编译成Java 字节码
  • 可以很好的运行在任何标准的JVM上
    • 甚至可以运行在非标准的JVM上,例如Dalvik
    • Scala的编译器是由Java编译器的作者编写的

按照Scala的方式思考

Scala 不仅仅是Java的改善。你需要用一个全新的思维方式来学习它,你会从这些课程中学到更多。

启动解释器

启动内置的sbt console

$ sbt conole

[...]

Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.

scala>

表达式

scala> 1 + 1
res0: Int = 2

res0是由解释器自动给你的表达式的结果生成的变量名。它的类型是Int,值是2。 在Scala里(几乎)所有的东西都是一个表达式。

你可以给表达式的结果设定一个名字。

scala> val two = 1 + 1
two: Int = 2

你不可以改变val变量的绑定。

变量

如果你需要改变绑定,你可以使用var来代替。

scala> var name = "steve"
name: java.lang.String = steve

scala> name = "marius"
name: java.lang.String = marius

函数

你可以用def来创建函数

scala> def addOne(m: Int): Int = m + 1
addOne: (m: Int)Int

在Scala里,你需要给函数的参数指定类型签名。解释器会回显你指定的类型签名。

scala> val three = addOne(2)
three: Int = 3

对于没有参数的函数,在调用的时候可以省略掉括号

scala> def three() = 1 + 2
three: ()Int

scala> three()
res2: Int = 3

scala> three
res3: Int = 3

匿名函数

你可以通过下面的方式创建匿名函数

scala> (x: Int) => x + 1
res2: (Int) => Int =

这个函数给一个Int型的整数加一。

scala> res2(1)
res3: Int = 2

你可以把匿名函数作为参数进行传递,或者把它们保存在val中。

scala> val addOne = (x: Int) => x + 1
addOne: (Int) => Int = 

scala> addOne(1)
res4: Int = 2

如果你的函数是由多个表达式组成的,你可以把它们写在一对{}里。

def timesTwo(i: Int): Int = {
  println("hello world")
  i * 2
}

也可以通过下面的方式来创建一个匿名函数

scala> { i: Int =>
  println("hello world")
  i * 2
}
res0: (Int) => Int =

在把匿名函数作为一个参数进行传递时,你会经常看到这种语法。

部分应用(偏函数)

你可以使用下划线来部分应用一个函数,这样就可以产生一个新的函数。Scala用下划线来表示在不同上下文下的不同内容,不过你可以认为它是一个没有名字的神奇通配符。在{ _ + 2}这样的上下文里,它表示一个没有名字的参数。你也可以这样来使用它:

scala> def adder(m: Int, n: Int) = m + n
adder: (m: Int,n: Int)Int
scala> val add2 = adder(2, _:Int)
add2: (Int) => Int = 

scala> add2(3)
res50: Int = 5

你可以部分应用参数列表里的任何参数,而并非只是最后一个。

Curried functions

有时候,使用者会先给你的函数传入部分参数,后面再传入其他的参数,这样做是很有用的。

下面的例子中,你可以对两个数进行乘法运算。在第一次调用时,你可以确定哪个是乘数,在第二次调用时,再确定被乘数。

scala> def multiply(m: Int)(n: Int): Int = m * n
multiply: (m: Int)(n: Int)Int

你可以直接把两个参数都传入。

scala> multiply(2)(3)
res0: Int = 6

或者,你也可以先传入第一个参数,然后部分应用第二个参数。

scala> val timesTwo = multiply(2) _
timesTwo: (Int) => Int = 

scala> timesTwo(3)
res1: Int = 6

你可以把任何有多个参数的函数当作curried function来使用。我们来用之前的adder做个示范。

scala> (adder _).curried
res1: (Int) => (Int) => Int =

可变长度的参数

对于方法,还有一个特别的语法用来接收多个相同类型的参数。对于一组字符串,要对它们应用String的capitalize函数,你可以这样写:

def capitalizeAll(args: String*) = {
  args.map { arg =>
    arg.capitalize
  }
}

scala> capitalizeAll("rarity", "applejack")
res2: Seq[String] = ArrayBuffer(Rarity, Applejack)

scala> class Calculator {
     |   val brand: String = "HP"
     |   def add(m: Int, n: Int): Int = m + n
     | }
defined class Calculator

scala> val calc = new Calculator
calc: Calculator = Calculator@e75a11

scala> calc.add(1, 2)
res1: Int = 3

scala> calc.brand
res2: String = "HP"

上面的例子中,展示了通过def定义方法以及使用val定义成员变量。方法也是函数,只不过它可以访问类的状态。

Constructor

Constructor并不是特殊的方法,它们是当前类外部的一段代码。我们来扩展一下Calculator的例子,使得它能够接受一个参数,并且使用这个参数来初始化类的内部状态。

class Calculator(brand: String) {
  /*
   * A constructor.
   */
  val color: String = if (brand == "TI") {
    "blue"
  } else if (brand == "HP") {
    "black"
  } else {
    "white"
  }

  // An instance method.
  def add(m: Int, n: Int): Int = m + n
}

注意两种不同方式的注释。

你可以使用这个constructor来构造一个实例:

scala> val calc = new Calculator("HP")
calc: Calculator = Calculator@1e64cc4d

scala> calc.color
res0: String = black

表达式

这个BasicCalculator例子向我们展示了Scala是如何面向表达式的。变量color的值取决于一个if/else表达式的结果。Scala是高度面向表达式的:大部分的语句都是表达式,而非声明。

题外话:函数 vs 方法

函数和方法在很大的程度上是可以相互替代的。因为函数和方法是如此的相似,以至于你可能无法分清你调用的对象是一个方法还是一个函数。当你考虑函数和方法的区别时,你就开始混乱了。

scala> class C {
     |   var acc = 0
     |   def minc = { acc += 1 }
     |   val finc = { () => acc += 1 }
     | }
defined class C

scala> val c = new C
c: C = C@1af1bd6

scala> c.minc // calls c.minc()

scala> c.finc // returns the function as a value:
res2: () => Unit =

 

当你调用一个“函数”但是没有带上括号但实际上没效果时,你也许会想噢,我想我明白Scala的函数是怎么起作用的,但是我认为也许不是这样的。或许有时候是需要括号的?或许你对函数比较了解,但是实际上却在使用一个方法。

实际上,就算你在区别函数和方法的问题上犯迷糊,你还是可以用Scala来做一些很出色的事情。如果你是一个Scala新手,并且你在google上阅读它们的区别,你可能会不是很明白。但是这并不表示你会用不好Scala。它只能说明函数和方法的区别是比较微妙的,如果要解释的话,那么就需要对语言进行深入了解。

基本继承

class ScientificCalculator(brand: String) extends Calculator(brand) {
  def log(m: Double, base: Double) = math.log(m) / math.log(base)
}

参考 《Effective Scala》中指出,如果子类和父类并没有什么大的区别的话,类型别名(Type alias)extends更好。《A Tour of Scala》中讲述了子类化(Subclassing)的技术。

重载方法

class EvenMoreScientificCalculator(brand: String) extends ScientificCalculator(brand) {
  def log(m: Int): Double = log(m, math.exp(1))
}

抽象类

你可以定义一个抽象类,抽象类可以定义一些方法,但是不需要实现它们。相反,继承抽象类的子类需要实现这些方法。抽象类是不能被实例化的。

scala> abstract class Shape {
     |   def getArea():Int    // subclass should define this
     | }
defined class Shape

scala> class Circle(r: Int) extends Shape {
     |   def getArea():Int = { r * r * 3 }
     | }
defined class Circle

scala> val s = new Shape
:8: error: class Shape is abstract; cannot be instantiated
       val s = new Shape
               ^

scala> val c = new Circle(2)
c: Circle = Circle@65c0035b

Traits

traints表示一系列可以扩展或者混入到你的类里的成员和行为。

trait Car {
  val brand: String
}

trait Shiny {
  val shineRefraction: Int
}
class BMW extends Car {
  val brand = "BMW"
}

 

通过with关键字,一个类可以扩展多个traint:

class BMW extends Car with Shiny {
  val brand = "BMW"
  val shineRefraction = 12
}

参考 《Effective Scala》中关于 trait的观点.

什么时候你需要使用Trait代替抽象类? 如果你想定义一个类似接口的类型,那么你很难在trait和抽象类之间做出选择。它们都可以让你定义一个具有某些行为的类型,然后要求扩展者去实习其他的行为。下面是一些经验法则:

  • 优先使用traint。一个类可以扩展多个traint,但是只能扩展一个抽象类。
  • 如果你需要在构造类的时候传入参数的话,那就是用抽象类。抽象类的构造器可以传入参数,trait则不能。例如,你不能这样写trait t(i:Int) {} ;参数i是非法的。

你并不是第一个问这个问题的人。你可以在《Scala traits vs abstract classes》《Difference between Abstract Class and Trait》, 和《Programming in Scala: To trait, or not to trait?》查看更加完整的解释。

类型

在前面的例子里,你见过我们定义了一个函数,它接收一个Int参数,而Int的类型是Number。函数也可以是泛型的,并且可以作用于任何类型。当你遇到这种情况时,你会看到类型参数是放在方括号里的。下面是一个支持泛型键-值的Cache示例。

trait Cache[K, V] {
  def get(key: K): V
  def put(key: K, value: V)
  def delete(key: K)
}

方法也可以引入类型参数。

def remove[K](key: K)

英文原文:Scala School,翻译:ImportNew - 朱伟杰

译文原文:http://www.importnew.com/3240.html

【如需转载,请在正文中标注并保留原文链接、译文链接和译者等信息,谢谢合作!】

关于作者: 朱伟杰

Java开发工程师,业余翻译

查看朱伟杰的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

1 条评论

  1. omytea 说道:

    最后一段翻译错误:

    而Int的类型是Number

    原文是that took an Int which is a type of Number

    应该为 定义了一个函数 接收数字类型Int作为参数

    Thumb up 0 Thumb down 0

跳到底部
返回顶部