151

阅读 Java-8 规范,我不断看到对“SAM 类型”的引用。我一直无法找到关于这是什么的明确解释。

什么是 SAM 类型,什么时候可以使用的示例场景是什么?

4

1 回答 1

171

总结一下 Jon 发布的链接1以防万一它出现故障,“SAM”代表“单一抽象方法”,“SAM-type”指的是接口,如Runnable,Callable等。 Lambda 表达式是 Java 8 中的一个新特性,是被认为是 SAM 类型,可以自由转换为它们。

例如,使用这样的界面:

public interface Callable<T> {
    public T call();
}

你可以像这样声明一个Callableusing lambda 表达式:

Callable<String> strCallable = () -> "Hello world!";
System.out.println(strCallable.call()); // prints "Hello world!"

这种情况下的 Lambda 表达式大多只是语法糖。它们在代码中看起来比匿名类更好,并且对方法命名的限制较少。从链接中获取此示例:

class Person { 
    private final String name;
    private final int age;

    public static int compareByAge(Person a, Person b) { ... }

    public static int compareByName(Person a, Person b) { ... }
}

Person[] people = ...
Arrays.sort(people, Person::compareByAge);

这将创建一个Comparator使用与 不同名称的特定方法,Comparator.compare这样您就不必遵循方法的接口命名,并且您可以在一个类中有多个比较覆盖,然后通过动态创建比较器lambda 表达式。

深入...

在更深层次上,Java 使用invokedynamicJava 7 中添加的字节码指令来实现这些。我之前说过,声明 Lambda 会创建一个匿名类的实例CallableComparable类似的实例,但严格来说并非如此。相反,第一次invokedynamic调用时,它会使用LambdaMetafactory.metafactory方法创建一个 Lambda 函数处理程序,然后在以后的 Lambda 调用中使用这个缓存的实例。更多信息可以在这个答案中找到。

这种方法很复杂,甚至包括可以直接从堆栈内存读取原始值和引用以传递到您的 Lambda 代码的代码(例如,绕过需要分配一个Object[]数组来调用您的 Lambda),但它允许 Lambda 实现的未来迭代替换旧的实现,而不必担心字节码的兼容性。如果 Oracle 的工程师在较新版本的 JVM 中更改了底层 Lambda 实现,则在较旧 JVM 上编译的 Lambda 将自动使用较新的实现,而无需开发人员进行任何更改。


1链接上的语法已过时。查看Lambda 表达式 Java Trail以了解当前语法。

于 2013-07-28T22:37:41.523 回答