其他区别(Other Differences)
Groovy和Java 之间仍存在一些其他的区别,本节将讨论其中之二:可选的类型声明(optional typing)和操作符重载(operator overloading)。
可选的类型声明 (optional typing)
在Groovy中,程序员可以声明静态或动态类型的变量。其中,可用关键字def(在脚本中也是可选的)来声明动态类型的变量。脚本运行时,Groovy会依据变量的所赋值来确定变量的运行时类型,如清单2.13所示。
清单2.13 Groovy动态类型
def var = 1
assert var.class in java.lang.Integer
var = 'Hello World'
assert var.class in java.lang.String
从清单可看到,当变量var被赋值为数字1,var运行时类型为java.lang.Integer;当var被赋值为 'Hello World'时,此时var运行时类型便成了java.lang.String。
然而,运行清单2.14中代码将会抛出ClassCastException异常。
清单2.14 ClassCastException异常
def var = 15
assert var == "15"
Exception thrown: java.lang.ClassCastException:
java.lang.String cannot be cast to java.lang.Integer
Exception thrown: java.lang.AssertionError: Expression: (var == 15). Values: var = 15
Groovy是一种类型安全的编程语言,这意味着不能把一种类型的对象当作其他类型,当然显式转换除外。例如,Integer对象不经过恰当的转换,永远不能被当作String类型的对象。要使上面代码正确运行,就必须把字符串"15"解析为Integer值,即:
def var = 15
assert var == Integer.parseInt("15")
静态类型的变量声明时必须明确数据类型(是指需要进行变量/对象类型声明的语言),并且无论在编译期还是运行期,数据类型均不发生变化。静态类型对变量所保存的值也有严格限制(Static typing will also restrict the types of values the variables may hold.),例如,运行清单2.15代码将抛出GroovyCastException异常。
int var = 1
assert var as java.lang.Integer
var = 'Hello World'
Exception thrown: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'Hello World' with class 'java.lang.String' to class 'java.lang.Integer'
正如第1章提到的,Groovy中一切都是对象,因此当您声明var变量为int型时,Groovy便用相对应的引用类型Integer来代替。因为在程序中使用的是显式类型,您只有通过合适转换才能将String赋值给Interger型var变量。
当然,如有可能Groovy会根据变量所赋的值自动转型;否则,便会抛出异常,如清单2.16所演示的那样。
清单2.16 Groovy自动转型
int var = 1
var = 2.7
assert var == 2
assert var.class in Integer
var = '1' as char
assert var == 49
assert var.class in Integer
在清单2.16中,java.lang.Integer类型的变量var被赋了一个浮点型值2.7,Groovy将浮点型值2.7自动转型为整型,结果为2。当把java.lang.Character类型值1赋值给var,Groovy也会将字符型值1自动转型为整型代表值,即49。
也许您会问,到底该使用静态类型还是动态类型呢?这绝对不是一个容易回答的问题。因为这两种方式各有优缺点。静态类型使代码更加清晰,有优秀IDE支持,如提供编译程序优化和很好的代码感知能力,IDE 还可以立即为您提供有关错误的反馈并突出显示代码以使它们更便于理解,它允许方法重载,在编译期提供完全性检查等。另一方面,动态类型相对灵活些,尤其编写脚本时,它允许在方法间传递对象,而不用关心对象的具体类型,也可运行鸭子类型(duck typing)。本书第4章将详细学习duck typing。
译者注:在计算机科学当中,duck typing是一种动态类型的概念,对象的类型由其运行时支持的属性和方法决定。duck typing的哲学是:一个对象能做什么就是什么;与之对应的继承哲学是:一个对象继承什么就是什么。这样,不同的对象只要满足兼容的属性和方法,那么不管它们之间在继承结构上有没有关联,彼此都可以替换使用。
由于Java是静态类型的语言,多数Java程序员开始学习Groovy时,会倾向于给所有变量声明类型,这样可使您轻松实现从Java到Groovy的转换。但随着逐渐熟悉Groovy,您就应该在声明变量时试着省略对象类型。看一下清单2.17的例子。
清单2.17 Groovy中省略对象类型
def url = new URL("http://groovy.codehaus.org")
def a = url.openConnection()
println a.getContentType()
text/html; charset=UTF-8
上面例子中,您不必定义URL对象的类型,或调用openConnection()方法后返回值的类型。唯一您感兴趣的是,能够调用返回值的getContentType()方法,并提取“content-type”头字段的值。
操作符重载(Operator Overloading)
第1章提到,Groovy中所有操作符实际上是调用相应的方法。例如,操作1+1在Groovy中被转换为1.plus(1)。C++语言中的操作符重载实在让人又爱又狠,而现在却以极其优雅的方式整合到Groovy中,帮助您很容易实现操作符重载。
提示:技术上讲,operator overriding比operator overloading更为准确,因为Groovy中所有的操作符转换成方法调用,并且这些方法能够被overridden。Overloading指不同参数列表的同一方法的不同实现。然而,operator overloading 通常被用来描述一个操作符行为改变,因此本文也使用operator overloading表示操作符重载。
Groovy还为Java常用方法提供内置的操作符。Groovy支持的操作符列表可参考Groovy文档和源代码:http://groovy.codehaus.org/Documentation。
Groovy 的“==”是重要的操作符之一,它与Java中的语义不同。Groovy中的“==”不能用于比较对象标识,而是委托给对象的equals方法。例如,Groovy中操作a==b相当于Java中的a.equals(b)。类似地,a!=b 即!a.equals(b)。
提示:Groovy中的操作符“==” 并不总等于调用equals方法的返回值。例如,代码assert 5==5.0将返回“true”,然而assert 5.equals(5.0)将会抛出AssertionError错误。这是因为在使用“==”操作符前,Groovy会先强制转型操作数,因此结果显示两个数字相等;而调用equals方法时则不同,Groovy不会执行强制转型操作,否则会打破Java中equals方法调用规范。Groovy网站声明将在这方面进行改进使之更一致和清晰。
Groovy中可轻松实现操作符重载。清单2.18重载了自增操作符(++)实现罗马数字从Ⅰ到Ⅸ的自增。
清单2.18 罗马数字的自增操作
class RomanNumber {
private String number
static numbers = ["I","II","III","IV","V","VI","VII","VIII","IX","X"]
RomanNumber(number){
this.number = number
}
boolean equals (Object other){
if (null == other) return false
if (! (other instanceof RomanNumber)) return false
if (number != other.number) return false
return true
}
int hashCode(){
number.hashCode()
}
String toString(){
this.number
}
RomanNumber next(){
if (this.number.toUpperCase() == "X")
throw new UnsupportedOperationException
("Sorry, you can only increment Roman Numbers up to X")
int index = numbers.indexOf(this.number.toUpperCase())
if (index < 0)
throw new IllegalArgumentException("Unknown Roman Number " + this.number)
return new RomanNumber(numbers[index + 1])
}
}
def number = new RomanNumber("II");
println "Number: $number"
number++;
assert number == new RomanNumber("III")
println "After incrementing: $number"
number++;
assert number == new RomanNumber("IV")
println "After incrementing: $number"
运行结果:
Number: II
After incrementing: III
After incrementing: IV
清单2.18只实现了罗马数字从Ⅰ到Ⅸ的自增,同样很容易实现其他罗马数字的自增。该清单中,被重载的equals方法,提供了null值验证,这是因为Groovy中比较操作符默认实现都是null safe,意味着Groovy优雅地处理null而不再抛出烦人的NullPointerException;然后,重载hashCode方法,即相同的罗马数字其hash code值也相等;最后,重载“++”操作符,即重载了“++”对应的方法调用next方法。
提示:实际上,我并没有重载(override)next方法,因为RomanNumber类及其父类Object并不存在next方法,称之为操作符实现(implementation)比较合理。