在 REPL 里做实验
优先擦用面向表达式编程
表达式返回值;语句执行代码,但不返回值。Scala 大部分语句都返回其最后一个表达式的值作为结果。
- 不变性(immutability):指对象或变量赋值后就不再改变状态。
- 可变性(mutability):指对象或变量在其生命周期中能够被改变或操纵。
优先选择不变性
Scala 里的所有变量都是指向对象的引用。把变量声明为 val 参数意味着它是个不变的“引用”。
所有的方法参数都是不变引用,类参数默认是不变引用。
创建可变引用的唯一方法是使用 var 引用。
Scala 里不变性很重要,它有助于程序员推理代码,在定义判等或写并发程序时尤其明显。
默认情况下,Scala 用对象位置判等法和散列。对象位置判等法用对象在内存中的位置来作为判等的唯一因素。
Scala 提供了 ##
方法来对应 hashCode 方法,==
方法来对应 equals 方法。hashCode 和 equals 方法总是成对实现。
在实现判等时一般推荐确保如下约束:
- 如果两个对象相等,它们的散列值应该也相等;
- 一个对象的散列值在对象生命周期中不应该变化;
- 在把对象发送到另一个 JVM 时,应该用两个 JVM 里都有的属性来判等。
满足上述要求的唯一方法就是使用不变对象。
不变对象可以安全地在多个线程之间传递而不用担心争用。能够消除锁以及锁带来的各种潜在 bug,极大提供代码库的稳定性。
使用 Option 不用 null
Option 可以视作容器,里面要么有东西(Some),要么什么都没有(None)。
在 Scala 里,参数类型为 Option 表示参数可能是未初始化的。Scala 的惯例是不要使用 null 或未初始化的参数传给函数。
多态场景下的判等
对于需要比引用判等更强的判等的类,最好避免多层实体类层次。
在 JVM 里实现 equals 时,一般来说在做深度判等前先判断引用是否相等的性能更高。设计好的 equals 方法的另一个常用范式是(在深度判等之前)用 hashCode 做个早期判断。
多态判等实现一般继承 scala.Equals
特质,覆写 canEqual 方法,和 equals 方法串联起来使用,使子类可以跳出(opt-out)其父类的判等实现。(注:其实就是个回调的钩子)
trait InstantaneousTime extends Equals {
val repr: Int
override def canEqual(other: Any) = other.isInstanceOf[InstantaneousTime]
override def equals(other: Any): Boolean =
other match {
case that: InstantaneousTime => // 通过 模式匹配 做了 this -> that 的类型判等
if (this eq that) true else {
(that.## == this.##) && (that canEqual this) && (repr == that.repr) // 通过 canEqual 做了 that -> this 的类型判等
}
case _ => false
}
override def hashCode(): Int = repr.hashCode
}
trait Event extends InstantaneousTime {
val name: String
override def canEqual(other: Any) = other.isInstanceOf[Event]
override def equals(other: Any): Boolean = other match {
case that: Event => // 通过 模式匹配 做了 this -> that 的类型判等
if (this eq that) {
true
} else {
(that canEqual this) && (repr == that.repr) && (name == that.name) // 通过 canEqual 做了 that -> this 的类型判等
}
case _ => false
}
}
上面的示例都是做了双向的类型判等,使比较的实例必须是同一类型的,屏蔽了父类和子类。
欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。