Scala资料免费送Scala代码编写中常见的十大陷阱

Posted 大数据研习社

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala资料免费送Scala代码编写中常见的十大陷阱相关的知识,希望对你有一定的参考价值。


很多Java开发者在使用Scala编写代码时,由于语法和编写习惯的不同,很多开发者会犯相同或相似的错误。一位Scala狂热爱好者近日总结了十大这样的错误,以供参考


相关资源:Scala全套视频教程、《快学Scala》、Scala免费课程、《Scala程序设计》等等,学习Scala这些资源你不可错过,现在都可以免费领取。领取办法:关注“大数据研习社”后,微信后台回复“scala”,即可获得下载链接。


 陷阱一:语法错误 

认为  “yield” 像 ”return” 一样。有人会这样写:

      
        
        
      
  1. for(i <- 0 to 10) {  

  2.   if (i % 2 == 0)  

  3.     yield i  

  4.   else 

  5.     yield -i  

正确的表示应该是:

      
        
        
      
  1. for(i <- 0 to 10)   

  2. yield {  

  3.   if (i % 2 == 0)  

  4.     i  

  5.   else 

  6.     -i  

 陷阱二:误用和语法错误 

滥用scala.xml.XML.loadXXX。这个的语法分析器试图访问外部的DTD、strip组件或类似的东西。在scala.xml.parsing.ConstructingParser.fromXXX中有另一个可选的语法分析器。同时,在处理XML时忘记了等号两端的空格。比如:

      
        
        
      
  1. val xml=<root/> 

这段代码真正的意思是:

      
        
        
      
  1. val xml.$equal$less(root).$slash$greater  

这种情况的发生是由于操作符相当随意,而且scala采用这样一种事实:字母数字字符与非字母数字字符通过下划线可以结合成为一个有效的标识符。这也使得“x+y”这样的表达式不会被当成一个标识符。而应该注意 “x_+”是一个有效的标识符。所以,赋值标识符的写法应该是:

      
        
        
      
  1. val xml = <root/> 

 陷阱三:用法错误 

为那些根本不是无关紧要的应用加入Application特征。

      
        
        
      
  1. object MyScalaApp extends Application {    

  2.   // ... body ...  

示例部分的问题在于,body部分在单元对象初始化时执行。首先,单元初始化的执行是异步的,因此你的整个程序不能与其它线程交互;其次,即时编译器(JIT)不会优化它,因此你的程序速度慢下来,这是没有必要的。


另外,不能与其它线程的交互也意味着你会忘记测试应用程序的GUI或者Actors。

 陷阱四:用法错误 

试图模式匹配一个字符串的正则表达式,而又假定该正则表达式是无界的:

      
        
        
      
  1. val r = """(\d+)""".r  

  2. val s = "--> 5 <---" 

  3. s match {  

  4.   case r(n) => println("This won't match")  

  5.   case _ => println("This will")  

此处的问题在于, 当模式模式匹配时, Scala的正则表达式表现为如同开始于”^”,结束于”$”。使之工作的正确写法是:

      
        
        
      
  1. val r = """(\d+)""".r  

  2. val s = "--> 5 <---" 

  3. r findFirstIn s match {  

  4.   case Some(n) => println("Matches 5 to "+n)  

  5.   case _ => println("Won't match")  

或者确保模式能匹配任意前缀和后缀:

      
        
        
      
  1. val r = """.*(\d+).*""".r  

  2. val s = "--> 5 <---" 

  3. s match {  

  4.   case r(n) => println("This will match the first group of r, "+n+", to 5")  

  5.   case _ => println("Won't match")  

 陷阱五:用法错误 

把var和val认为是字段(fields):

Scala强制使用统一访问准则(Uniform Access Principle),这使得我们无法直接引用一个字段。所有对任意字段的访问只能通过getters和setters。val和var事实上只是定义一个字段,getter作为val字段,对于var则定义一个setter。

常觉得惊讶。因此,不能重复使用它们的名字。共享命名空间的是自动定义的getter和setter而不是字段本身。通常程序员们会试图寻找一种访问字段的方法,从而可以绕过限制——但这只是徒劳,统一访问准则是无法违背的。它的另一个后果是,当进行子类化时val会覆盖def。其它方法是行不通的,因为val增加了不变性保证,而def没有。


当你需要重载时,没有任何准则会指导你如何使用私有的getters和setters。Scala编译器和库代码常使用私有值的别名和缩写,反之公有的getters和setters则使用fullyCamelNamingConventions(一种命名规范)。其它的建议包括:重命名、实例中的单元化,甚至子类化。这些建议的例子如下:

重命名

      
        
        
      
  1. class User(val name: String, initialPassword: String) {  

  2.   private lazy var encryptedPassword = encrypt(initialPassword, salt)  

  3.   private lazy var salt = scala.util.Random.nextInt  

  4.   private def encrypt(plainText: String, salt: Int): String = { ... }  

  5.   private def decrypt(encryptedText: String, salt: Int): String = { ... }  

  6.   def password = decrypt(encryptedPassword, salt)  

  7.   def password_=(newPassword: String) = encrypt(newPassword, salt)  

单例模式(Singleton)

      
        
        
      
  1. class User(initialName: String, initialPassword: String) {  

  2.    private object fields {  

  3.      var name: String = initialName;  

  4.      var password: String = initialPassword;  

  5.    }  

  6.    def name = fields.name  

  7.    def name_=(newName: String) = fields.name = newName  

  8.    def password = fields.password  

  9.    def password_=(newPassword: String) = fields.password = newPassword  

  10.  } 

或者,对于一个类来说,可以为相等关系或hashCode自动定义可被重用的方法

      
        
        
      
  1. class User(name0: String, password0: String) {  

  2.   private case class Fields(var name: String, var password0: String)  

  3.   private object fields extends Fields(name0, password0)  

  4.   def name = fields.name  

  5.   def name_=(newName: String) = fields.name = newName  

  6.   def password = fields.password  

  7.   def password_=(newPassword: String) = fields.password = newPassword  

子类化

      
        
        
      
  1. case class Customer(name: String)  

  2. class ValidatingCustomer(name0: String) extends Customer(name0) {  

  3.   require(name0.length < 5)  

  4.   def name_=(newName : String) =  

  5.     if (newName.length < 5) error("too short")  

  6.     else super.name_=(newName)  

  7. }  

  8. val cust = new ValidatingCustomer("xyz123"

 陷阱六:用法错误 

忘记类型擦除(type erasure)。当你声明了一个类C[A]、一个泛型T[A]或者一个函数或者方法m[A]后,A在运行时并不存在。这意味着,对于实例来讲,任何参数都将被编译成AnyRef,即使编译器能够保证在编译过程中类型不会被忽略掉。

这也意味着在编译时你不能使用类型参数A。例如,下面这些代码将不会工作:

      
        
        
      
  1. def checkList[A](l: List[A]) = l match {  

  2.   case _ : List[Int] => println("List of Ints")  

  3.   case _ : List[String] => println("List of Strings")  

  4.   case _ => println("Something else")  

在运行时,被传递的List没有类型参数。 而List[Int]和List[String]都将会变成List[_]. 因此只有第一种情况会被调用。

你也可以在一定范围内不使用这种方法,而采用实验性的特性Manifest, 像这样:

      
        
        
      
  1. def checkList[A](l: List[A])(implicit m: scala.reflect.Manifest[A]) = m.toString match {  

  2.   case "int" => println("List of Ints")  

  3.   case "java.lang.String" => println("List of Strings")  

  4.   case _ => println("Something else")  

 陷阱七:设计错误 

Implicit关键字的使用不小心。Implicits非常强大,但要小心,普通类型不能使用隐式参数或者进行隐匿转换。

例如,下面一个implicit表达式:

      
        
        
      
  1. implicit def string2Int(s: String): Int = s.toInt 

这是一个不好的做法,因为有人可能错误的使用了一个字符串来代替Int。对于上面的这种情况,更好的方法是使用一个类。

      
        
        
      
  1. case class Age(n: Int)  

  2. implicit def string2Age(s: String) = Age(s.toInt)  

  3. implicit def int2Age(n: Int) = new Age(n)  

  4. implicit def age2Int(a: Age) = a.n 

这将会使你很自由的将Age与String或者Int结合起来,而不是让String和Int结合。类似的,当使用隐式参数时,不要像这样做:

      
        
        
      
  1. case class Person(name: String)(implicit age: Int) 

这不仅因为它容易在隐式参数间产生冲突,而且可能导致在毫无提示情况下传递一个隐式的age, 而接收者需要的只是隐式的Int或者其它类型。同样,解决办法是使用一个特定的类。

另一种可能导致implicit用法出问题的情况是有偏好的使用操作符。你可能认为”~”是字符串匹配时最好的操作符,而其他人可能会使用矩阵等价(matrix equivalence),分析器连接等(符号)。因此,如果你使用它们,请确保你能够很容易的分离其作用域。

 陷阱八:设计错误 

计不佳的等价方法。尤其是:

◆试着使用“==”代替“equals”(这让你可以使用“!=”)

◆使用这样的定义:

      
        
        
      
  1. def equals(other: MyClass): Boolean 

而不是这样的:

      
        
        
      
  1. 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的系统中,对你的主机进行了命名却没有在主机文件中声明。特别的,下面这条指令不会工作:

      
        
        
      
  1. ping `hostname`  

在这种情况下,fsc和scala都不会工作,而scalac则可以。这是因为fsc运行在背景模式下,通过TCP套接字监听连接来加速编译,而scala却用它来加快脚本的执行速度。

 陷阱十:风格错误 

使用while。虽然它有自己的用处,但大多数时候使用for往往更好。在谈到for时,用它们来产生索引不是一个好的做法。

避免这样的使用:

      
        
        
      
  1. def matchingChars(string: String, characters: String) = {  

  2.   var m = "" 

  3.   for(i <- 0 until string.length)  

  4.     if ((characters contains string(i)) && !(m contains string(i)))  

  5.       m += string(i)  

  6.   m  

而应该使用:

      
        
        
      
  1. def matchingChars(string: String, characters: String) = {  

  2.   var m = "" 

  3.   for(c <- string)  

  4.     if ((characters contains c) && !(m contains c))  

  5.       m += c  

  6.   m  

如果有人需要返回一个索引,可以使用下面的形式来代替按索引迭代的方法。如果对性能有要求,它可以较好的应用在投影(projection)(Scala 2.7)和视图(Scala 2.8)中。

      
        
        
      
  1. def indicesOf(s: String, c: Char) = for {  

  2.   (sc, index) <- s.zipWithIndex  

  3.   if c == sc  

  4. } yield index   




如果你喜欢这些资料,那就分享给更多的人吧,让更多的人得到帮助!
美德智慧 

关注大数据趋势和技术应用,

分享有价值的技术干货。

每周一份大数据技术资料免费领取。



大数据研习社
大数据技术爱好者最喜爱的学习社


以上是关于Scala资料免费送Scala代码编写中常见的十大陷阱的主要内容,如果未能解决你的问题,请参考以下文章

spark常见错误持续更新

Scala 与 Python 的 Spark 性能

在Scala中免费验证

Scala分析器?

scala中常用特殊符号

Scala