实现MapChangeListener的onChanged - 为什么这个lambda

时间:2017-12-13 00:42:01

标签: java javafx lambda listener

在我的JavaFX中,我尝试使用var img = document.getElementById('img'); var height = window.getComputedStyle(img,null).getPropertyValue("height"); var width= window.getComputedStyle(img,null).getPropertyValue("width"); alert(height); alert(width); ObservableMap<String, String>来监听键和值的更改(添加/删除键或相应的值),然后完成其工作。

为了使监听器有效,实现的方法是:

MapChangeListener

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

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

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

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

注意这两个不同例子中圆括号的位置。第二个似乎是我演员,但我真的不明白为什么第二个选项有效 有人可以解释一下吗?

P.S。:实际上,我遇到了这个因为我正在处理一个问题 map.addListener((MapChangeListener<String, String>) change -> { //code here to implement onChange method }
这是一个多图,上面两个的第一个“方式”不起作用(正确的调整)。 /编辑:我用第一个“方式”再次尝试,实际上它确实有效,我没注意到的代码出现了错误 END EDIT / 。然后我尝试了第二个选项,它确实有效,我很茫然。然后我用简单的地图ObservableMap<String, List<String>>发现了同样的“行为”,并且出现了这个问题。

2 个答案:

答案 0 :(得分:2)

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

第二个只是演员。您告诉编译器哪种方法签名与此lambda匹配。 (MapChangeListener<String, String>)将整个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) -> {})时,编译器最终会把它变成Java字节码(在.class文件中),这相当于:

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

我们称之为Anonymous Class

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

由于lambda会减少你需要键入的内容,因此当存在类似示例的重载方法时,很容易让lambda表达式对编译器不明确。请记住,编译器需要首先确定哪个重载,然后它将帮助您为您实例化对象。

当您编写foo.bar((Double a) -> {})时,编译会注意到您有一个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 -> {}),你已经暗示你希望它匹配foo.bar(IntfC)重载的编译器。编译器发现您有一个未知类型的参数,但由于您已经告诉它与IntfC匹配,因此它将假定该参数为Double

现在到最后一部分,调用foo.bar(IntfA)并不会自动调用doSomething(Double a)指定的IntfA方法。在我的例子中,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!");
});

这导致&#34;我的考试得到了100分!&#34;在控制台上打印。

答案 1 :(得分:1)

实际上,Lambda不需要表达其类型,除非存在歧义。

如果您不输入change,它将与具有相同参数长度的addListener(InvalidationListener)冲突。有两种方法可以通过显式表达类型(您的第一个片段)或通过将编译器指向正确的重载(第二个)来解决这个问题,这与lambda语义无关。

重申第二点,说你有

void print(String s)

void print(Integer i)

致电

print(null)会导致含糊不清。解决方案是print((String)null),当然不是类型转换,因为null没有类型,而是编译器注释。