zl程序教程

您现在的位置是:首页 >  IT要闻

当前栏目

Groovy 语法 Promotion提升和Coercion强制转换学习

2023-02-19 12:22:33 时间

1. 介绍

本篇内容为Groovy学习第32篇,学习Groovy语法中的提升与强制转换相关知识点。(Promotion和coercion)

学习在Groovy中的各种数据类型的各种强制转换和类型变换。

如果不了解Groovy中的数据时如何进行转换的,那么可以学习一下本篇内容,应该能够给你一些参考。

2. 提升和强制转换

2.1 数值转换

整数提升:数字提升的规则在数学运算一节中有详细说明。[4. Groovy语法-Number和Boolean数据类型学习 (zinyan.com)](https://zinyan.com/?p=389#2.5-数学运算)

主要就是下图所示的,数值类型的转换。


byte

char

short

int

long

BigInteger

float

double

BigDecimal

byte

int

int

int

int

long

BigInteger

double

double

BigDecimal

char


int

int

int

long

BigInteger

double

double

BigDecimal

short



int

int

long

BigInteger

double

double

BigDecimal

int




int

long

BigInteger

double

double

BigDecimal

long





long

BigInteger

double

double

BigDecimal

BigInteger






BigInteger

double

double

BigDecimal

float







double

double

double

double








double

double

BigDecimal









BigDecimal

不同数值之间的提升,是按照该表格的关系进行的。

2.2 闭包closure的类型转换

在前面介绍闭包相关知识的时候,有介绍过闭包中的各种转换,相关知识点可以通过:https://zinyan.com/?p=461,https://zinyan.com/?p=462,https://zinyan.com/?p=463了解。​

这里只是进行简单的复习和介绍。

2.2.1 SAM单例对象,进行闭包转换

SAM类型是定义单个抽象方法的类型。例如我们创建接口:它的入参是个T泛型。

interface Predicate<T> {
boolean accept(T obj)
}

具有单个抽象方法的抽象类:

abstract class Zinyan {
abstract String getName()
void hello() {
println "Hello, $name"
}
}

可以使用as运算符将任何闭包转换为SAM类型:

Predicate filter = { it.contains 'G' } as Predicate
assert filter.accept('Groovy') == true

Greeter greeter = { 'Groovy' } as Greeter
greeter.hello() //输出:Hello, Groovy

从Groovy 2.2.0 开始,as Type表达式是可选的。我们可以省略它,只需编写:

Predicate filter = { it.contains 'G' }
assert filter.accept('Groovy') == true

Greeter greeter = { 'Groovy' }
greeter.hello() //输出:Hello, Groovy

PS: 上面的  { it.contains 'G' }就是一个闭包对象哦

这意味着我们也可以使用方法指针,如下例所示:

boolean doFilter(String s) { s.contains('G') }

Predicate filter = this.&doFilter
assert filter.accept('Groovy') == true

Greeter greeter = GroovySystem.&getVersion
greeter.hello() //输出:Hello, Groovy

2.2.2 调用接受带有闭包的SAM类型的方法

关闭SAM类型强制的第二个也是可能更重要的用例是调用接受SAM类型的方法。设想以下方法:

public <T> List<T> filter(List<T> source, Predicate<T> predicate) {
source.findAll { predicate.accept(it) }
}

然后,可以使用闭包调用它,而无需创建接口的显式实现:

assert filter(['Java','Groovy'], { it.contains 'G'} as Predicate) == ['Groovy']

从Groovy 2.2.0开始,还可以省略显式强制,并像使用闭包一样调用该方法:

assert filter(['Java','Groovy']) { it.contains 'G'} == ['Groovy']

这样做的优点是允许我们在方法调用中使用闭包语法,也就是说,将闭包放在括号之外,从而提高了代码的可读性。

2.2.3 对任意类型的强制闭包

上面介绍了SAM单例对象的强制转换,这里介绍其他的类型。

除了SAM类型之外,闭包还可以强制到任何类型,尤其是特定的接口。让我们定义以下接口:

interface FooBar {
int foo()
void bar()
}

定义了一个接口对象,它有两个方法分别是foo和bar。我们可以使用as关键字将闭包强制到接口中:

def impl = { println 'ok'; 123 } as FooBar

这将生成一个类,所有方法都使用闭包实现:

assert impl.foo() == 123
impl.bar() //输出: ok

但也可以强制对任何类进行闭包。例如,我们可以用class替换我们定义的接口,而不改变assert断言的结果:

class FooBar {
int foo() { 1 }
void bar() { println 'bar' }
}

def impl = { println 'ok'; 123 } as FooBar

assert impl.foo() == 123
impl.bar()

PS: 断言结果不满足是会出新错误并停止程序继续执行的

2.3 Map强制转换成类型

通常使用一个闭包来实现一个接口或一个具有多个方法的类是不可行的。作为替代方案,Groovy允许将Map​强制到接口或类中。在这种情况下,Map​的键被解释为方法名,而值是方法实现。以下示例说明了将Map强制到迭代器中:

def map
map = [
i: 10,
hasNext: { map.i > 0 },
next: { map.i-- },
]
def iter = map as Iterator

当然,这是一个相当做作的例子,但说明了这个概念。我们只需要实现那些实际调用的方法,但如果调用的方法在映射中不存在,则会引发MissingMethodException或

UnsupportedOperationException,具体取决于传递给调用的参数,如下例所示:

interface X {
void f()
void g(int n)
void h(String s, int n)
}

x = [ f: {println "f called"} ] as X
x.f() // 正常的方法调用
x.g() // MissingMethodException 异常触发
x.g(5) // UnsupportedOperationException 异常触发

异常的类型取决于调用本身:

MissingMethodException:如果调用的参数与接口/类中的参数不匹配,就会触发该异常警告。

UnsupportedOperationException:如果调用的参数与接口/类的重载方法之一匹配,就会触发该异常警告。

2.4 String强制转换成enum

Groovy允许透明String​(或GString)强制枚举值。假设定义了以下枚举:

enum State {
up,
down
}

则可以将字符串分配给枚举,而不必使用显式作为强制:

State st = 'up'
assert st == State.up

也可以使用GString作为值:

def val = "up"
State st = "${val}"
assert st == State.up

但是,这会引发运行时错误(IllegalArgumentException):

State st = 'not an enum value'

注意,也可以在switch语句中使用隐式强制:

State switchState(State st) {
switch (st) {
case 'up':
return State.down // 显式赋值
case 'down':
return 'up' // 返回类型的隐式强制
}
}

特别是,请查看case​如何使用字符串常量。但如果调用一个使用带有String​参数的枚举的方法,则仍必须使用as作为强制:

assert switchState('up' as State) == State.down
assert switchState(State.down) == State.up
2.5 自定义类型强制转换

类可以通过实现asType​方法来定义自定义强制策略。自定义强制是使用as​运算符调用的,并且从不隐式。例如,假设定义了两个类,Polar和Cartesian,如以下示例所示:

class Polar {
double r
double phi
}
class Cartesian {
double x
double y
}

你想从极坐标转换成笛卡尔坐标。一种方法是在Polar类中定义asType方法:

def asType(Class target) {
if (Cartesian==target) {
return new Cartesian(x: r*cos(phi), y: r*sin(phi))
}
}

这允许使用as强制运算符:

def sigma = 1E-16
def polar = new Polar(r:1.0,phi:PI/2)
def cartesian = polar as Cartesian
assert abs(cartesian.x-sigma) < sigma

把所有这些放在一起,Polar类看起来像这样:

class Polar {
double r
double phi
def asType(Class target) {
if (Cartesian==target) {
return new Cartesian(x: r*cos(phi), y: r*sin(phi))
}
}
}

但也可以在Polar类之外定义asType,如果想为“封闭”类或不拥有源代码的类定义自定义强制策略,例如使用元类:

Polar.metaClass.asType = { Class target ->
if (Cartesian==target) {
return new Cartesian(x: r*cos(phi), y: r*sin(phi))
}
}

PS: 自定义类型转换主要的就是关键方法asType了。实现asType方法,然后自己就可以定义各种类型的转换了。

2.6 类文本vs变量和as运算符

只有对类有静态引用时,才能使用as关键字,如以下代码所示:

interface Greeter {
void greet()
}
def greeter = { println 'Hello, Groovy!' } as Greeter // Greeter is known statically
greeter.greet()

但是,如果通过反射获得类,例如通过调用class.forName,该怎么办?

Class clazz = Class.forName('Greeter')

尝试使用as关键字对类的引用将失败:

greeter = { println 'Hello, Groovy!' } as clazz
// throws:
// unable to resolve class clazz
// @ line 9, column 40.
// greeter = { println 'Hello, Groovy!' } as clazz

会出现异常错误,因为as​关键字只对类文本有效。我们需要调用asType方法:

greeter = { println 'Hello, Groovy!' }.asType(clazz)
greeter.greet()

3. 小结

到这里,Groovy中有关于强制转换和类型提升的相关知识就分享完毕了。以上内容可以通过Groovy官网文档:

[Groovy Language Documentation (groovy-lang.org)](http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_promotion_and_coercion)深入学习。