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

2022-09-01 20:40:37

我正在查看该接口的Java源代码,并遇到了这一小段代码:Map

    /**
     * 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());
    }

从方法声明中,我了解到这是一个泛型方法,它返回一个比较器,该比较器的类型要么是从传递给它的映射条目推断出来的,要么是在方法中显式提供的。

真正让我失望的是回报值。看来 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 表达式转换为 ?他们还能被塑造成什么样子?或者 lambda 表达式本质上只是一个 ?有人能澄清这一切吗?ComparatorComparator


答案 1

虽然彼得给出了一个很好的答案,但为了进一步澄清,让我补充更多。

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 种不同的类型。

但请注意,一旦设置了类型(初始化时),就无法再更改它。它在字节码级别被吸收。所以很明显,你不能传递给一个期望的方法,因为一旦初始化,它们就像普通的java对象一样。(您可以查看此类以在旅途中玩游戏并生成lambda)cComparator


因此,在以下情况下:

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

lambda 使用其目标类型作为 Comparator 和 Serializable 进行初始化。请注意,方法的返回类型只是 ,但由于在初始化时也将其刻录到它,因此即使此消息在方法签名中丢失,也始终可以对其进行序列化。ComparatorSerializable

现在请注意,强制转换为 lambda 与 不同。对于 lambda,您将使用其类型作为声明的目标类型来初始化 lambda。但是使用 ,您将变量类型化为 Foo 和 Bar。在前一种情况下,您正在设置类型。在后一种情况下,它已经具有一种类型,并且您正在尝试运气将其转换为其他类型。因此,在前者中,它抛出ClassCastException,因为它不能转换为((Foo & Bar) o).bar();((Foo & Bar) o).bar();ooBar

如何将接口添加到强制转换中?

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

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

不。Java 没有结构类型。所以没有基于该方法的特殊类型。它只是试图向两者投射,并且由于后者而失败oSO1Bar

能否将 Lambda 表达式转换为比较器?他们还能被塑造成什么样子?或者 lambda 表达式本质上只是一个比较器?有人能澄清这一切吗?

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


答案 2

如何将接口添加到强制转换中?

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

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

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

能否将 Lambda 表达式转换为比较器?

只能强制转换为对对象已存在的类型的引用。在本例中,您将要创建的 lambda 定义为 。您可以使用任何只具有一个抽象方法的类型。Comparator

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

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

我发现一个有趣的方法是.countStream


推荐