本篇文章为Groovy语言学习第十七篇,在上一篇针对类成员信息的学习了解了构造函数的多种模式,方法的创建方式,
以及可变参数,默认参数的相关知识后,本篇继续学习相关类成员信息。
主要内容为方法的多态选择优先级,以及Groovy中方法的异常声明。
动态Groovy支持多调度(也称为多方法)。当调用方法时,实际调用的方法是基于方法参数的运行时类型动态确定的。首先将考虑方法名称和参数数量(包括可变参数的允许值),然后考虑每个参数的类型。示例如下所示:
PS:后面会有文章专门介绍什么是动态Groovy什么是静态Groovy。现在大家可以简单理解为,脚本写法编译的是动态的,其他参照Java语法规则写的就是静态的。
//创建方法1,传递两个Object对象。
def method(Object o1, Object o2) {
'两个Object对象' }
//创建方法2,传递Integer和String对象。
def method(Integer i, String s) {
'第一个参数为Integer,第二个参数为String' }
//创建方法3,传递String和Integer对象。
def method(String s, Integer i) {
'第一个参数为String,第二个参数为Integer' }
//将会调用第三个方法
println(method('zinyan',23)) //输出:第一个参数为String,第二个参数为Integer
//将会调用第一个方法
println(method('zinyan','Z同学')) //输出: 两个Object对象
//将会调用第二个方法
println(method(1024,'Z同学')) //输出: 第一个参数为Integer,第二个参数为String
在上面的示例中,Groovy会自动根据方法的数据类型,调用符合规则的方法进行运算。这个逻辑也是面向对象中的多态的概念之一了。
还有一种比较特殊的情况,就是编译时不知道数据类型。例如通过后台接口传值等,预先不知道会是String还是Integer还是Object对象。
示例如下:
//创建方法1,传递两个Object对象。
def method(Object o1, Object o2) {
'两个Object对象' }
//创建方法2,传递Integer和String对象。
def method(Integer i, String s) {
'第一个参数为Integer,第二个参数为String' }
//创建方法3,传递String和Integer对象。
def method(String s, Integer i) {
'第一个参数为String,第二个参数为Integer' }
List<List<Object>> pairs = [['zin', 1], [2, 'yan'], [3, 4]] //创建三种 数组对象
pairs.collect{
a,b -> println(method(a,b))
}
输出结果为:
第一个参数为String,第二个参数为Integer
第一个参数为Integer,第二个参数为String
两个Object对象
在实际运行中Groovy会将参数,代入到每个方法中,进行匹配一轮。直到匹配度最高的方法,就会触发该方法并执行。
方法选择就是从具有兼容参数类型的有效方法候选中找到最接近的拟合。因此,方法(Object,Object)对于前两次调用也是有效的,但与类型完全匹配的变量相比,它的匹配度不高。为了确定最接近的拟合,运行时有一个实际参数类型与声明参数类型之间的距离的概念,并试图最小化所有参数之间的总距离。
下表中列出了一些影响方法选择计算的一些因素:
groovy.lang.GroovyRuntimeException
异常了。Object[]
的方法。上面示例中也有介绍如果方法类型优先级相同。Groovy无法判定该调用哪个方法时就会出现groovy.lang.GroovyRuntimeException: Ambiguous method overloading for method
异常。
我们如果有示例如下的代码:
//方法1
def method(Date d, Object o) {
'Date对象和Object对象' }
//方法2
def method(Object o, String s) {
'Object对象和String对象' }
//如果我们想调用方法1,示例如下:
println(method(new Date(),'zinyan.com')) //输出: aught: groovy.lang.GroovyRuntimeException: Ambiguous method overloading for method Zinyan#method.
在上面示例中。并不会调用方法1反而会出现异常。如果方法出现了上面的混乱情况,可以通过以下方式处理:
//方法1
def method(Date d, Object o) {
'Date对象和Object对象' }
//方法2
def method(Object o, String s) {
'Object对象和String对象' }
//方案1:
println(method(new Date(),'zinyan.com' as Object)) //输出:Date对象和Object对象
//方案2:
println(method(new Date(),(Object)'zinyan.com')) //输出:Date对象和Object对象
//方案3:
println(method(new Date() as Object,(Object)'zinyan.com')) //输出:Object对象和String对象
将几种情况都进行了梳理。结合方案中的输出结果能够理解。
我们方法中出现了try/catch
。但是不想在方法中处理,而选择抛出去由使用方法的对象处理。就需要对方法进行异常声明了。
Groovy自动允许将选中的异常视为未选中的异常。这意味着不需要声明方法可能引发的任何已检查异常,如下面的示例所示,如果找不到文件,则会引发FileNotFoundException
:
def badRead() {
//得到一个文件对象
new File('zinyan.txt').text
}
shouldFail(FileNotFoundException) {
badRead()
}
也不需要在try/catch
块中围绕对上一个示例中的badRead方法的调用(PS:如果我们愿意,我们也可以自由这样做)
如果希望声明我们的代码可能抛出的任何异常,可以自由地这样做。添加异常不会改变代码与任何其他Groovy代码的使用方式,但可以将其视为代码读者的文档。异常将成为字节码中方法声明的一部分,因此如果我们的代码可能从Java调用,那么包含它们可能会很有用。下面的示例说明了使用显式检查异常声明:
def badRead() throws FileNotFoundException {
new File('doesNotExist.txt').text
}
shouldFail(FileNotFoundException) {
badRead()
}
ps:在Groovy中如果有异常,我们可以抛弃不用声明。但是我们的脚本如果要配合Java一起混编。那么在方法中添加
throws FileNotFoundException
会更方便java端的调用。
关于面向对象编程中,方法的相关知识就到这里了。下一篇学习类成员中的字段和属性知识点。
以上内容可以参考Groovy官方文档:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_method_selection_algorithm