这个通过一个例子来解释Scala面向对象编程的基本概念,这里我们需要为MongoDB(一种NoSQL数据库,详见我的另一篇博客)编写一个简单的API。MongoDB官方提供了Java API,而我们要做的是使用Scala对其进行简单的封装。
先来搭建一个叫MongoClient的类作为建立数据库连接的入口,定义一个MongoClient类,并指定主构造函数的两个参数(val host: String, val port: Int)。
class MongoClient(val host: String, val port: Int)
构造函数参数的格式可以是[val/var] param : type,区别在于:val参数是只读的(相当于只有get方法),var参数是可写的(有get和set方法),如果没有前缀则是私有的(没有get和set方法)
可以在类中定义多个构造函数(包括默认构造函数),例如下面是MongoClient的一个完整定义:
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)) }
def this() 定义了一个默认构造函数。
case class是一种特殊的Class,编译器会为其自动生成一些样板代码(equals, hashCode, toString, apply等)。
case class Person(firstName:String, lastName:String)
Scala中不存在静态变量和静态方法,所有的变量和方法必须定义在类或者定义在object中,所谓的object就是一个单例对象。
object可以用来定义一个可执行类:
object HelloWrold extends App { println("Hello World") }
object可以定义
object的另一种常见的用法是作为一个类的伴随对象,用来定义一些“静态”的工厂函数。例如下面这段程序中,我们首先定义了DB类,然后定义了一个同样叫DB的伴随对象,伴随对象可以可以访问伴随类的私有成员。
class DB private (val underlying: MongoDB){ //注意private关键词,DB的构造函数是私有的 def collectionNames = for(name <- new JSetWrapper(underlying.getCollectionNames())) yield name private def collection(name: String) = underlying.getCollection(name) def readOnlyCollection(name: String) = new DBCollection(collection(name)) def administrableCollection(name: String) = new DBCollection(collection(name)) with Administrable def updatableCollection(name: String) = new DBCollection(collection(name)) with Updatable } object DB { def apply(underlying: MongoDB) = new DB(underlying) }
Scala中包也是一种特殊的对象,在包内部可以看定义其他类与对象,如果要在包里面定义函数或者变量的话,请使用包对象:
//package.scala package object bar { val minimumAge = 18 def verifyAge = {} } //other.scala package bar class BarTender { def serveDrinks = { verifyAge; ... } }
包的定义可以是嵌套式的(C#中常见),或者是平板式的(Java中常见)。引用包的常见写法有:
import com.mongodb.DBObject import com.mongodb._ //_与Java中的×(星号)是一个意思 import com.mongodb.{DBCollection => MongoDBCollection} //引入包后更改其名称
scala.Any是任何类的父类;Scala.AnyRef相当于Java中的Object类,即任何引用类的父类;AnyVal是任何值类的父类。
在图中实线表示继承关系,虚线表示view关系,view的意思是类型间存在隐式转换。
在类层级的最底下是Scala.Null和Scala.Nothing,Scala.Null类型只有一个实例化对象null, Scala.Nothing则不能实例化。
Trait可以翻译成特质,你可以认为特质是一种增强版的接口,但我认为把它理解为一种部分实现的类更为合适。为什么这么说?因为使用特质构造程序,允许我们把一个大的类打散成多个特质,相当于把机器拆成零件,然后再自由组装。
特性 | Interface (Java) | Abstract class (Java + Scala) | Trait |
---|---|---|---|
实例化 | No | No | No |
构造参数 | No | Yes | No |
方法定义 | Yes | Yes | Yes |
方法实现 | No | Yes | Yes |
成员变量 | No | Yes | Yes |
多重组合(继承) | Yes | No | Yes |
例如下面这个例子中,我们把mongodb java API的功能打散成几个不同的特质,所以特质都从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 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) } } 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 LocaleAware extends ReadOnly { override def findOne(doc: DBObject) = { doc.put("locale", java.util.Locale.getDefault.getLanguage) super.findOne(doc) } override def find(doc: DBObject) = { doc.put("locale", java.util.Locale.getDefault.getLanguage) super.find(doc) } } class DBCollection(override val underlying: MongoDBCollection) extends ReadOnly //用法 new DBCollection(mongoDBCollection) with Updatable with Administrable
Scala中容器被划分为两类:immutable和mutable,分别属于包 scala.collection.immutable和 scala.collection.mutable。Java程序员可以用String和StringBuffer的来理解它们的区别,即immutable容器上的增删操作会返回一个新的immutable容器,而mutable容器上的增删操作是in place(会产生副作用)。
所有immutable和mutable容器都继承自scala.collection里定义的抽象类,它的类结构如下:
常见的collection操作
并发容器
Scala 2.9引入了并发容器,使得很多容器操作可以安全的并发执行。例如,在普通容器类后加.par后缀即可转换为并发容器。
myCollection.par.foldLeft(0)((a,b) => a+b)
Par容器的内部实现是基于类似于MergeSort的分割组合算法。