Scala讲座:函数、操作符及与Java的比较
本文节选自最近在日本十分流行的Scala讲座系列的第三篇,由JavaEye的fineqtbull翻译。本系列的作者牛尾刚在日本写过不少有关Java和Ruby的书籍,相当受欢迎。
包和函数定义以及类型声明
不过还是想把结婚这个动作明确表现出来呀,那就试着写一下吧。对于函数式+面向对象的Scala来说有两种考虑方式。
第一个是面向对象的方法,当然就是让Person类持有表示结婚的方法getMarriedTo(对方:Person)了。另一个就是函数式方法,结婚是那女双方的事情,只在一方的Person类中定义getMarriedTo方法可能也不太确切,可以在Scala特有的单例对象中(object)定义marry方法来描述结婚这一事件。下面的例子中将Person类移到了Life包中,并在Life包中定义了同名的单例对象(singleton object),然后在Person类和对象中定义了getMarriedTo和marray方法。另外,因为这里的Person单例对象与Person类同名
且在同一个源文件里,所以他们互相又成为伴生对象和伴生类。
package life { class Person(val firstName:String, val lastName:String, var spouse:Person) { def this(fn:String, ln:String) = this(fn, ln, null) def introduction = "我的名字是," + firstName + " " + lastName + (if (spouse != null) ",对方的名字是," + spouse.firstName + " " + spouse.lastName + "。" else "。") def getMarriedTo(p : Person) { this.spouse = p; p.spouse = this //姓可以在以后自由更改 } override def toString : String = super.toString + " [姓: " + lastName + " 名: " + firstName + " 配偶: " + (if (spouse != null) " ("+ spouse.lastName + "," + spouse.firstName + ")" else "没有") + "]" } object Person { def marry(p1: Person, p2: Person): Unit = { p1.spouse = p2; p2.spouse = p1 //姓可以在以后自由更改 } } }
上述Unit类型代表不返回任何值,相当于Java中的void。如果想把Person类以别的名称来使用则可以用import语句来声明别名。比如以下程序中为Person类定义了名为Man的别名。
scala> import life.{Person => Man} import life.{Person=>Man}
实际上述语句与以下声明type别名的语句是一样的
scala> import life.Person import life.Person scala> type Man = Person defined type alias Man
正像这样,我们可以利用Scala的交互式环境一边写简洁的代码一边一点一点地确认结果来进行开发。还有,包和类都可以嵌套定义,这里就省略了。
Scala中操作符也是方法
实际上Scala并没有内嵌在语言中的操作符。加法+、乘法*、减法-、除法/、字符串连接+和列表连接++等操作符都是Int、String或List等类型中的方法(有时可能是父类中的方法)。因此,操作符中的特殊字符在Scala中可以被用作方法名称的一部分,这对于定义迷你语言(DSL,特定领域语言)来说是非常重要的。
那么,将“m先生和f女士结婚后f女士的姓变为m”这一动作以“m < + f”来表示吧。在Scala中这
表示“对接受对象m适用方法< +,参数为f”,是“m.< +(f)”的简化形式。马上就在Person类中定义一个两元操作符方法“< +”吧。虽然返回值也可以是Unit,这里就以接受对象自己为返回值吧。
class Person … { … def < +(p : Person): Person = { //姓与接受对象的姓相一致 this.getMarriedTo(p) //和p结婚返回值为Unit p.lastName = this.lastName //改变姓,赋值表达式的返回值是Unit this //以接受对象自己作为返回值 } … }
下面的代码是f嫁给了m,f的姓改为了m的姓了。
scala> import life.Person import life.Person scala> val m = new Person("Fei", "Zhang") m: life.Person = life.Person@14683c0 [姓: Zhang 名: Fei 配偶: 没有] scala> val f = new Person("Can", "Diao") f: life.Person = life.Person@863941 [姓: Diao 名: Can 配偶: 没有] scala> m < + f res0: life.Person = life.Person@14683c0 [姓: Zhang 名: Fei 配偶: (Zhang,Can)]
到这里我们尝试了一下两元操作符,Scala也可以定义一元操作符,但不同的是方法名称的格式为“unary_操作符”。
Java与Scala的混合
Scala可以非常方便的使用Java的类、接口以及其中定义的方法。不仅仅是调用方法,将Scala类定义为Java类或接口的子类或接口实现也是很容易的。还有,用scalac编译Scala类后生成的仅仅是.class文件,完全可以毫无区别的把Java和Scala混在一起开发。
前面定义了life包,现在就定义一个单例对象Demo吧,在里面将嵌入使用Java的Swing库的例子。将JFrame类在Demo中以Window为别名引入(import),然后就可以看看创建对象的样子了。
object Demo { import javax.swing.{JFrame=>Window} import javax.swing.JFrame._ val mameWindow = new Window("window 1") mameWindow setSize(200, 150) mameWindow setDefaultCloseOperation(EXIT_ON_CLOSE) mameWindow setVisible(true) }
定义了该单例对象后,同是与该对象名同名的Demo类也被定义了。可以用Demo来引用该单例对象,如下所示执行后,可以看到窗口的左上角打开一个小窗口。
scala> Demo res0: Demo.type = Demo$@1205d8d
Scala与Java在语法上的差异
这里简单地列举一下Scala与Java在语法上的差异。
• 类型的声明不是“类型 变量 = 值”而是“变量:类型 = 值”。但是,在类型推断可能的情况下类型声明可以省略。
• 不可变的变量用val,可变的变量用var来声明。任意的数据都可以用def来命名(包括val也可以替换成def)。使用def来声明函数和方法。
• 语句分隔符“;”是可选的,通常用换行来表示。
• 一连串复合语句可以用“;”来分割,然后用“{”和“}”块来包括起来。如果单语句的不用大括号包括也可以。例如for语句既可以是for(i < - List(1, 2, 3, 4)){println(i)},也可以是for(i < - List(1, 2, 3, 4)) println(i)。
• 包括数字、字符串和数组,所有的数据都是对象。包括Java的原类型int、double和bool等所有的数据都对应于Scala中的相应类。
• void作为Unit类来处理,Unit的唯一实例是()。
• 以array(i)来使用数组的索引而不是array[I]。数组项目的取得array(i)和更新array(i) = x也可以认为是调用array.apply(i)和array.update(i, x)方法。
• []可以用来指定范型的具体类型,比如type IList = List[Int]为声明项目类型为Int的列表类型。可以用asInstanseOf[T]方法来强制转换类型,虽然使用了范型之后大部分情况下是用不着的。
• for循环并不是语法,而是被定义为称作for-comprehension的语法糖,最后被转换成map和filter等方法的组合。
• 有意放弃了静态(static)的概念,而是用单例对象来取代了静态对象和方法。不是用class而是用object像“object Singleton extends Object { val data: Int }”一样被定义,其中的属性可以用来代替静态成员。
• 使用import语句来引入包和类,并用“_”代替了“*”。比如import javax.swing.JFrame; import javax.swing.JFrame._。可以格式 “import javax.swing.{JFrame=>MyWindow}”来声明类型的别名。