Scala编程语言近来抓住了很多开发者的眼球 如果你粗略浏览Scala的网站 你会觉得Scala是一种纯粹的面向对象编程语言 而又无缝地结合了命令式和函数式的编程风格 Christopher Diggins认为
不太久之前编程语言还可以毫无疑义地归类成 命令式 或者 函数式 Scala代表了一个新的语言品种 它抹平了这些人为划分的界限
根据David Rupp在博客中的说法 Scala可能是下下一代Java 这么高的评价让人不禁想看看它到底是什么东西
Scala有几项关键特性表明了它的面向对象的本质 例如 Scala中的每个值都是一个对象 包括基本数据类型(即布尔值 数字等)在内 连函数也是对象 另外 类可以被子类化 而且Scala还提供了基于mixin的组合(mixin based position)
与只支持单继承的语言相比 Scala具有更广泛意义上的类重用 Scala允许定义新类的时候重用 一个类中新增的成员定义(即相较于其父类的差异之处) Scala称之为mixin类组合
Scala还包含了若干函数式语言的关键概念 包括高阶函数(Higher Order Function) 局部套用(Currying) 嵌套函数(Nested Function) 序列解读(Sequence Comprehensions)等等
Scala是静态类型的 这就允许它提供泛型类 内部类 甚至多态方法(Polymorphic Method) 另外值得一提的是 Scala被特意设计成能够与Java和 NET互 *** 作 Scala当前版本还不能在 NET上运行(虽然上一版可以) 但按照计划将来可以在 NET上运行
Scala可以与Java互 *** 作 它用scalac这个编译器把源文件编译成Java的class文件(即在JVM上运行的字节码) 你可以从Scala中调用所有的Java类库 也同样可以从Java应用程序中调用Scala的代码 用David Rupp的话来说
它也可以访问现存的数之不尽的Java类库 这让(潜在地)迁移到Scala更加容易
这让Scala得以使用为Java 或者 编写的巨量的Java类库和框架 Scala会经常性地针对这几个版本的Java进行测试 Scala可能也可以在更早版本的Java上运行 但没有经过正式的测试 Scala以BSD许可发布 并且数年前就已经被认为相当稳定了
说了这么多 我们还没有回答一个问题 为什么我要使用Scala? Scala的设计始终贯穿着一个理念
创造一种更好地支持组件的语言 (《The Scala Programming Language》 Donna Malayeri)
也就是说软件应该由可重用的部件构造而成 Scala旨在提供一种编程语言 能够统一和一般化分别来自面向对象和函数式两种不同风格的关键概念
藉著这个目标与设计 Scala得以提供一些出众的特性 包括
* 面向对象风格
* 函数式风格
* 更高层的并发模型
Scala把Erlang风格的基于actor的并发带进了JVM 开发者现在可以利用Scala的actor模型在JVM上设计具伸缩性的并发应用程序 它会自动获得多核心处理器带来的优势 而不必依照复杂的Java线程模型来编写程序
* 轻量级的函数语法
o 高阶
o 嵌套
o 局部套用(Currying)
o 匿名
* 与XML集成
o 可在Scala程序中直接书写XML
o 可将XML转换成Scala类
* 与Java无缝地互 *** 作
Scala的风格和特性已经吸引了大量的开发者 比如Debasish Ghosh就觉得
我已经把玩了Scala好一阵子 可以说我绝对享受这个语言的创新之处
lishixinzhi/Article/program/Java/hx/201311/26873
对于支持并发和分布式处理 高可扩展 基于组件的应用程序来说 Scala的功能是很强大的 它利用了面向对象和函数式程序设计的优点 这种基于Java虚拟机的语言在宣布Twitter正使用它时受到了最多的冲击(相关 CTO评论 从Scala进驻Twitter看多语言混杂系统的前景) 如果使用正确 Scala可以大量减少应用程序对代码的需求
对于Scala编程 我们收集了这些常见代码编写中的陷阱 这些技巧来自于Daniel Sobral 一个曾参加过FreeBSD项目和Java软件开发工程的Scala狂热爱好者
语法错误
认为 yield 像 return 一样 有人会这样写
for(i <to ) {
if (i % == )
yield i
else
yield i
}
正确的表示应该是
for(i <to )
yield {
if (i % == )
i
else
i
}
误用和语法错误
滥用scala xml XML loadXXX 这个的语法分析器试图访问外部的DTD strip组件或类似的东西 在scala xml parsing ConstructingParser fromXXX中有另一个可选的语法分析器 同时 在处理XML时忘记了等号两端的空格 比如
val xml=<root/>
这段代码真正的意思是
val xml $equal$less(root) $slash$greater
这种情况的发生是由于 *** 作符相当随意 而且scala采用这样一种事实 字母数字字符与非字母数字字符通过下划线可以结合成为一个有效的标识符 这也使得 x+y 这样的表达式不会被当成一个标识符 而应该注意 x_+ 是一个有效的标识符 所以 赋值标识符的写法应该是
val xml = <root/>
用法错误
为那些根本不是无关紧要的应用加入Application特征
object MyScalaApp extends Application {
// body
}
示例部分的问题在于 body部分在单元对象初始化时执行 首先 单元初始化的执行是异步的 因此你的整个程序不能与其它线程交互 其次 即时编译器(JIT)不会优化它 因此你的程序速度慢下来 这是没有必要的
另外 不能与其它线程的交互也意味着你会忘记测试应用程序的GUI或者Actors
用法错误
试图模式匹配一个字符串的正则表达式 而又假定该正则表达式是无界的
val r = (\d+) r
val s = ><
s match {
case r(n) =>println( This won t match )
case _ =>println( This will )
}
此处的问题在于 当模式模式匹配时 Scala的正则表达式表现为如同开始于 ^ 结束于 $ 使之工作的正确写法是
val r = (\d+) r
val s = ><
r findFirstIn s match {
case Some(n) =>println( Matches to +n)
case _ =>println( Won t match )
}
或者确保模式能匹配任意前缀和后缀
val r = *(\d+) * r
val s = ><
s match {
case r(n) =>println( This will match the first group of r +n+ to )
case _ =>println( Won t match )
}
用法错误
把var和val认为是字段(fields)
Scala强制使用统一访问准则(Uniform Access Principle) 这使得我们无法直接引用一个字段 所有对任意字段的访问只能通过getters和setters val和var事实上只是定义一个字段 getter作为val字段 对于var则定义一个setter
Java程序员通常认为var和val是字段 而当发现在他们的方法中它们共享相同的命名空间时 常常觉得惊讶 因此 不能重复使用它们的名字 共享命名空间的是自动定义的getter和setter而不是字段本身 通常程序员们会试图寻找一种访问字段的方法 从而可以绕过限制 但这只是徒劳 统一访问准则是无法违背的 它的另一个后果是 当进行子类化时val会覆盖def 其它方法是行不通的 因为val增加了不变性保证 而def没有
当你需要重载时 没有任何准则会指导你如何使用私有的getters和setters Scala编译器和库代码常使用私有值的别名和缩写 反之公有的getters和setters则使用fullyCamelNamingConventions(一种命名规范) 其它的建议包括 重命名 实例中的单元化 甚至子类化 这些建议的例子如下
重命名
class User(val name: String initialPassword: String) {
private lazy var encryptedPassword = encrypt(initialPassword salt)
private lazy var salt = scala util Random nextInt
private def encrypt(plainText: String salt: Int): String = { }
private def decrypt(encryptedText: String salt: Int): String = { }
def password = decrypt(encryptedPassword salt)
def password_=(newPassword: String) = encrypt(newPassword salt)
}
单例模式(Singleton)
class User(initialName: String initialPassword: String) {
private object fields {
var name: String = initialName
var password: String = initialPassword
}
def name = fields name
def name_=(newName: String) = fields name = newName
def password = fields password
def password_=(newPassword: String) = fields password = newPassword
}
或者 对于一个类来说 可以为相等关系或hashCode自动定义可被重用的方法
class User(name : String password : String) {
private case class Fields(var name: String var password : String)
private object fields extends Fields(name password )
def name = fields name
def name_=(newName: String) = fields name = newName
def password = fields password
def password_=(newPassword: String) = fields password = newPassword
}
子类化
case class Customer(name: String)
class ValidatingCustomer(name : String) extends Customer(name ) {
require(name length <)
def name_=(newName : String) =
if (newName length <) error( too short )
else super name_=(newName)
}
val cust = new ValidatingCustomer( xyz )
用法错误
忘记类型擦除(type erasure) 当你声明了一个类C[A] 一个泛型T[A]或者一个函数或者方法m[A]后 A在运行时并不存在 这意味着 对于实例来讲 任何参数都将被编译成AnyRef 即使编译器能够保证在编译过程中类型不会被忽略掉
这也意味着在编译时你不能使用类型参数A 例如 下面这些代码将不会工作
def checkList[A](l: List[A]) = l match {
case _ : List[Int] =>println( List of Ints )
case _ : List[String] =>println( List of Strings )
case _ =>println( Something else )
}
在运行时 被传递的List没有类型参数 而List[Int]和List[String]都将会变成List[_] 因此只有第一种情况会被调用
你也可以在一定范围内不使用这种方法 而采用实验性的特性Manifest 像这样
def checkList[A](l: List[A])(implicit m: scala reflect Manifest[A]) = m toString match {
case int =>println( List of Ints )
case java lang String =>println( List of Strings )
case _ =>println( Something else )
}
设计错误
Implicit关键字的使用不小心 Implicits非常强大 但要小心 普通类型不能使用隐式参数或者进行隐匿转换
例如 下面一个implicit表达式
implicit def string Int(s: String): Int = s toInt
这是一个不好的做法 因为有人可能错误的使用了一个字符串来代替Int 对于上面的这种情况 更好的方法是使用一个类
case class Age(n: Int)
implicit def string Age(s: String) = Age(s toInt)
implicit def int Age(n: Int) = new Age(n)
implicit def age Int(a: Age) = a n
这将会使你很自由的将Age与String或者Int结合起来 而不是让String和Int结合 类似的 当使用隐式参数时 不要像这样做
case class Person(name: String)(implicit age: Int)
这不仅因为它容易在隐式参数间产生冲突 而且可能导致在毫无提示情况下传递一个隐式的age 而接收者需要的只是隐式的Int或者其它类型 同样 解决办法是使用一个特定的类
另一种可能导致implicit用法出问题的情况是有偏好的使用 *** 作符 你可能认为 ~ 是字符串匹配时最好的 *** 作符 而其他人可能会使用矩阵等价(matrix equivalence) 分析器连接等(符号) 因此 如果你使用它们 请确保你能够很容易的分离其作用域
设计错误
设计不佳的等价方法 尤其是
◆试着使用 == 代替 equals (这让你可以使用 != )
◆使用这样的定义
def equals(other: MyClass): Boolean
而不是这样的
override def equals(other: Any): Boolean
◆忘记重载hashCode 以确保当a==b时a hashCode==b hashCode(反之不一定成立)
◆不可以这样做交换 if a==b then b==a 特别地 当考虑子类化时 超类是否知道如何与一个子类进行对比 即使它不知道该子类是否存在 如果需要请查看canEquals的用法
◆不可以这样做传递 if a==b and b ==c then a==c
用法错误
在Unix/Linux/*BSD的系统中 对你的主机进行了命名却没有在主机文件中声明 特别的 下面这条指令不会工作
ping `hostname`
在这种情况下 fsc和scala都不会工作 而scalac则可以 这是因为fsc运行在背景模式下 通过TCP套接字监听连接来加速编译 而scala却用它来加快脚本的执行速度
风格错误
使用while 虽然它有自己的用处 但大多数时候使用for往往更好 在谈到for时 用它们来产生索引不是一个好的做法
避免这样的使用
def matchingChars(string: String characters: String) = {
var m =
for(i <until string length)
if ((characters contains string(i)) &&!(m contains string(i)))
m += string(i)
m
}
而应该使用
def matchingChars(string: String characters: String) = {
var m =
for(c <string)
if ((characters contains c) &&!(m contains c))
m += c
m
}
如果有人需要返回一个索引 可以使用下面的形式来代替按索引迭代的方法 如果对性能有要求 它可以较好的应用在投影(projection)(Scala )和视图(Scala )中
def indicesOf(s: String c: Char) = for {
(sc index) <s zipWithIndex
if c == sc
lishixinzhi/Article/program/Java/hx/201311/26022
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)