Facebook Twitter LinkedIn E-mail
magnify
Home 2013 十二月

祝大家圣诞快乐并新年好

快过节了,事情也多了些,博客暂时没有更新,这里祝祝大家圣诞快乐并新年好,来年再见。
merry-christmas-hd-wallpapers

 

Scala开发教程(48): 选择瘦接口还是胖接口设计?

Trait的一种主要应用方式是可以根据类已有的方法自动为类添加方法。也就是说,Trait可以使得一个瘦接口变得丰满些,把它变成胖接口。
选择瘦接口还是胖接口的体现了面向对象设计中常会面临的在实现者与接口用户之间的权衡。胖接口有更多的方法,对于调用者来说更便捷。客户可以捡一个完全符合他们功能需要的方法。另一方面瘦接口有较少的方法,对于实现者来说更简单。然而调用瘦接口的客户因此要写更多的代码。由于没有更多可选的方法调用,他们或许不得不选一个不太完美匹配他们所需的方法并为了使用它写一些额外的代码。
Java的接口常常是过瘦而非过胖。例如,从Java 1.4开始引入的CharSequence接口,是对于字串类型的类来说通用的瘦接口,它持有一个字符序列。下面是把它看作Scala中Trait的定义:

trait CharSequence { 
  def charAt(index: Int): Char 
  def length: Int 
  def subSequence(start: Int, end: Int): CharSequence 
  def toString(): String 
}

尽管类String成打的方法中的大多数都可以用在任何CharSequence上,Java的CharSequence接口定义仅提供了4个方法。如果CharSequence代以包含全部String接口,那它将为CharSequence的实现者压上沉重的负担。任何实现Java里的CharSequence接口的程序员将不得不定义一大堆方法。因为Scala的Trait可以包含具体方法,这使得创建胖接口大为便捷。
在Trait中添加具体方法使得胖瘦对阵的权衡大大倾向于胖接口。不像在Java里那样,在Scala中添加具体方法是一次性的劳动。你只要在Trait中实现方法一次,而不再需要在每个混入Trait的方法中重新实现它。因此,与没有Trait的语言相比,Scala里的胖接口没什么工作要做。
要使用Trait加强接口,只要简单地定义一个具有少量抽象方法的Trait——Trait接口的瘦部分——和潜在的大量具体方法,所有的都实现在抽象方法之上。然后你就可以把丰满了的Trait混入到类中,实现接口的瘦部分,并最终获得具有全部胖接口内容的类。

 

Scala开发教程(47): Trait的基本概念

在Scala中Trait为重用代码的一个基本单位。一个Traits封装了方法和变量,和Interface相比,它的方法可以有实现,这一点有点和抽象类定义类似。但和类继承不同的是,Scala中类继承为单一继承,也就是说子类只能有一个父类。当一个类可以和多个Trait混合,这些Trait定义的成员变量和方法也就变成了该类的成员变量和方法,由此可以看出Trait集合了Interface和抽象类的优点,同时又没有破坏单一继承的原则。
下面我们来看看Trait的基本用法:
定义一个Trait的方法和定义一个类的方法非常类似,除了它使用trait而非class关键字来定义一个trait.

trait Philosophical{
  def philosophize() {
    println("I consume memeory, therefor I am!")
  }
}

这个Trait名为Philosophical。它没有声明基类,因此和类一样,有个缺省的基类AnyRef。它定义了一个方法,叫做philosophize。这是个简单的Trait,仅够说明Trait如何工作。
一但定义好Trait,它就可以用来和一个类混合,这可以使用extends或with来混合一个trait.例如:

class Frog extends Philosophical{
  override def toString="gree"
}

这里我们使用extends为Frog添加Philosophical Trait属性,因此Frog 缺省继承自Philosophical的父类AnyRef,这样Frog类也具有了Philosophical的性质(因此Trait也可以翻译成特质,但后面我们还是继续使用Trait原文)。

scala> val frog = new Frog
frog: Frog = green

scala> frog.philosophize
I consume memeory, therefor I am!

可以看到Frog添加了Philosophical(哲学性)也具有了哲学家的特性,可以说出类似“我思故我在”的话语了。和Interface一样,Trait也定义一个类型,比如:

scala> val phil:Philosophical = frog
phil: Philosophical = green

scala> phil.philosophize
I consume memeory, therefor I am!

变量phil的类型为Philosophical。
如果你需要把某个Trait添加到一个有基类的子类中,使用extends继承基类,而可以通过with 添加Trait。比如:

class Animal
 
class Frog extends Animal with Philosophical{
  override def toString="green"
}

还是和Interface类似,可以为某个类添加多个Trait属性,此时使用多个with即可,比如:

class Animal
trait HasLegs 
class Frog extends Animal with Philosophical with HasLegs{
  override def toString="green"
}

目前为止你看到的例子中,类Frog都继承了Philosophical的philosophize实现。此外Frog也可以重载philosophize方法。语法与重载基类中定义的方法一样。

class Animal
trait HasLegs 
class Frog extends Animal with Philosophical with HasLegs{
  override def toString="green"
  def philosophize() {
    println("It ain't easy being " + toString + "!")
  }
 
}

因为Frog的这个新定义仍然混入了特质Philosophize,你仍然可以把它当作这种类型的变量使用。但是由于Frog重载了Philosophical的philosophize实现,当你调用它的时候,你会得到新的回应:

scala> val phrog:Philosophical = new Frog
phrog: Philosophical = green

scala> phrog.philosophize
It ain't easy being green!

这时你或许推导出以下结论:Trait就像是带有具体方法的Java接口,不过其实它能做的更多。Trait可以,比方说,声明字段和维持状态值。实际上,你可以用Trait定义做任何用类定义做的事,并且语法也是一样的,除了两点。第一点,Trait不能有任何“类”参数,也就是说,传递给类的主构造器的参数。换句话说,尽管你可以定义如下的类:

class Point(x: Int, y: Int)

但下面的Trait定义直接报错:

scala> trait NoPoint(x:Int,y:Int)
<console>:1: error: traits or objects may not have parameters
       trait NoPoint(x:Int,y:Int)

 

Scala开发教程(46): 所有类的公共子类–底层类型

前面我们给出了Scala的类的一个关系图:
20131201001
在这张图的最下方我们可以看到有两个类,scala.Null和scala.Nothing. 这两个类的作用是Scala支持统一方式用来处理面向对象的一些边角情况。因为它们在类层次图的下方,因此也称为底层类型
类Null代表null引用,它是所有引用类(每个由AnyRef派生的类)的子类。Null和值类型不兼容,也就是比如说,你不能把null赋值给一个整数类型变量:

scala> val i:Int=null
<console>:7: error: an expression of type Null is ineligible for implicit conversion
       val i:Int=null

Nothing类型为图中类层次关系的最下方,它是所有其他类的子类,然而这个类型没有任何实例(也就是没有任何值对应Nothing类型)前面提到,Nothing类型的一个用法是示意应用程序非正常终止,比如Predef的有一个error方法:

def error(message:String) :Nothing =
  throw new RuntimeException(message)

error的返回类型就是Nothing,告诉调用者该方法没有正常退出(抛出异常)。正因为Nothing为所有其它类型的子类,你可以灵活使用如error这样的函数。比如:

def divide(x:Int,y:Int):Int=
  if(y!=0) x/y
  else error("Cannot divide by Zero")

if “then”分支的类型为Int(x/y),else分支的类型为error返回值,其类型为Nothing,因为Nothing为所有类型的子类,它也是Int的子类,因此divide的类型为Int。

 

Scala开发教程(45): Scala基本数据类型的实现方法

Scala的基本数据类型是如何实现的?实际上,Scala以与Java同样的方式存储整数:把它当作32位的字类型。这对于有效使用JVM平台和与Java库的互操作性方面来说都很重要,。标准的操作如加法或乘法都被实现为数据类型基本运算操作。然而,当整数需要被当作(Java)对象看待的时候,Scala使用了“备份”类java.lang.Integer。如在整数上调用toString方法或者把整数赋值给Any类型的变量时,就会这么做,需要的时候,Int类型的整数能自动转换为java.lang.Integer类型的“装箱整数(boxed integer)”。
这些听上去和Java的box操作很像,实际上它们也很像,但这里有一个重要的差异,Scala使用box操作比在Java中要少的多:

// Java代码 
boolean isEqual(int x,int y) { 
  return x == y; 
} 
System.out.println(isEqual(421,421));

你当然会得到true。现在,把isEqual的参数类型变为java.lang.Integer(或Object,结果都一样):

// Java代码 
boolean isEqual(Integer x, Integery) { 
  return x == y; 
} 
System.out.println(isEqual(421,421));

你会发现你得到了false!原因是数421使用”box”操作了两次,由此参数x和y是两个不同的对象,因为在引用类型上==表示引用相等,而Integer是引用类型,所以结果是false。这是展示了Java不是纯面向对象语言的一个方面。我们能清楚观察到基本数据值类型和引用类型之间的差别。

现在在Scala里尝试同样的实验:

scala> def isEqual(x:Int,y:Int) = x == y
isEqual: (x: Int, y: Int)Boolean

scala> isEqual(421,421)
res0: Boolean = true

scala> def isEqual(x:Any,y:Any) = x == y
isEqual: (x: Any, y: Any)Boolean

scala> isEqual(421,421)
res1: Boolean = true

Scala 的 == 设计出自动适应变量类型的操作,对值类型来说,就是自然的(数学或布尔)相等。对于引用类型,==被视为继承自Object的equals方法的别名。比如对于字符串比较:

scala> val x = "abcd".substring(2)
x: String = cd

scala> val y = "abcd".substring(2)
y: String = cd

scala> x==y
res0: Boolean = true

而在Java里,,x与y的比较结果将是false。程序员在这种情况应该用equals,不过它容易被忘记。
然而,有些情况你需要使用引用相等代替用户定义的相等。例如,某些时候效率是首要因素,你想要把某些类哈希合并:hash cons然后通过引用相等比较它们的实例,为这种情况,类AnyRef定义了附加的eq方法,它不能被重载并且实现为引用相等(也就是说,它表现得就像Java里对于引用类型的==那样)。同样也有一个eq的反义词,被称为ne。例如:

scala> val x =new String("abc")
x: String = abc

scala> val y = new String("abc")
y: String = abc

scala> x == y
res0: Boolean = true

scala> x eq y
res1: Boolean = false

scala> x ne y
res2: Boolean = true

 

Scala开发教程(44): Scala的类层次关系

前面我们介绍了Scala的类的继承,本篇我们介绍Scala语言自身定义的类的层次关系,在Scala中,所有的类都有一个公共的基类称为Any,此外还定义了所有类的子类Nothing,下面的图给出的Scala定义的类层次关系的一个概要:
20131201001

由于所有的类都继承自Any,因此Scala中的对象都可以使用==,!=,或equals来比较,使用##或hashCode给出hash值,使用toString 转为字符串。Any的==和!=定位为fianl,因此不可以被子类重载。==实际上和equals等价,!=和equals的否定形式等价,因此重载equals可以修改==和!=的定义。

根类Any有两个子类:AnyVal和AnyRef。AnyVal是Scala里每个内建值类型的父类。有九个这样的值类型:Byte,Short,Char,Int,Long,Float,Double,Boolean和Unit。其中的前八个对应到Java的基本数值类型,它们的值在运行时表示成Java的类型。Scala里这些类的实例都写成字面量。例如,42是Int的实例,’x’是Char的实例,false是Boolean的实例。值类型都被定义为即是抽象的又是final的,你不能使用new创造这些类的实例。

scala> new Int
<console>:8: error: class Int is abstract; cannot be instantiated
              new Int
              ^

scala> 

另一个值类,Unit,大约对应于Java的void类型;被用作不返回任何有趣结果的方法的结果类型。Unit只有一个实例值,被写作().

值类支持作为方法的通用的数学和布尔操作符。例如,Int有名为+和*的方法,Boolean有名为||和&&的方法。值类也从类Any继承所有的方法。你可以在解释器里测试:

scala> 42 toString
res3: String = 42
scala> 42.hashCode
res6: Int = 42

可以看到Scala的值类型之间的关系是扁平的,所有的值类都是scala.AnyVal的子类型,但是它们不是互相的子类。代之以它们不同的值类类型之间可以隐式地互相转换。例如,需要的时候,类scala.Int的实例可以自动放宽(通过隐式转换)到类scala.Long的实例。隐式转换还用来为值类型添加更多的功能。例如,类型Int支持以下所有的操作:

scala> 42 max 43
res0: Int = 43

scala> 42 min 43
res1: Int = 42

scala> 1 until 5
res2: scala.collection.immutable.Range = Range(1, 2, 3, 4)

scala> 1 to 5 
res3: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)

scala> 3.abs
res4: Int = 3

scala> (-3).abs
res5: Int = 3

这里解释其工作原理:方法min,max,until,to和abs都定义在类scala.runtime.RichInt里,并且有一个从类Int到RichInt的隐式转换。当你在Int上调用没有定义在Int上但定义在RichInt上的方法时,这个转换就被应用了。
类Any的另一个子类是类AnyRef。这个是Scala里所有引用类的基类。正如前面提到的,在Java平台上AnyRef实际就是类java.lang.Object的别名。因此Java里写的类和Scala里写的都继承自AnyRef。如此说来,你可以认为java.lang.Object是Java平台上实现AnyRef的方式。因此,尽管你可以在Java平台上的Scala程序里交换使用Object和AnyRef,推荐的风格是在任何地方都只使用AnyRef。

Scala类与Java类不同在于它们还继承自一个名为ScalaObject的特别的Marker Trait(Trait我们在后面再进一步解释)