文章目录
  1. 1. 一些特性
    1. 1.1. 的使用
    2. 1.2. bean方法
    3. 1.3. object 的工厂方法
    4. 1.4. package object
    5. 1.5. trait
    6. 1.6. case class
    7. 1.7. copy constructor
    8. 1.8. abstract override
    9. 1.9. sealed
  2. 2. Value Class
  3. 3. implicit
  4. 4. DEMO: Mongo
    1. 4.1. 1
    2. 4.2. 2
    3. 4.3. 3

一些特性

_root_的使用

1
2
3
4
5
6
7
8
9
10
11
package monads { class IOMonad }
package io {
  package monads {
    class Console { val m = new _root_.monads.IOMonad }
  }
}

import java.util.Date
import java.sql.{Date => SqlDate}
// 也可以这样
import java.sql.{Date => _ }

bean方法

1
2
3
4
5
6
// 如果在构造函数中没有定义 var 或者 val, 将会是似有的
class Person(var firstName:String, var lastName:String,
        private var _age:Int) {
  def age = _age
  def age_=(newAge: Int) = _age = newAge
}

object 的工厂方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Role { def canAccess(page: String): Boolean }
class Root extends Role {
  override def canAccess(page:String) = true
}
class SuperAnalyst extends Role {
  override def canAccess(page:String) = page != "Admin"
}
class Analyst extends Role {
  override def canAccess(page:String) = false
}
object Role {
  def apply(roleName:String) = roleName match {
    case "root" => new Root
    case "superAnalyst" => new SuperAnalyst
    case "analyst" => new Analyst
  }
}

package object

1
2
3
4
5
6
7
8
9
10
11
12
13
// 一般放在 package.scala文件下
// minimumAge and verifyAge will be available to
// all members of the package bar
package object bar {
  val minimumAge = 18
  def verifyAge = {}
}

// 用法如下
package bar
class BarTender {
  def serveDrinks = { verifyAge; ... }
}

trait

In OOP languages, a mixin is a class that provides certain functionality that could be used by other classes.

Another difference between traits and abstract classes in Scala is that an abstract class can have constructor parameters, but traits can’t take any param- eters. Both can take type parameters.

The difference between def and val is that val gets evaluated when an object is created, but def is evaluated every time a method is called.

case class

when you prefix a class with case, the following things will hap- pen automatically:

  • Scala prefixes all the parameters with val, and that will make them public value. But remember that you still never access the value directly; you always access through accessors.
  • Both equals and hashCode are implemented for you based on the given parameters.
  • The compiler implements the toString method that returns the class name and its parameters.
  • Every case class has a method named copy that allows you to easily create a mod- ified copy of the class’s instance. You’ll learn about this later in this chapter.
  • A companion object is created with the appropriate apply method, which takes the same arguments as declared in the class.
  • The compiler adds a method called unapply, which allows the class name to be used as an extractor for pattern matching (more on this later).
  • A default implementation is provided for serialization:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    scala> val me = Person("Nilanjan", "Raychaudhuri")
    me: Person = Person(Nilanjan,Raychaudhuri)
    
    scala> val myself = Person("Nilanjan", "Raychaudhuri")
    myself: Person = Person(Nilanjan,Raychaudhuri)
    
    scala> me.equals(myself)
    res1: Boolean = true
    
    scala> me.hashCode
    res2: Int = 1688656232
    
    scala> myself.hashCode
    res4: Int = 1688656232
    

copy constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
scala> val skipOption = Skip(10, NoOption)
  skipOption: Skip = Skip(10,NoOption())
  
scala> val skipWithLimit = skipOption.copy(anotherOption = Limit(10, NoOption))
  skipWithLimit: Skip = Skip(10,Limit(10,NoOption))

// 内部结构如下
case class Skip(number: Int, anotherOption: QueryOption)
    extends QueryOption {
  def copy(number: Int = number,
  anotherOption: QueryOption = anotherOption) = {
    Skip(number, anotherOption)
  }
}

abstract override

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
trait DogMood {
  def greet
}

trait AngryMood extends DogMood {
  // 父方法是抽象方法, 错误
  override def greet = {
    println("bark")
    super.greet
  }
}
// 但是可以这样
//  abstract override, which means it should be mixed in
// with some class that has the concrete definition of the greet method.
trait AngryMood extends DogMood {
 abstract override def greet = {
   println("bark")
   super.greet
 }
}

sealed

classes marked final in Scala can’t be overridden by subclasses. But classes marked sealed can be overridden as long as the subclasses belong to the same source file.

Value Class

Scala allows user-defined value classes (which could be case classes as well) that extend AnyVal. Value classes are a new mechanism to avoid run-time allocation of the objects. To create a value class you need to abide by some important rules, including:

  • The class must have exactly one val parameter (vars are not allowed).
  • The parameter type may not be a value class.
  • The class can not have any auxiliary constructors.
  • The class can only have def members, no vals or vars.
  • The class cannot extend any traits, only universal traits.

Value classes allow you to add extension methods to a type without the runtime overhead of creating instances.

1
2
3
4
5
6
7
8
9
10
11
class Wrapper(val name: String) extends AnyVal {
  def up() = name.toUpperCase
}

val w = new Wrapper("hey")
w.up()

// 他被编译成这样
object Wrapper {
  def up$extension(_name: String) = _name.toUpperCase
}

A value class can only extend a universal trait, one that extends Any (normally traits by default extend AnyRef). Universal traits can only have def members and no initializa- tion code:

1
2
3
4
5
6
7
8
9
trait Printable extends Any {
  def p() = println(this)
}
case class Wrapper(val name: String) extends AnyVal with Printable {
  def up() = name.toUpperCase
}
...
val w = Wrapper("Hey")
w.p()

Even though now you can invoke the p method on a Wrapper instance at runtime an instance will also be created because the implementation of the p method prints the type. There are limitations when allocation is necessary; if you assign a value class to an array, the optimization will fail.

implicit

1
2
3
4
5
6
7
8
9
10
11
12
implicit def double2Int(d: Double): Int = d.toInt

val someInt: Int = 2.3

-- rewritten by the compiler:
val someInt: Int = double2Int(2.3)

-- we can avoid the runtime cost by turning our implicit classes
-- into value classes
implicit class RangeMaker(val left: Int) extends AnyVal {
  def -->(right: Int): Range = left to right
}

DEMO: Mongo

1

As a developer, I want an easier way to connect to my MongoDB server and access document databases.

As a developer, I want to query and manage documents.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.scalainaction.mongo

class MongoClient(val host:String, val port:Int) {
  require(host != null, "You have to provide a host name")
  private val underlying = new Mongo(host, port)
  def this() = this("127.0.0.1", 27017)
  def version = underlying.getVersion
  def dropDB(name:String) = underlying.dropDatabase(name)
  def createDB(name:String) = DB(underlying.getDB(name))
  def db(name:String) = DB(underlying.getDB(name))
}

package com.scalainaction.mongo
import com.mongodb.{DB => MongoDB}
// 私有构造器, 除了伴生对象能够访问
class DB private(val underlying: MongoDB) {
  def collectionNames = for(name <- new
    JSetWrapper(underlying.getCollectionNames)) yield name

}
object DB {
  def apply(underlying: MongDB) = new DB(underlying)
}

case class 的 unapply

1
2
3
4
5
6
7
object Person {
def apply(firstName:String, lastName:String) = {
  new Person(firstName, lastName)
}
def unapply(p:Person): Option[(String, String)] =
  Some((p.firstName, p.lastName))
}

2

The second user story you need to implement in your driver is an ability to create, delete, and find documents in a MongoDB database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.scalainaction.mongo
import com.mongodb.{DBCollection => MongoDBColleciton}
import com.mongodb.DBObject

class DBCollection(override val underlying: MongoDBCollection) extends ReadOnly

trait ReadOnly {
  val underlying:MongoDBCollection
  def name = underlying getName
  def fullName = underlying getFullName
  def find(doc: DBObject) = underlying find doc
  def findOne(doc: DBObject) = underlying findOne doc
  def findOne = underlying findOne
  def getCount(doc: DBObject) = underlying getCount doc
}

trait Administrable extends ReadOnly {
  def drop: Unit = underlying drop
  def dropIndexes: Unit = underlying dropIndexes
}

trait Updatable extends ReadOnly {
  def -=(doc: DBObject): Unit = underlying remove doc
  def +=(doc: DBObject): Unit = underlying save doc
}

trait Memoizer extends ReadOnly {
  val history = scala.collection.mutable.Map[Int, DBObject]()
  override def findOne = {
    history.getOrElseUpdate(-1, { super.findOne })
  }
  override def findOne(doc: DBObject) = {
    history.getOrElseUpdate(doc.hashCode, { super.findOne(doc) })
  }
}


import com.mongodb.{DB => MongoDB}
import scala.collection.convert.Wrappers._
class DB private(val underlying: MongoDB) {
  private def collection(name: String) = underlying.getCollection(name)
  def readOnlyCollection(name: String) =
      new DBCollection(collection(name)) with Memoizer
  def administrableCollection(name: String) =
      new DBCollection(collection(name)) with Administrable with Memoizer
  def updatableCollection(name: String) =
      new DBCollection(collection(name)) with Updatable with Memoizer
  def collectionNames = for(name <- new
    JSetWrapper(underlying.getCollectionNames)) yield name
}
object DB {
  def apply(underlying: MongoDB) = new DB(underlying)
}

3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
sealed trait QueryOption
case object NoOption extends QueryOption
case class Sort(sorting: DBObject, anotherOption: QueryOption)
    extends QueryOption
case class Skip(number: Int, anotherOption: QueryOption)
    extends QueryOption
case class Limit(limit: Int, anotherOption: QueryOption)
    extends QueryOption

case class Query(q: DBObject, option: QueryOption = NoOption) {
  def sort(sorting: DBObject) = Query(q, Sort(sorting, option))
  def skip(skip: Int) = Query(q, Skip(skip, option))
  def limit(limit: Int) = Query(q, Limit(limit, option))
}

trait ReadOnly {
  val underlying: MongoDBCollection
  def name = underlying getName
  def fullName = underlying getFullName
  def find(query: Query): DBCursor = {
    def applyOptions(cursor:DBCursor, option: QueryOption): DBCursor = {
      option match {
        case Skip(skip, next) => applyOptions(cursor.skip(skip), next)
        case Sort(sorting, next)=> applyOptions(cursor.sort(sorting), next)
        case Limit(limit, next) => applyOptions(cursor.limit(limit), next)
        case NoOption => cursor
      }
    }
    applyOptions(find(query.q), query.option)
  }
  def find(doc: DBObject): DBCursor = underlying find doc
  def findOne(doc: DBObject) = underlying findOne doc
  def findOne = underlying findOne
  def getCount(doc: DBObject) = underlying getCount doc
}
文章目录
  1. 1. 一些特性
    1. 1.1. 的使用
    2. 1.2. bean方法
    3. 1.3. object 的工厂方法
    4. 1.4. package object
    5. 1.5. trait
    6. 1.6. case class
    7. 1.7. copy constructor
    8. 1.8. abstract override
    9. 1.9. sealed
  2. 2. Value Class
  3. 3. implicit
  4. 4. DEMO: Mongo
    1. 4.1. 1
    2. 4.2. 2
    3. 4.3. 3