GEP-12
摘要: SAM 强制转换
SAM 代表单一抽象方法。SAM 类型是一个只有一个抽象方法的抽象类或接口。SAM 强制转换涉及将一个groovy.lang.Closure
实例转换为适合我们 SAM 类型的对象。强制转换可以在赋值时发生,也可以在方法调用时发生。由于这种转换可能超出了 Closure 本身提供的类型,因此它可能不仅仅是简单的 Java 风格的强制转换。Closure 成为所有 SAM 类型的子类型。Groovy 有其他此类转换,不需要显式强制转换或 asType 使用,例如数字对象转换以及 GString 到 String 的转换。
动机
早在 Java8 之前,我们就讨论过如何使用 Closure 支持不同的接口,例如 Runnable 和 Callable。这两个都是简单的例子,任何框架都可以定义无数的接口和抽象类。然后,需要“Groovify”库,编写一个能够将 Closure 对象转换为库理解的格式的辅助层。虽然这种方法不太可能使 Groovy Builder 变得多余,但它仍然可以帮助简化集成。
“单一抽象方法”的含义
对于根据此 GEP 成为 SAM 类型的 SAM 类型,需要一个只有一个抽象方法的抽象类或接口。任何静态方法以及非抽象方法都不算在内。抽象方法可以在 SAM 类本身中定义,也可以在父类中定义。但是,该方法所需的可见性修饰符是 public。
SAM 示例
-
简单接口
interface SAM {
def foo()
}
-
带有 defender 方法(也称为虚拟扩展方法)的接口
interface SAM {
def foo()
def bar() default { 1 }
}
-
从另一个接口继承的接口
interface ParentOfSAM {}
interface SAM extends ParentOfSAM {
def foo()
}
-
从另一个接口继承的接口,但本身没有定义方法
interface ParentOfSAM {
def foo()
}
interface SAM extends ParentOfSAM {}
-
简单抽象类
abstract class SAM {
abstract foo()
}
-
带有抽象方法和非抽象方法的抽象类
abstract class SAM {
abstract foo()
def bar() { 1 }
}
-
扩展其他类的抽象类
abstract class ParentOfSAM1 {
abstract foo()
}
-
abstract class SAM1 extends ParentOfSAM1 {}
class ParentOfSAM {
def bar() { 1 }
}
abstract class SAM2 extends {
abstract foo()
}
-
实现接口的抽象类
interface SomeInterface{
def foo()
}
abstract class SAM1 implements SomeInterface {}
abstract class SAM2 implements Runnable{}
interface AnotherInterface {}
abstract class SAM3 implements AnotherInterface {
abstract foo()
}
非 SAM 示例
-
空接口
interface SomeMarker {}
-
有两个方法的接口
interface TwoMethods {
def foo()
def bar()
}
-
有两个抽象方法的抽象类
abstract class TwoMethods {
abstract foo()
abstract bar()
}
-
空的抽象类
abstract class Empty {}
对方法选择的影响
正常的 method selection 算法试图找到与给定参数运行时类型最具体的 method,并且对于 null 最通用的 method。由于 SAM 类型和目标方法参数类型之间没有继承关系,“最具体”需要在部分内容上重新定义。假设 SAM 类型类似于给定目标类型的直接子类,但是如果 SAM 类型是 Closure 实现的类型(Runnable 和 Callable),则不需要进行 SAM 强制转换。这种情况在 method selection 中是优先的。如果存在重载方法,其中每个方法都可以用作 SAM 强制转换的目标,那么 method selection 将失败,无论它们的内部关系如何。在 Groovy 中,SAM 类型的实际方法签名和强制转换目标并不重要。此外,目标类型是抽象类还是接口也不重要。
两个 SAM 目标导致运行时方法选择失败的示例
interface SAM1 { def foo(String s)}
interface SAM2 { def bar(Integer i)}
def method(x, SAM1 s1){s1.foo(x)}
def method(x, SAM2 s2){s2.bar(x)}
method (1) {it} // fails because SAM1 and SAM2 are seen as equal
method ("1") {it} // fails because SAM1 and SAM2 are seen as equal
SAM 类型被忽略,因为存在非强制转换情况的示例
interface SAM {def foo(String s)}
def method(SAM s) {1}
def method(Runnable r) {2}
assert method {it} == 2
对静态类型系统的影响
静态类型系统的范围分为基本部分(适用于 Groovy 2.2)和扩展部分(适用于更高版本,例如 2.3 或 3.0)。
Groovy 2.2 静态检查
Groovy 2.2 中的类型检查将限制为模拟普通 Groovy 的行为。不会执行任何方法签名检查,并且不会根据 open block 提供的类型进行任何额外的测试或方法选择。
Groovy 2.2+ 静态检查
在更高版本的 Groovy 中,需要改进静态类型检查器,以便通过 open block 或 lambda 提供的类型签名来细化方法选择。只有当提供的类型和 SAM 中的目标类型在数量和类型本身方面匹配时,SAM 类型才适合进行强制转换。可以在这里找到更详细的描述:http://cr.openjdk.java.net/~dlsmith/jsr335-0.6.1/F.html
参考资料和有用链接
-
GEP-12: SAM 强制转换(网页存档链接)
参考实现
-
https://github.com/groovy/groovy-core/commits/SAM(GitHub 上的功能分支)
更新历史
- 3 (2013-07-01)
-
从 Codehaus wiki 中提取的版本
- 4 (2018-10-29)
-
大量细微调整