【问题标题】:Implementing onChanged of MapChangeListener - why this lambda实现 MapChangeListener 的 onChanged - 为什么这个 lambda
【发布时间】:2018-05-26 19:14:33
【问题描述】:

在我的 JavaFX 中,我尝试使用 ObservableMap<String, String>MapChangeListener 来监听键和值的变化(添加/删除键或相应的值),然后执行其工作。

要让监听器生效,实现的方法是:

void onChanged(MapChangeListener.Change<? extends K,? extends V> change)

我首先使用 lambda 表达式做的,不会产生任何错误:

map.addListener((MapChangeListener.Change<? extends String, ? extends String> change) -> {
    //code here to implement onChange method
}

这是我发现的,仍然没有产生任何错误:

map.addListener((MapChangeListener<String, String>) change -> {
    //code here to implement onChange method
}

请注意这两个不同示例中圆括号的位置。在我看来,第二个似乎是演员,但我真的不明白为什么第二个选项有效。
谁能给我解释一下,好吗?

P.S.:实际上,我遇到这个是因为我正在处理一个
ObservableMap&lt;String, List&lt;String&gt;&gt;,
那是一个多图,上面两个中的第一个“方式”不起作用(通过正确的调整)。 /EDIT:我再次尝试了第一个“方式”,实际上它确实有效,我没有注意到的代码有错误END EDIT/。然后我尝试了第二个选项,它确实有效,我很茫然。然后我用一张简单的地图&lt;String, String&gt; 发现了同样的“行为”,这个问题就出现了。

【问题讨论】:

  • 给你一个额外的例子来说明差异。

标签: java javafx lambda listener


【解决方案1】:

这两个是等价的。第一个,您正在定义 lambda 表达式的参数 - 请注意,您的括号涵盖了整个更改参数。这允许编译器知道要匹配哪个重载。

第二个只是一个演员表。您正在告诉编译器要匹配此 lambda 的哪种方法签名。 (MapChangeListener&lt;String, String&gt;) 将整个 lambda 表达式转换为 MapChangeListener,因此编译器知道它确实是 addListener(MapChangeListener)。由于您已经定义了MapChangeListener 定义的单个参数,因此编译器也不会抱怨它是错误的。

编辑

现在我有更多的时间,我会给你一些具体的例子,这将帮助你更深入地理解。

public class Foo {
    public final void bar(IntfA a) {}
    public final void bar(IntfB b) {}
    public final void bar(IntfC c) {}
}

@FunctionalInterface
public interface IntfA {
    void doSomething(Double a);
}

@FunctionalInterface
public interface IntfB {
    void doSomething(Integer a);
}

@FunctionalInterface
public interface IntfC {
    void doSomething(Double a);
}

public class Test {
    public static void main(String[] args)
    {
        Foo foo = new Foo();

        foo.bar(a -> {}); // Ambiguous
        foo.bar((Integer a) -> {}); // Okay, this is IntfB
        foo.bar((Double a) -> {}); // Ambiguous between IntfA and IntfC
        foo.bar((IntfC) a -> {}); // No longer ambiguous since you specified that it's IntfC
        foo.bar((IntfC) (a, b) -> {}); // Method signature does not match IntfC
    }
}

编辑 2

您似乎需要更多帮助。

当您定义方法bar(IntfA) 时,您期望的是IntfA 的对象,无论IntfA 是接口类型还是类类型。

然后,lambda 表达式只是编译时方便的语法。当我写foo.bar((Integer a) -&gt; {})时,编译器最终会把它变成Java字节码(在.class文件中),相当于这个:

foo.bar(new IntfB() {
    public void doSomething(Integer a) {
    }
});

我们称之为Anonymous Class

使用 lambda 的最大和可能唯一的区别是,它使您的代码更短。有时它使您的代码更具可读性,有时它使您的代码可读性降低。

由于 lambda 减少了您需要输入的内容的数量,因此当存在示例中的重载方法时,很容易使 lambda 表达式对编译器产生歧义。请记住,编译器需要首先确定哪个重载,然后它会帮助您为您实例化对象。

当您编写 foo.bar((Double a) -&gt; {}) 时,编译器会注意到您有一个 lambda 表达式,该表达式接受一个 Double 参数并且不返回任何内容。然后它将查看bar() 的三个重载。它注意到bar(IntfA)bar(IntfC) 都接受一个函数接口,并且两个接口的方法都接受一个Double 参数并且什么都不返回。此时,编译器不确定是否应该生成相当于哪两组代码的字节码:

选择 1:

foo.bar(new IntfA() {
    public void doSomething(Double a) {
    }
});

选择 2:

foo.bar(new IntfC() {
    public void doSomething(Double a) {
    }
});

如果你写foo.bar((IntfC) a -&gt; {}),你已经在暗示编译器你希望它匹配foo.bar(IntfC)重载。编译器看到你有一个未知类型的参数,但由于你已经告诉它匹配IntfC,它会假定参数是Double

现在到最后一部分,调用foo.bar(IntfA) 不会自动调用IntfA 指定的doSomething(Double a) 方法。在我的示例中,bar() 方法什么也没做,但通常人们会写一些有用的东西。

再举例:

public final void bar(IntfB obj) {
    if (obj == null)
        System.out.println("I was waiting for an IntfB object but I got nothing!");
    else
        obj.doSomething(100);
}

foo.bar((Integer a) -> {
    System.out.println("I got " + a + " marks for my exam!");
});

这会导致“我的考试得了 100 分!”打印在控制台上。

【讨论】:

  • 好吧,这让我明白了更多,真的。但我有一个问题。每当我看到这个,就像在你的例子中一样,我应该期望在bar 方法的主体中有a1.doSomething(a2),其中区分a1 的类型是IntfAa2 类型的Integer。我说的对吗?
  • @Kenna 我不明白你的问题。不可能有a1.doSomething(a2)
  • 不,我的意思是foo.bar((Integer a) -&gt; {}) 给出了doSomething(Integer a) 的实现,然后调用了bar(IntfA a)。现在我应该期待在bar 内部我会打电话给doSomething(Integer a)。但是由于doSomething(Integer a)bar(IntfA a)的参数同名a,所以我想区分一下,我说a1a2。我的意思是我应该期望在bar 内部调用doSomething,否则(Integer a) -&gt; {} 将无效,因为它不会在bar 内部调用。还是我错过了什么?
  • 完美!非常感谢杰!特别是最后一部分是我感兴趣的,你让我更好地理解。事实上,我在我的 cmets 中的意思是,通常在 bar-like 方法中调用 doSomething-like 方法(使用 lambda 或方法引用或匿名类实现),但如果有't, doSomething 方法没有被调用(但是被实现了)。
  • @Kenna 看到人们试图理解总是很高兴。有些人只是学习如何复制正确的内容并粘贴到正确的位置 - 可能会奏效一两次,但并非总是如此。
【解决方案2】:

Lambda 实际上不需要表达它的类型,除非有歧义。

如果您不输入change,它将与具有相同参数长度的addListener(InvalidationListener) 冲突。有两种方法可以解决这个问题,要么显式表达类型(你的第一个 sn-p),要么将编译器引导到正确的重载(第二个),这与 lambda 语义无关。

重申第二点,说你有

void print(String s)

void print(Integer i)

打电话

print(null) 会引起歧义。解决方案是print((String)null),这当然不是类型转换,因为 null 没有类型,而是编译器注释。

【讨论】:

    猜你喜欢
    • 2019-03-26
    • 2010-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-03
    相关资源
    最近更新 更多