15

The following test fails

@Test
public void test() {
    Function<String, Integer> foo = Integer::parseInt;
    Function<String, Integer> bar = Integer::parseInt;
    assertThat(foo, equalTo(bar));
}

is there any way to make it pass?

edit: I'll try to make it more clear what I'm trying to do.

Lets say I have these classes:

class A {
  public int foo(Function<String, Integer> foo) {...}
}

class B {
  private final A a; // c'tor injected
  public int bar() {
    return a.foo(Integer::parseInt);
  }
}

now lets say i want to write unit test for B:

@Test
public void test() {
  A a = mock(A.class);
  B b = new B(a);
  b.bar();
  verify(a).foo(Integer::parseInt);
}

the problem is that the test fails, because the method references are not equal.

Lii
  • 11,553
  • 8
  • 64
  • 88
Asaf David
  • 3,583
  • 3
  • 29
  • 33
  • 1
    "this prevents me from writing a unit test for a method taking a function as argument" <-- first guess: the test does not test what it should. What if you told a little more about the test in question? – fge Jan 28 '15 at 10:51
  • I would assume the way to test that two functions are equivalent is to feed stuff into them and check the same thing comes out on the other side. – biziclop Jan 28 '15 at 10:52
  • 1
    @biziclop but you would need to test the whole domain of input values, and sometimes you just can't do it :p – fge Jan 28 '15 at 10:53
  • @fge Isn't that the fundamental problem of all unit tests? You have to pick your test values carefully. But let's ignore unit tests for a moment: how do you define equivalence between two functions? It's simple: `f(x)` and `g(x)` are equivalent if `f(x) = g(x)` for every `x`. So this is what you have to try to test. – biziclop Jan 28 '15 at 10:56
  • 1
    @biziclop that is not what unit testing is about; it is about testing behavior. The reason why I ask for what the test is about is that I think the test definition itself is not correct – fge Jan 28 '15 at 11:55
  • @fge I think we're saying basically the same thing: in a unit test you need to test behaviour, and functions have nothing but behaviour. – biziclop Jan 28 '15 at 12:36
  • 2
    @biziclop I am not saying the same thing; my hypothesis here is that it is not the function which is to be tested but the user of that function. As such its behavior should be mocked to fit the system under test. If equality then have mockito provide the `same()` reference, for instance. But somehow I doubt this is the real purpose of the test. Still, the op doesn't say so this remain a hypothesis. – fge Jan 28 '15 at 12:42

4 Answers4

15

Lambdas are not cached and this seems to be deliberate. There is no way to compare two lambdas to see if they would do the same thing.

You need to do something like

static final Function<String, Integer> parseInt = Integer::parseInt;

@Test
public void test() {
    Function<String, Integer> foo = parseInt;
    Function<String, Integer> bar = parseInt;
    assertThat(foo, equalTo(bar));
}

Answer from Brian Goetz; Is there a way to compare lambdas?

Tim B
  • 40,716
  • 16
  • 83
  • 128
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Why do you think labdas should be the same thing as method references? – bodrin Nov 26 '18 at 15:27
  • added comments to https://stackoverflow.com/questions/24095875/is-there-a-way-to-compare-lambdas/24098805#24098805 – bodrin Nov 26 '18 at 15:47
  • @bodrin All lambdas refer to an explicit or implicit method call with potentially some parameters taken from context. – Peter Lawrey Nov 26 '18 at 16:55
4

I don't have the API at hand, but Function is an interface. Integer::parseInt seems not to cache, so it will return two different instances, which will be compared by reference => false.

You can make it pass by writing a Comparator, which does what you want.

LastFreeNickname
  • 1,445
  • 10
  • 17
  • And when you think about it, how else could they be compared? – biziclop Jan 28 '15 at 10:53
  • 7
    I doubt that there is any comparator implementation which would do what OP wants, in a portable way at least. – Marko Topolnik Jan 28 '15 at 11:01
  • @MarkoTopolnik Yes, the main reason being that `MethodHandle`s aren't just simple method pointers, you can create composite or fully artificial method handles that don't point to any actual method of a real class. And how do you tell a handle of a method that does nothing but throw an `UnsupportedOperationException` is equivalent to one you created via `MethodHandles.throwException()`? – biziclop Jan 28 '15 at 11:04
  • 1
    @biziclop Are `MethodHandle`s actually involved in the resulting objects? Isn't this only used for the method reference *creation* site? I expect the result of the evaluation of a method reference to be an instance of a synthetic class implementing the requested interface with code which directly invokes the method (`invokevirtual`). – Marko Topolnik Jan 28 '15 at 11:12
  • @MarkoTopolnik They are involved in the `LambdaMetafactory`, which creates the resulting objects, so there's no way for the factory to know what that handle is for. (Well, short of inspecting the bytecode behind it.) So for this to work the API needs to change to provide more (and portable) information about what a `MethodHandle` points to. – biziclop Jan 28 '15 at 11:42
  • 1
    @biziclop I see what you mean, `MethodHandle` cannot be queried for the class/method name it will invoke. Javadoc: `[MethodHandles] are not distinguished by the name or the defining class of their underlying methods` – Marko Topolnik Jan 28 '15 at 11:50
  • 1
    The `LambdaMetafactory` *does* know the target of a `MethodHandle`. See [`revealDirect`](http://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandles.Lookup.html#revealDirect-java.lang.invoke.MethodHandle-). Otherwise it had hard times trying to generate a class calling the method… – Holger Jan 28 '15 at 12:57
3

Have look at the Java Language Specification:

15.27.4. Run-time Evaluation of Lambda Expressions

At run time, evaluation of a lambda expression is similar to evaluation of a class instance creation expression, insofar as normal completion produces a reference to an object. Evaluation of a lambda expression is distinct from execution of the lambda body.

Either a new instance of a class with the properties below is allocated and initialized, or an existing instance of a class with the properties below is referenced.

These rules are meant to offer flexibility to implementations of the Java programming language, in that:

  • A new object need not be allocated on every evaluation.

  • Objects produced by different lambda expressions need not belong to different classes (if the bodies are identical, for example).

  • Every object produced by evaluation need not belong to the same class (captured local variables might be inlined, for example).

  • If an "existing instance" is available, it need not have been created at a previous lambda evaluation (it might have been allocated during the enclosing class's initialization, for example).

In principle, this implies that even a single occurrence of Integer::parseInt in your source code may lead to different object instances (even of different classes) when being evaluated multiple times, not to speak of multiple occurrences of it. The exact decision is left to the actual JRE implementation. See this answer discussing the current behavior of Oracle’s implementation.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
0

It's OK that the test doesn't pass. Lambdas are not objects, they are not subject to properties such as object identity. Instead they are adhoc implementations of functional interfaces.

I believe you shouldn't be expecting your code to rely on the behavior you have described.

fps
  • 33,623
  • 8
  • 55
  • 110