《深入理解 Scala》第五章 — 利用隐式转换编写更有表达力

Scala 的隐式转换系统定义了一套定义良好的查找机制,让编译器能够调整代码。Scala 编译器可以推导下面两种情况:

  • 缺少参数的方法调用或构造器调用;
  • 缺少了的从一种类型到另一种类型的转换。(是指调用某个对象上不存在的方法时,隐式转换自动把对象转换成有该方法的对象)

介绍隐式转换系统

implicit 关键字可通过两种方式使用:1、方法或变量定义;2、方法参数列表。如果关键字用在变量或方法上,等于告诉编译器在隐式解析(implicit resolution)的过程中可以使用这些方法和变量。

隐式解析是指编译器发现代码里的缺少部分信息,而去查找缺少信息的过程。

implicit 关键字用在方法参数列表的开头,是告诉编译器应当通过隐式解析来确定所有的参数值。

scala> def findAnInt(implicit x: Int) = x
findAnInt: (implicit x: Int)Int

scala> findAnInt
<console>:9: error: could not find implicit value for parameter x: Int
              findAnInt
              ^

scala> implicit val test = 5
test: Int = 5

scala> findAnInt
res1: Int = 5

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

scala> findTwoInt  // implicit 作用于整个参数列表
res2: Int = 10

scala> implicit val test2 = 5
test2: Int = 5

scala> findTwoInt
<console>:11: error: ambiguous implicit values:
 both value test of type => Int
 and value test2 of type => Int
 match expected type Int
              findTwoInt
              ^

scala> findTwoInt(2, 5)  // 隐式的方法参数仍然能够显式给出
res4: Int = 7

隐式的方法参数仍然能够显式给出。

继续阅读

《深入理解 Scala》第四章 — 面向对象编程

限制在对象或特质的 body 里初始化逻辑的代码

在编译特质的时候, Scala 创建一个接口/实现对(interface/implementation pair,接口的类名是特质的名字,实现的类名是特质的名字加上 $class),接口用于 JVM 交互,实现则是一组静态方法,在类实现特质时可以用到。

$ cat Test.scala
trait Application {
    def main(args: Array[String]){
    }
}

object Test extends Application {
    println("hello world!")
}

// 编译后的接口和实现
$ javap -c Test{,\$.class}
//  接口类
Compiled from "Test.scala"
public final class Test {
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #16                 // Field Test$.MODULE$:LTest$;
       3: aload_0
       4: invokevirtual #18                 // Method Test$.main:([Ljava/lang/String;)V  // 转发调用实现类的方法
       7: return
}

//  实现类
Compiled from "Test.scala"
public final class Test$ implements Application {
  public static final Test$ MODULE$;

  // 静态初始化块,实例化实现类
  public static {};
    Code:
       0: new           #2                  // class Test$
       3: invokespecial #14                 // Method "<init>":()V
       6: return

  public void main(java.lang.String[]);
    Code:
       0: aload_0
       1: aload_1
       2: invokestatic  #21                 // Method Application$class.main:(LApplication;[Ljava/lang/String;)V
       5: return
}

目前,静态初始化代码块里的方法不能应用 HopSpot 优化。

继续阅读

《深入理解 Scala》第三章 — 来点样式 — 编码规范

编码规范的目标可以归纳为三类:

  • 代码发现:作用在于使工程师能够轻松地推理代码,看懂其他工程师的意图。
  • 代码一致性:作用在于使整个项目的编码风格保持一致。
  • 错误防止:指那些有助于在生产代码里避免 bug 的样式规则。

设计编码规范应该采取的方法:

  1. 从错误防止规范开始;
  2. 开发一套代码发现相关的规范,应与团队使用的开发环境匹配;
  3. 最后是代码一致性规范,应考虑到自动化工具支持的需要。

空悬操作符是指一行代码的最后一个非空字符是一个操作符,有助于编译器判断语句真正结束的位置。

名称重整(name mangling):是指编译器修改或修饰类名或方法名,以便把它编译到底层的平台上。Scala 里名称重整用在嵌套类或辅助方法上。

当 Scala 必须生成匿名函数或匿名类时,Scala 会生成一个名字,包括函数或类所处的类名、字符串 annofun 和一个数字, 用 $ 符号连接起来。

编译器也会对默认参数应用名称重整措施。Scala 里默认参数也是被编码成方法的,方法名为 default 加上用来表示参数在函数里的出现顺序的序号。这出现在方法名字空间(namespace)里而不是类名字空间里。方法名$default$序号

继续阅读

《深入理解 Scala》第二章 — 核心规则

在 REPL 里做实验

优先擦用面向表达式编程

表达式返回值;语句执行代码,但不返回值。Scala 大部分语句都返回其最后一个表达式的值作为结果。

  • 不变性(immutability):指对象或变量赋值后就不再改变状态。
  • 可变性(mutability):指对象或变量在其生命周期中能够被改变或操纵。

优先选择不变性

Scala 里的所有变量都是指向对象的引用。把变量声明为 val 参数意味着它是个不变的“引用”。

所有的方法参数都是不变引用,类参数默认是不变引用。

创建可变引用的唯一方法是使用 var 引用。

Scala 里不变性很重要,它有助于程序员推理代码,在定义判等或写并发程序时尤其明显。

默认情况下,Scala 用对象位置判等法和散列。对象位置判等法用对象在内存中的位置来作为判等的唯一因素。

Scala 提供了 ## 方法来对应 hashCode 方法,== 方法来对应 equals 方法。hashCode 和 equals 方法总是成对实现。

继续阅读

《深入理解 Scala》第一章 — Scala 一种混合式编程语言

Scala 的预期目标是将面向对象、函数式编程和强大的类型系统结合起来,同时仍然要能写出优雅、简洁的代码。

Scala 视图将以下三组对立的思想融合到一种语言中:

  • 函数式编程和面向对象编程
  • 富有表达力的语法和静态类型
  • 高级的语言特性同事保持与 Java 的高度集成

面向对象编程

面向对象编程是一种自顶向下的程序设计方法,将代码以名词(对象)做切割,每个对象有某种形式的标识符(self/this)、行为(方法)和状态(成员变量)。识别出名词并且定义出它们的行为后,再定义出名词之间的交互。实现交互时,必须将这些交互放在其中一个对象中(而不能独立存在)。现代面向对象设计倾向于定义出”服务类“,将操作多个领域对象的方法集合放在里面。这些服务类虽然也是对象,但通常不具有独立状态,也没有与它们所操作的对象无关的独立行为。

函数式编程

函数式编程方法通过组合和应用函数来构造软件。函数式编程通常倾向于将软件分解为其需要执行的行为或操作,而且通常采用自底向上的方法。函数式编程:对不可变性的强调有助于编写并发程序,视图将副作用推迟到尽可能晚,提供了非常强大的对事物进行抽象和组合的能力。消除副作用使得对程序进行推理(reasoning)变得容易。

继续阅读