11.4 动态和静态编程

静态类型编程语言和动态类型编程语言的一个重要差别在于,对于静态编程语言,编译得到的程序(就JVM语言而言,为生成的Java字节码)包含的方法调用和引用是经过解析或编译的,而Groovy、Clojure和Python等动态编程语言等到程序运行阶段才做出这方面的决策。

与人生的其他方面一样,这两种方法都有优点和缺点。

编程风格 优点 缺点
静态 • 应用程序的运行速度快 • 能够在编译阶段发现很多细微的错误 • 顶级IDE提供了相应的支持,它们提供了卓越的重构工具,可极大地提高效率 • 编译阶段通常耗时很长 • 为满足编译器的要求,通常需要编写更多的代码
动态 • 通常需要编写的代码更少 • 编译速度通常非常快 • 支持元编程 • 应用程序的性能通常较低 • 很多错误要到运行阶段才能发现;细微的错误可能隐藏很长时间 • 需要编写代码来验证传递给函数的参数类型 • IDE提供的支持有限,因为提供相关的支持要难得多

在动态语言(如Groovy和Clojure等JVM语言以及JavaScript、Python和Ruby等非JVM语言)中调用方法时,要等到运行阶段才判断对象实例是否有相应的方法,如果有,再判断传入的参数是否合法。而在静态语言(如Java、Scala和Kotlin等JVM语言以及C、C#、C++和Visual Basic等非JVM语言)中,这些工作是由编译器完成的,运行阶段只需直接执行编译后的指令。

显然,在运行阶段解析方法调用及访问属性需要占用一定的处理时间,因此你可能会问,这样做有什么好处呢?答案是能够进行元编程,且实现起来很容易,这将在下一节介绍。

11.4.1 元编程

为了理解元编程的概念,我们来创建一个小型类:

  1. class MetaProgrammingDemo {
  2. }
  3. def demo = new MetaProgrammingDemo()
  4. // 接下来的代码会引发异常!
  5. demo.nonExistingProperty = "some value"
  6. println(demo.nonExistingProperty)

如你所料,当JVM执行最后一行代码时,将引发异常:

  1. groovy.lang.MissingPropertyException: No such property: nonExistingProperty
  2. for class: MetaProgrammingDemo

接下来给MetaProgrammingDemo类添加如下方法:

  1. def propertyMissing(String name) {
  2. println("Non-existent property '$name' was read")
  3. return -1
  4. }
  5. def propertyMissing(String name, args) {
  6. println("Non-existent property '$name' was written to: '$args'")
  7. }

现在运行这些代码,JVM不会引发异常,而是向控制台打印如下输出:

  1. Non-existent property 'nonExistingProperty' was written to: 'some value'
  2. Non-existent property 'nonExistingProperty' was read
  3. -1

注意,读取的属性值与代码设置的属性值不同,这是因为我们以硬编码的方式返回了-1。读取不存在的属性时,将调用方法propertyMissing(String name),而设置不存在的属性时,将调用方法propertyMissing(String name, args)

在Groovy中访问属性(无论是读取还是写入)时,Groovy将在运行阶段执行如下操作。

(1) 如果存在相应的获取/设置函数,就调用它。

(2) 如果不存在相应的获取/设置函数,就查找相应的实例变量或类变量;如果找到,就访问它。

(3) 如果没有相应的变量,就查找方法propertyMissing;如果找到,就调用它。

(4) 如果上述操作都以失败告终,就引发groovy.lang.MissingPropertyException异常。

11.4 动态和静态编程 - 图1 实际流程比上面描述的更复杂,因为除本章讨论的元编程方式外,Groovy还提供了其他元编程方式。

在动态编程语言中,可让代码以为原本不存在的属性是存在的,这在静态类型语言中几乎是无法实现的。这种技术为何很有用呢?在什么情况下很有用呢?目前这些对你来说可能不那么显而易见。下一章将使用Groovy的XML生成器,它大量地使用了元编程,可能会让你对如何在自定义类中使用这种强大的技术有一定的了解。

前面只处理了不存在的属性;对于方法,也可使用类似的技术。请在MetaProgrammingDemo类中添加如下方法:

  1. def methodMissing(String name, args) {
  2. println("Non-existent method '$name' was called with '$args'
  3. parameters")
  4. }

再在既有的测试代码中添加如下代码:

  1. demo.methodThatDoesNotExist(1000, "demo")

现在如果运行这些代码,将向控制台打印下述额外输出:

  1. Non-existent method 'methodThatDoesNotExist' was called with '[1000, demo]'
  2. parameters

11.4 动态和静态编程 - 图2 这种技巧仅适用于Groovy。如果你在其他JVM语言中使用这个类,JVM不会在遇到读写未知属性的语句时自动调用方法missingProperty(),也不会在遇到未知的方法调用时自动调用missingMethod()

11.4.2 Groovy静态编程

要让编译器在编译特定的类或方法时切换到静态编程模式,可使用注解@TypeChecked,但在此之前必须将其从groovy.transform包导入:

  1. import groovy.transform.TypeChecked
  2. @TypeChecked
  3. class TypeCheckedClass {
  4. }

如果你给类指定了这个注解,编译器将像静态类型语言的编译器那样做大量的检查,并将直接引用编译成方法和属性,而不再让代码在运行阶段再决定必须调用哪个方法。这可让代码的执行速度更快些,但实际上,仅当代码在循环中被多次调用或被多个线程同时调用时,这种差别才能显现出来。

使用注解@TypeChecked后,类就不能使用元编程等动态编程功能。例如,下面的代码无法通过编译:

  1. import groovy.transform.TypeChecked
  2. @TypeChecked
  3. class Demo {
  4. static void main(String[] args) {
  5. def d = new Demo()
  6. // 无法通过编译
  7. d.thisMethodDoesNotExist()
  8. }
  9. def methodMissing(String name, args) {
  10. println("Method '$name' was called")
  11. }
  12. }

如果在GroovyConsole中运行这些代码(运行代码前,GroovyConsole也会在幕后对其进行编译),将出现如下编译错误:

  1. [Static type checking] - Cannot find matching method
  2. Demo#thisMethodDoesNotExist(). Please check if the declared type is right
  3. and if the method exists.

鉴于编译器不确定methodMissing()会不会支持调用thisMethodDoesNotExist()(它可能引发异常,也可能只处理特定的方法名),因此拒绝对这些代码进行编译。要解决这个问题,可将注解@TypeCheckedDemo类中删除。但还有另一种解决方案:让编译器的类型检查器忽略某些方法。为此,可在代码行static void main(String[] args)前面添加如下注解:

  1. @TypeChecked(groovy.transform.TypeCheckingMode.SKIP)
  2. static void main(String[] args) {
  3. ...
  4. }

这让类型检查器不要检查方法main()。当然,如果你在某个类的很多方法前面都添加了这个注解,也许根本就不需要给这个类添加注解@TypeChecked。注解@TypeChecked也可用于方法:

  1. class Demo2 {
  2. def static void main(String[] args) {
  3. def d = new Demo()
  4. d.typeCheckedDemoMethod()
  5. }
  6. @TypeChecked
  7. def typeCheckedDemoMethod() {
  8. // 静态类型实现代码……
  9. }
  10. }