看到这里有几个有意思的 规则,转载于此:

Read Eval Print Loop (REPL)

REPL在Scala里面指的是直接运行scala.exe进入的交互式命令行模式。广义上讲,也泛指那些在线编程工具。

核心规则1:请使用REPL来熟悉Scala语言。

Scala的REPL有个好处是能够将我们输入的每行代码的内部表示反馈出来。比如:

scala> def add(a:Int, b:Int):Int = a + b

add: (a: Int, b: Int)Int

我们定义一个函数,完成两个数的加法。Scala回显给我们的内容可以帮助我们写代码。

表达式与语句

表达式与语句的区别是:语句是用来执行的,而表达式是用来求值的。在程序员的世界里,表达式就是返回值,语言就是没有返回值执行程序。

Scala是表达式导向的编程语言。但并不是100%成立,Scala代码中还是有控制语块,毕竟我们写程序就是为了控制各种实体为我们服务的。

核心规则2:使用表达式,而不是语句。

这条规则主要是帮助我们简化代码,就像前面加法的例子,a+b就是一个表达式。相比于我们C语言写的相同实现,简单不好。代码里面,像这样的例子肯定还是存在很多的。

不要使用Return

当我们使用表达式的时候,就不需要Return了。因为表达式本身就是用来求值的,我们必要再去显式地说我现在要返回什么。Scala编译器自动使用最后一个表达式的返回值作为函数的返回值。

我们应该记得一个编程指导意见就是函数在同一个地方返回。如果我们现在没有Return语句了,像在Scala中,有没有类似的编程指导呢?看个例子:

object NoReturn extends scala.App {def createErrorMessage1(errorCode : Int) : String = {     val result = errorCode match {       case 1 => "Network Failure"       case 2 => "I/O Failure"       case 3 => "Unknown Error"     }     return result   }   def createErrorMessage2(errorCode: Int) : String = {     var result : String = null            // not val     errorCode match {       case 1 =>         result = "Network Failure"       case 2 =>         result = "I/O Failure"       case _ =>         result = "Unknown Error"     }     return result;   }   def createErrorMessage3(errorCode : Int) : String = {     errorCode match {       case 1 => "Network Failure"       case 2 => "I/O Failure"       case 3 => "Unknown Error"     }   }   println(createErrorMessage1(1))   println(createErrorMessage2(2))   println(createErrorMessage3(3))   println(1 match{case 1 => "Network Failure" case 2 => 3})   println(2 match{case 1 => "Network Failure" case 2 => 3}) }

createErrorMessage2应该是我们以往的写法。定义一个局部变量,然后匹配errorCode,对其进行赋值。createErrorMessage1是Scala推荐的写法(虽然还不够简洁),它使用的是val而不是var,来声明临时变量。val表示值,赋值后就不允许再更改;var是变量,可以重复赋值。createErrorMessage1的的result之后是一个表达式。求值之后直接就赋值了。createErrorMessage3就更加简洁了,差不多到了终极形态了。函数直接就返回一个表达式,少了临时对象。

注:match case支持每个分支返回的类型不同。这个特性在函数式编程中非常有用。

Scala虽然支持所有的3中写法,但是推荐最后一种。因为它帮助简化了代码的复杂度,增加了程序的不可变性。不可变指的是程序在执行过程中,所有的状态(变量)都是常量。不可变的代码比可变代码更加容易理解、调试和维护。

表达式导向的语言倾向与使用不可变的对象,能减少程序中的可变对象。

使用不可变对象

核心规则3:使用不可变对象可以大幅减少运行时故障。当面对可变与不可变的选择时,选择不可变对象无疑是最安全的。

对象等价性

Scala提供了##和==来判断对象是不是等价,它们可以作用于AnyRef(引用)和AnyVal(值)。

对象的哈希值和equal应该成对出现。因为等价性经常使用到了hash值。

import collection.immutable.HashMap
class Point2(var x: Int, var y: Int) extends Equals {   def move(mx: Int, my: Int) : Unit = {     x = x + mx     y = y + my   }   override def hashCode(): Int = y + (31*x)   def canEqual(that: Any): Boolean = that match {     case p: Point2 => true     case _ => false   }   override def equals(that: Any): Boolean = {     def strictEquals(other: Point2) =       this.x == other.x && this.y == other.y     that match {       case a: AnyRef if this eq a => true       case p: Point2 => (p canEqual this) && strictEquals(p)       case _ => false     }   } } object ObjecteEquality extends scala.App {   val x = new Point2(1,1)   val y = new Point2(1,2)   val z = new Point2(1,1)   println(x == y) // false   println(x == z) // true   val map = HashMap(x -> "HAI", y -> "ZOMG")   println(map(x)) // HAI   println(map(y)) // ZOMG   println(map(z)) // HAI, if we remove hashCode, there will be an exception   x.move(1,1) // println(map(x)) //Exception in thread "main" java.util.NoSuchElementException: key not found: Point2@40   println(map.find(_._1 == x)) }

3-22行定义了一个Point2类,它继承自Equals。

trait Equals extends Any {

  def canEqual(that: Any): Boolean

  def equals(that: Any): Boolean

}

定义了自己的move方法和hashCode方法。canEqual用来判断是否可以在对象上应用equal方法,这里只是检查是否类型匹配。equal包含一个内部函数strictEquals用来判断对象的成员是否相等。equal首先检查是不是引用了同一个Point2对象,如果是,直接返回true。否则,检查类型是不是匹配,如果是,用strictEquals用来判断对象的成员是否相等。

第36行:println(map(z)),它的正确执行依赖于hashCode是否定义。Map在寻找指定key的值的时候,会调用key.##。

第38行,由于move改变了x的内部状态,hashCode计算出来的新值当做key去Map里面查找,找不到对应的值,就会报NoSuchElementException异常。

第40行,比较奇特。看下find的定义:

trait IterableLike:

override /*TraversableLike*/ def find(p: A => Boolean): Option[A] = iterator.find(p)

object Iterator:

  def find(p: A => Boolean): Option[A] = {

    var res: Option[A] = None

    while (res.isEmpty && hasNext) {

      val e = next()

      if (p(e)) res = Some(e)

    }

    res

  }

传给find的是一个predicate。迭代器遍历集合中的每个元素,并将该元素作为参数传给predicate。所有我们这里传给predicate的参数是一个键值对[A,B]。_就是传给predicate的参数。_1指的是键值对中的第一个元素(实际上是元组中的第一个元素),即A,也就是作为key的Point2。现在很容易明白这句的意思了,就是与x的hashCode一样的元素。_1的定义位于:

trait Product2:

  /** A projection of element 1 of this Product.

   * @return A projection of element 1.

   */

  def _1: T1

在我们实现对象的等价判断的时候,请遵循:

  • 如果两个对象相等,那它们应该有相同的hashCode。

  • 对象的hashCode在其生命周期内不会改变。

  • 如果将一个对象发送给其他的JVM,应该保证等价判断依赖于对象在两个JVM都可用的属性。主要用于序列化。

如果我们的对象是不可变的,那么上面的条件2自行就满足了,这会简化等价判断。另外,不可变性不仅仅简化等价判断,也会简化并行数据的访问,因为不存在同步互斥。

使用None而不是null

null的使用还是很受大家诟病的。null迫使大家添加了额外的处理代码。Scala使用Option来包装了null的处理,我们不在需要去判断变量是否为空。我们可以将Option看成一个通用的容器,包含了一些对象的容器(Some),或者是空容器(None)。这两周容器都需要对象的类型。

类似地,Scala还有空的列表Nil。

核心规则4:使用None而不是null

Java中,我们经常会碰到空异常。如果我们学会了正确使用Option,完全可以避免空异常的发生。

Scala的Option伴生对象(companion object)包含一个工厂方法,将Java的null自动转换为None:var x : Option[String] = Option(null)。等价于var x : Option[String] = None。

Scala更加高级的用法是把它当作一个集合。这意味着,你可以在Option上使用map、flatMap、foreach,甚至是for表达式。

使用Null的一些高级实例:

class HttpSession
class Connection
object DriverManager {def getConnection(url: String, user: String, pw: String): Connection = {println("getConnection")new Connection}
}
object AdvancedNull extends scala.App {//CREATE AN OBJECT OR RETURN A DEFAULT   def getTemporaryDirectory(tmpArg: Option[String]): java.io.File = {     tmpArg.map(name => new java.io.File(name)).       filter(_.isDirectory).       getOrElse(new java.io.File(       System.getProperty("java.io.tmpdir")))   }   //EXECUTE BLOCK OF CODE IF VARIABLE IS INITIALIZED   val username1: Option[String] = Option("Sulliy")   for (uname <- username1) {     println("User: " + uname)   }   val username2: Option[String] = None   for (uname <- username2) {     println("User: " + uname)   }   def canAuthenticate(username: String, password: Array[Char]): Boolean = {     println("canAuthenticate")     true   }   def privilegesFor(username: String): Int = {     println("privilegesFor")     0   }   def injectPrivilegesIntoSession(session: HttpSession, privileges: Int): Unit = {     println("injectPrivilegesIntoSession")   }   def authenticateSession(session: HttpSession,                           username: Option[String],                           password: Option[Array[Char]]) = {     for (u <- username;          p <- password;          if canAuthenticate(u, p)) {       val privileges = privilegesFor(u)       injectPrivilegesIntoSession(session, privileges)     }   }   authenticateSession(new HttpSession, None, None)   //USING POTENTIAL UNINITIALIZED VARIABLES TO CONSTRUCT ANOTHER VARIABLE   def createConnection(conn_url: Option[String],                        conn_user: Option[String],                        conn_pw: Option[String]): Option[Connection] =     for {       url <- conn_url       user <- conn_user       pw <- conn_pw     } yield DriverManager.getConnection(url, user, pw)   createConnection(None, Option("sully"), None)   def lift3[A, B, C, D](f: Function3[A, B, C, D]): Function3[Option[A], Option[B], Option[C], Option[D]] = {     (oa: Option[A], ob: Option[B], oc: Option[C]) =>       for (a <- oa; b <- ob; c <- oc) yield f(a, b, c)   }   lift3(DriverManager.getConnection)(Option("127.0.0.1"), Option("sulliy"), Option("sulliy")) }

11行-16行,示例了通过一个文件名获取File对象。由于输入参数是Option[String],该函数可以接受None,既null。map、filter、getOrElse都是Option的成员函数:

@inline final def map[B](f: A => B): Option[B] =

if (isEmpty) None else Some(f(this.get))

@inline final def filter(p: A => Boolean): Option[A] =

if (isEmpty || p(this.get)) this else None

@inline final def getOrElse[B >: A](default: => B): B =

if (isEmpty) default else this.get

前两个函数都又返回了Option[],所以我们可以进行级联的书写。map返回None(Option的子类),None的filter返回None,None的getOrElse返回default,即new java.io.File( System.getProperty("java.io.tmpdir")。

我们在需要创建一个对象或者返回一个缺省对象的时候,可以使用这种方法。

18行-21行,示例了将Option放在for循环里面。由于username1赋值了,username2为空,因此20行会被执行,24行不会被执行。37行-47行,给了一个更加复杂的例子。

49行-56行,示例了通过一个可能未初始化的对象来创建新对象。用到了yield。

58行-62行,提供了一个通用方法,将普通的Java方法封装为支持Option的新的Scala方法。这样,我们就不需要自己去处理所有参数的null检查。

多态环境下的等价判断

核心规则5:在使用多态情况下,使用scala.Equals提供的模板。

前面已经提到了scala.Equals,需要注意的是:请同时重写canEqualequal。

理解Scala - 核心规则相关推荐

  1. 《深入理解Spark:核心思想与源码分析》——1.2节Spark初体验

    本节书摘来自华章社区<深入理解Spark:核心思想与源码分析>一书中的第1章,第1.2节Spark初体验,作者耿嘉安,更多章节内容可以访问云栖社区"华章社区"公众号查看 ...

  2. 《深入理解Spark:核心思想与源码分析》——1.3节阅读环境准备

    本节书摘来自华章社区<深入理解Spark:核心思想与源码分析>一书中的第1章,第1.3节阅读环境准备,作者耿嘉安,更多章节内容可以访问云栖社区"华章社区"公众号查看 1 ...

  3. 深入理解Kafka核心设计与实践原理_01

    深入理解Kafka核心设计与实践原理_01 01_初识Kafka 1.1 基本概念 1.2 安装与配置 1.3 生产与消费 1.4 服务端参数配置 01_初识Kafka 1.1 基本概念 一个典型的 ...

  4. 《深入理解Spark:核心思想与源码分析》——第1章环境准备

    本节书摘来自华章社区<深入理解Spark:核心思想与源码分析>一书中的第1章环境准备,作者耿嘉安,更多章节内容可以访问云栖社区"华章社区"公众号查看 第1章 环 境 准 ...

  5. c# 添加中文描述 给enum_理解C# 核心概念 – C# 程序集本地化

    在之前几讲中,老白给大家介绍了C#中module和Assembly的生成和使用.在这一篇中,老白将更加深入的介绍下Assembly其中的一个知识点--本地化(Localization). 什么是本地化 ...

  6. 《深入理解Spark:核心思想与源码分析》——3.10节创建和启动ExecutorAllocationManager...

    本节书摘来自华章社区<深入理解Spark:核心思想与源码分析>一书中的第3章,第3.10节创建和启动ExecutorAllocationManager,作者耿嘉安,更多章节内容可以访问云栖 ...

  7. 深入理解Webpack核心模块Tapable钩子[异步版]

    接上一篇文章 深入理解Webpack核心模块WTApable钩子(同步版) tapable中三个注册方法 1 tap(同步) 2 tapAsync(cb) 3 tapPromise(注册的是Promi ...

  8. Database之SQLSever:SQL命令实现理解索引、规则、默认概念及其相关案例之详细攻略

    Database之SQLSever:SQL命令实现理解索引.规则.默认概念及其相关案例之详细攻略 目录 SQL命令实现理解索引.规则.默认概念 索引 规则

  9. 深入理解RCU|核心原理

    hi,大家好,今天给大家分享并行程序设计中最重要的锁-RCU锁,RCU锁本质是用空间换时间,是对读写锁的一种优化加强,但不仅仅是这样简单,RCU体现出来的垃圾回收思想,也是值得我们学习和借鉴,各个语言 ...

最新文章

  1. BigDecimal类(精度计算类)的加减乘除
  2. python2 python3 中 raw_input input 区别
  3. Markdown语法补充
  4. Linux笔记-rpm与yum的基本概念
  5. Python编程及应用师资研修班--昆明
  6. Windows 键盘快捷键 : Windows 快捷键
  7. java 好用的 schedule_Java用Timer schedule搞定定时职务
  8. 运用“异或”对原文加密,并解密
  9. 融跃品牌月:央视出手,各大卫视联合融跃助力金融学子成梦
  10. 反相高低频技术磨皮法
  11. 手机ppi排行测试软件,依然是目前屏幕色准表现最好的智能手机:iPhone XS 屏幕测试...
  12. 在Word中的方框里打对勾都有哪些方法?☑☑☑
  13. Several ports (8005, 8080, 8009) required by demo are already in use. The server may already be runn
  14. Python——青蛙旅行项目
  15. 高德足迹地图在哪里,高德地图怎么点亮城市?高德地图足迹地图查看方法
  16. “清华学霸计划表”刷爆家长群:自律的孩子有多棒?你想象不到
  17. 汽车电子功能安全标准ISO26262解析(五)——FTA
  18. 【Linux编程】进程间通信(IPC)C语言实现
  19. 网络营销的多种表现形式
  20. 为什么工程师出身的 CEO 越来越“香”?

热门文章

  1. MariaDB Audit  Statistics
  2. MVC-通过对象获取整个表单内容
  3. ServiceStack.Ormlit 使用Insert的时候自增列不会被赋值
  4. MVC3学习第十三章 佟掌柜第二弹——MVC3下利用陕北吴旗娃的分页控件实现数据分页...
  5. 2012年7月的主要目标
  6. 最为完整的gdb调试
  7. Scala学习笔记(1)-环境搭建
  8. L1正则化与嵌入式特征选择(稀疏性)
  9. 视频图像处理基础知识0(双线性插值算法进行图像缩放)【转】
  10. 快速计算--斐波那契数列