Simple Scala: Rounding Out the Basics

操作符重载

scala中的所有操作符都是方法. 1 + 2 事实上是 1.+(2),即调用1的”+”方法并传入参数2.并且调用无参方法时可以省略方法名前的”.”操作符,比如 2 toString.

Characters:Scala中允许所有的ASCII字符,包括\u0020到\u007F之间的所有字符.
保留关键字不可以重用:所有保留的关键字均不可重用,字符”_”也是一个保留的关键字.
清晰的标示符:一个清晰的标示符可以以字母或下划线开始,后跟字母,数字或下划线.

无参方法

定义方法时,如果该方法没有参数列表,则可以省略方法名后括号,如果不省略该括号,则调用时即可以带括号也可以不带括号,通常的做法是无参方法均不添加括号,调用时也不添加括号,这样比较统一.

优先规则

scala中的运算优先级:

  1. All letters
  2. |
  3. ^
  4. &
  5. < >
  6. = !
  7. :
    • -
    • / %
  8. All other special characters

特定领域语言

特定领域语言,或者说DSLs,是指针对特殊问题领域的设计语言,其目标是以更加简明直观的方式表达该领域中的概念.比如SQL就可以视为DSL,因为它是编程语言来表达的关系模型的解释.
scala同时支持内嵌式的DSL和需要解析器支持的外部DSL,其灵活的语法规则对中缀和后缀的方法调用语法提供了出色的支持,可以很好的使用scala语法结构编写嵌入式DSL.
这是一个BDD的实例,展示了非常自然的DSL语法:

import org.scalatest.{ FunSpec, ShouldMatchers }
class NerdFinderSpec extends FunSpec with ShouldMatchers {
  describe ("nerd finder") {
    it ("identify nerds from a List") {
      val actors = List("Rick Moranis", "James Dean", "Woody Allen") 
      val finder = new NerdFinder(actors)
      finder.findNerds shouldEqual List("Rick Moranis", "Woody Allen")
    }
  }
}

IF语句

scala中的if语句与java类似提供同样的功能:

if(2+2==5){
  println("Hello from 1984.")
}elseif(2+2==3){
  println("Hello from Remedial Math class?")
}else{
  println("Hello from a non-Orwellian future.")
}

但最大的不同是scala中的语句返回值,因此可以将一段if表达式当做一个值赋给变量:

val configFile = new java.io.File("somefile.txt")

val configFilePath = if (configFile.exists()) { 
  configFile.getAbsolutePath()
}else{ 
  configFile.createNewFile() 
  configFile.getAbsolutePath()
}

满足条件的分支计算后所得的结果将会赋给变量configFilePath,而值的类型会由编译器自动推断为所有分支类型的最接近父类.

FOR表达式

for循环:

val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund",
                        "Scottish Terrier", "Great Dane", "Portuguese Water Dog")
for (breed <- dogBreeds) 
  println(breed)

生成器表达式:
“breed <- dogBreeds”实质上是一个生成器表达式,因为它从一个集合中不停的生成值,”<-“操作符遍历集合中的每个值.

for (i <- 1 to 10) println(i)

过滤器:

for (breed <- dogBreeds
  if !breed.startsWith("Yorkshire")          // if后跟一个boolean守卫,或多个
  if breed.contains("Terrier") && !breed.startsWith("Yorkshire")
) println(breed)

Yielding

除了循环打印集合中的值,或许需要使用另一个集合来处理当前集合中的部分值,使用yield关键字则可以在for表达式中生成新的集合:

val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund",
                    "Scottish Terrier", "Great Dane", "Portuguese Water Dog")
val filteredBreeds = for {
  breed <- dogBreeds
  if breed.contains("Terrier") && !breed.startsWith("Yorkshire")
} yield breed       // 满足if条件的元素会生成一个新的集合赋值给filteredBreeds变量,集合类型与原始集合相同

与循环不同的是,循环时for后面跟括号,而yield时使用分号.

while循环

import java.util.Calendar
def isFridayThirteen(cal: Calendar): Boolean = { 
  val dayOfWeek = cal.get(Calendar.DAY_OF_WEEK) 
  val dayOfMonth = cal.get(Calendar.DAY_OF_MONTH)
  // Scala returns the result of the last expression in a method
  (dayOfWeek == Calendar.FRIDAY) && (dayOfMonth == 13) 
}
while (!isFridayThirteen(Calendar.getInstance())) { 
  println("Today isn't Friday the 13th. Lame.")
  // sleep for a day
  Thread.sleep(86400000)
}

do-while循环

var count = 0
do{
  count += 1 
  println(count)
} while (count < 10)

条件操作符

  1. “&&”
  2. “||”
  3. “>”
  4. “>=”
  5. “<”
  6. “<=”
  7. “==”
  8. “!=”
    “&&”和”||”是短路操作符,一旦达到条件就会停止运算.

异常处理

scala鼓励通过使用功能构造和强类型来减少异常处理的出现,但仍然不能避免,特别是与java代码交接的地方.与java不同,scala中没有受检异常.

object TryCatch {
    /** Usage: scala rounding.TryCatch filename1 filename2 ... */ 
    def main(args: Array[String]) = {
        args foreach (arg => countLines(arg)) 
    }
    import scala.io.Source // 
    import scala.util.control.NonFatal
    def countLines(fileName: String) = { 
        println()
        var source: Option[Source] = None
        try{
            source = Some(Source.fromFile(fileName)) 
            val size = source.get.getLines.size
            println(s"file $fileName has $size lines")
        } catch {
            case NonFatal(ex) => println(s"Non fatal exception! $ex")
        } finally {
            for (s <- source) {
            println(s"Closing $fileName...")
            s.close 
            }
        } 
    }
}

lazy val

懒值,推迟对值的加载,只有在第一次调用时才会进行计算,必须是不可变类型,常用场景:

  • 表达式消耗比较高,比如打开一次数据库连接.
  • 推迟一些并不是立即需要的工作的启动时间.
  • 有时对象中的字段需要计算才能得到,只有在需要的时候再去计算.

    class Book(name:String){

    println("new book"+name)
    override def toString() = "《"+name+"》"
    

    }

    lazy val b = new Book(“Java”)
    println(“Test”)
    println(b.toString)

    res:
    Test // 首先打印Test而没有初始化b
    new bookJava
    《Java》

枚举

scala中的枚举实现与java完全不同:

object Breed extends Enumeration {
    type Breed = Value
    val doberman = Value("Doberman Pinscher") 
    val yorkie = Value("Yorkshire Terrier") 
    val scottie = Value("Scottish Terrier") 
    val dane = Value("Great Dane")
    val portie = Value("Portuguese Water Dog")
}
import Breed._

// Usage
// print a list of breeds and their IDs
println("ID\tBreed")
for (breed <- Breed.values) println(s"${breed.id}\t$breed")

// print a list of Terrier breeds
println("\nJust Terriers:")
Breed.values filter (_.toString.endsWith("Terrier")) foreach println

def isTerrier(b: Breed) = b.toString.endsWith("Terrier") 

println("\nTerriers Again??")
Breed.values filter isTerrier foreach println

另一个实例:

object WeekDay extends Enumeration {
    type WeekDay = Value
    val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
}
import WeekDay._
def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)
WeekDay.values filter isWorkingDay foreach println

格式化字符串

val name = "Buck Trends"
println(s"Hello, $name")

valgross =100000F
val net = 64000F
val percent = (net / gross) * 100
println(f"$$${gross}%.2f vs. $$${net}%.2f or ${percent}%.1f%%")
res0: $100000.00 vs. $64000.00 or 64.0%

scala> f"${i}%.2f" 
res4: String = 200.00

scala> val s = "%02d: name = %s".format(5, "Dean Wampler") 
s: String = "05: name = Dean Wampler"

特质:接口和混入

java中的接口允许声明方法但不允许定义方法,scala中使用trait代替了java中的接口.
我们创建一个服务端然后混入日志类:

class ServiceImportante(name: String) { 
    def work(i: Int): Int = {
        println(s"ServiceImportante: Doing important work! $i")
        i+1 
    }
}
val service1 = new ServiceImportante("uno")
(1 to 3) foreach (i => println(s"Result: ${service1.work(i)}"))

输出:

ServiceImportante: Doing important work! 1
Result: 2
ServiceImportante: Doing important work! 2
Result: 3
ServiceImportante: Doing important work! 3
Result: 4

然后如何混入一个日志类呢,这里会简单的使用println方法.下面有两个特质,一个是抽象特质,没有具体的成员,另一个特质实现了当一个抽象特质中的方法用于输出日志:

trait Logging {
definfo (message:String):Unit 
def warning(message: String): Unit 
def error (message: String): Unit
}
trait StdoutLogging extends Logging {
def info (message: String) = println(s"INFO: $message") 
def warning(message: String) = println(s"WARNING: $message") 
def error (message: String) = println(s"ERROR: $message")
}

Logging的代码与java中的interface实现基本一致.最后声明一个server实例并混入日志类:

val service2 = new ServiceImportante("dos") with StdoutLogging { 
    override def work(i: Int): Int = {
        info(s"Starting work: i = $i")
        val result = super.work(i)
        info(s"Ending work: i = $i, result = $result") result
    } 
}
(1 to 3) foreach (i => println(s"Result: ${service2.work(i)}"))

使用”with”关键字混入StdoutLogging特质,然后就可以使用StdoutLogging特质中定义的所有方法.当然也可以在定义server类时直接混入,而不是在创建server实例时再混入.