Java lambda表达式,转换和比较器
我正在查看Map
接口的Java源代码,并遇到了一小段代码:
/** * Returns a comparator that compares {@link Map.Entry} in natural order on value. * * The returned comparator is serializable and throws {@link * NullPointerException} when comparing an entry with null values. * * @param the type of the map keys * @param 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> Comparator<Map.Entry> comparingByValue() { return (Comparator<Map.Entry> & Serializable) (c1, c2) -> c1.getValue().compareTo(c2.getValue()); }
从方法声明中我得到的是,这是一个generics方法,它返回一个类型的Comparator,该类型可以从传递给它的映射条目推断出来,也可以在方法中明确提供。
什么真正让我失望的是回报价值。 看来lambda表达式
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
显式转换为Comparator<Map.Entry>
。 这是正确的吗?
我还注意到,明显的强制转换包括& 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(); }
那么,有两个问题:
-
如何在演员表中添加界面? 这只是强制执行接口方法的返回类型吗?
-
你能将Lambda表达式转换为
Comparator
吗? 他们还可以演绎什么呢? 或者lambda表达式本质上只是一个Comparator
? 有人可以澄清这一切吗?
如何在演员表中添加界面?
这有一个强制转换的语法,但它实际上是通过类型接口定义你正在创建的lambda的类型。 也就是说,您没有创建对象的实例,然后将其转换为其他类型。
这只是强制执行接口方法的返回类型吗?
这实际上定义了lambda将在运行时构建的类型。 有一个LambdaMetaFactory在运行时获取此类型,如果类型包含Serializable
,则生成额外的代码。
你能将Lambda表达式转换为比较器吗?
您只能对对象已经存在的类型进行引用。 在这种情况下,您定义要创建的lambda必须是Comparator
。 您可以使用任何只有一个抽象方法的类型。
或者lambda表达式本质上只是一个比较器?
可以在不同的上下文和不同的接口中使用相同的lambda代码(复制+粘贴)而无需更改。 它不必是Comparator
因为您将在JDK中的许多其他示例中看到。
我觉得有趣的是Stream
上的count
方法。
虽然彼得给出了一个很好的答案,但为了进一步明确,我还要加上更多。
lambda仅在初始化时获得其精确类型。 这基于目标类型。 例如:
Comparator comp = (Integer c1, Integer c2) -> c1.compareTo(c2); BiFunction c = (Integer c1, Integer c2) -> c1.compareTo(c2); Comparator compS = (Comparator & Serializable) (Integer c1, Integer c2) -> c1.compareTo(c2);
在所有3种情况下,它在同一个lambda之上,但它根据您提供的引用类型得到它的类型。 因此,您可以在每种情况下将相同的lambda设置为3种不同的类型。
但请注意,一旦设置了类型(初始化时),就不能再进行更改了。 它在字节码级别上被吸收。 所以显然你不能把c
传递给一个期望Comparator
的方法,因为一旦初始化它们就像普通的java对象一样。 (你可以看看这个类来玩,并在旅途中生成lambdas)
所以在以下情况下:
(Comparator> & 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没有结构类型 。 所以没有基于该方法的特殊类型。 它只是试图将o
转换为SO1
和Bar
,并且由于后者而失败
你能将Lambda表达式转换为比较器吗? 他们还可以演绎什么呢? 或者lambda表达式本质上只是一个比较器? 有人可以澄清这一切吗?
如上所述。 可以根据所有接口符合该lambda的条件,将lambda初始化为任何FunctionalInterface 。 在上面的例子中,你显然无法将(c1, c2) -> c1.compareTo(c2)
初始化为谓词
根据Java语言规范 ,强制转换操作符(括号中的任何内容)可以是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();