Java lambda表达式,转换和比较器

时间:2015-02-13 22:09:02

标签: java casting lambda java-8

我正在查看Map界面的Java源代码,并遇到了一小段代码:

    /**
     * Returns a comparator that compares {@link Map.Entry} in natural order on value.
     *
     * <p>The returned comparator is serializable and throws {@link
     * NullPointerException} when comparing an entry with null values.
     *
     * @param <K> the type of the map keys
     * @param <V> the {@link Comparable} type of the map values
     * @return a comparator that compares {@link Map.Entry} in natural order on value.
     * @see Comparable
     * @since 1.8
     */
    public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
        return (Comparator<Map.Entry<K, V>> & Serializable)
            (c1, c2) -> c1.getValue().compareTo(c2.getValue());
    }

从方法声明中我得到的是,这是一个泛型方法,它返回一个类型的Comparator,该类型可以从传递给它的映射条目推断出来,也可以在方法中明确提供。

真正让我失望的是回报价值。看来lambda表达式

(c1, c2) -> c1.getValue().compareTo(c2.getValue());

显式地转换为Comparator<Map.Entry<K, V>>。这是对的吗?

我还注意到,明显的演员阵容包括& Serializable。我之前从未见过一个接口与一个类中的类相结合,但它在编译器中看起来有效:

((SubClass & AnInterface) anObject).interfaceMethod();

虽然以下方法不起作用:

public class Foo {
    public static void main(String[] args) {
        Object o = new Foo() {
            public void bar() {
                System.out.println("nope");
            }
        };
        ((Foo & Bar) o).bar();
    }   
}

interface Bar {
    public void bar();
}

所以,有两个问题:

  1. 如何在演员表中添加界面?这只是强制执行接口方法的返回类型吗?

  2. 您可以将Lambda表达式转换为Comparator吗?还有什么可以演绎的呢?或者lambda表达式基本上只是Comparator?有人可以澄清所有这些吗?

3 个答案:

答案 0 :(得分:16)

尽管彼得给出了一个很好的答案,但为了进一步明确,我还要添加更多答案。

lambda仅在初始化时获得其精确类型。这基于目标类型。例如:

Comparator<Integer> comp = (Integer c1, Integer c2) -> c1.compareTo(c2);
BiFunction<Integer, Integer, Integer> c = (Integer c1, Integer c2) -> c1.compareTo(c2);
Comparator<Integer> compS = (Comparator<Integer> & Serializable) (Integer c1, Integer c2) -> c1.compareTo(c2);

在所有3种情况下,它在同一个lambda之上,但它根据你提供的引用类型得到它的类型。因此,您可以在每种情况下将相同的lambda设置为3种不同的类型。

但是请注意,一旦设置了类型(初始化时),就不能再更改它了。它在字节码级别上被吸收。显然你不能将c传递给期望Comparator的方法,因为一旦初始化,它们就像普通的java对象一样。 (你可以看看这个class来玩,并在旅途中生成lambda)


以下是:

(Comparator<Map.Entry<K, V>> & Serializable)
            (c1, c2) -> c1.getValue().compareTo(c2.getValue());

lambda初始化,其目标类型为Comparator和Serializable。请注意,方法的返回类型仅为Comparator,但由于Serializable在初始化时也会记录它,因此即使此消息在方法签名中丢失,也始终可以序列化。

现在请注意,转换为lambda与((Foo & Bar) o).bar();不同。在lambda的情况下,您正在初始化lambda,其类型为声明的目标类型。但是使用((Foo & Bar) o).bar();,您将变量o压缩为Foo和Bar。在前一种情况下,您正在设置类型。在后一种情况下,它已经有一个类型,你正在尝试将它转换为其他东西。因此在前者中,它会抛出ClassCastException,因为它无法将o转换为Bar

  

如何为演员添加界面?

对于对象,就像通常一样。对于lambda,如上所述。

  

这是否只强制执行接口方法的返回类型?

没有。 Java没有Structural Types。所以没有基于该方法的特殊类型。它只是尝试将o投射到SO1Bar,但由于后者而失败

  

您可以将Lambda表达式转换为比较器吗?还有什么可以演绎的呢?或者lambda表达式本质上只是一个比较器?有人可以澄清所有这些吗?

如上所述。可以根据所有接口符合该lambda的条件将lambda初始化为任何FunctionalInterface。在上面的示例中,您显然无法将(c1, c2) -> c1.compareTo(c2)初始化为Predicate

答案 1 :(得分:15)

  

如何在演员表中添加界面?

这具有强制转换的语法,但它实际上是通过类型接口定义您正在创建的lambda的类型。即你没有创建一个对象的实例,然后被转换为另一种类型。

  

这是否只强制执行接口方法的返回类型?

这实际上定义了lambda将在运行时构建的类型。有一个LambdaMetaFactory在运行时获取此类型,如果类型包含Serializable,则会生成额外的代码。

  

您可以将Lambda表达式转换为比较器吗?

您只能对对象已经存在的类型进行引用。在这种情况下,您要定义要创建的lambda必须为Comparator。您可以使用任何只有一种抽象方法的类型。

  

或者lambda表达式本质上只是一个比较器?

可以在不同的上下文和不同的接口中使用相同的lambda代码(复制+粘贴)而无需更改。它不必是Comparator,您将在JDK中的许多其他示例中看到。

我觉得有趣的是count上的Stream方法。

答案 2 :(得分:4)

根据Java Language Specification,强制转换操作符(括号中的任何内容)可以是 ReferenceType,后跟一个或多个AdditionalBound术语,即一个或多个接口类型。此外,规范还指出如果操作数的编译时类型永远不会根据强制转换规则强制转换为强制转换运算符指定的类型,那么这是一个编译时错误。

在你的情况下,Foo没有实现Bar,但是这个事实在编译时可能并不明显,所以你得到ClassCastException,因为尽管匿名类中定义的方法具有与Bar中定义的签名相同的签名,该对象未明确实现Bar。此外,匿名类中定义的方法是隐藏的,除非在定义它们的同一语句中调用,即

new MyClass() {
  void doSomethingAwesome() { /* ... */ }
}.doSomethingAwesome();

有效,但事实并非如此:

MyClass awesome = new MyClass() {
  void doSomethingAwesome() { /* ... */ }
};
// undefined method, does not compile!
awesome.doSomethingAwesome();