Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two exact method references are not equal [duplicate]

Tags:

java

java-8

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.

like image 288
Asaf David Avatar asked Jan 28 '15 10:01

Asaf David


3 Answers

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?

like image 137
Peter Lawrey Avatar answered Oct 22 '22 14:10

Peter Lawrey


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.

like image 20
LastFreeNickname Avatar answered Oct 22 '22 13:10

LastFreeNickname


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.

like image 3
Holger Avatar answered Oct 22 '22 15:10

Holger