Две ссылки на точные методы не равны

Следующий тест терпит неудачу

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

есть ли способ сделать это?

изменить. Я попытаюсь сделать более понятным, что я пытаюсь сделать.

Предположим, что у меня есть эти классы:

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);
  }
}

теперь скажу, что я хочу написать unit test для B:

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

проблема заключается в том, что тест не выполняется, потому что ссылки метода не равны.

Ответ 1

Lambdas не кэшируются, и это кажется преднамеренным. Нет никакого способа сравнить два лямбда, чтобы увидеть, будут ли они делать то же самое.

Вам нужно сделать что-то вроде

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));
}

От Брайана Гетца; Есть ли способ сравнить lambdas?

Ответ 2

У меня нет API под рукой, но функция - это интерфейс. Integer:: parseInt, похоже, не кэшируется, поэтому он будет возвращать два разных экземпляра, которые будут сравниваться с помощью reference = > false.

Вы можете сделать это, написав Компаратор, который делает то, что вы хотите.

Ответ 3

Посмотрите на спецификацию Java Language:

15.27.4. Оценка времени лямбда-выражения.

Во время выполнения оценка выражения лямбда аналогична оценке выражения создания экземпляра класса, поскольку нормальное завершение создает ссылку на объект. Оценка лямбда-выражения отличается от выполнения лямбда-тела.

Относится и инициализируется либо новый экземпляр класса со следующими ниже свойствами, либо ссылается на существующий экземпляр класса со следующими свойствами.

...

Эти правила предназначены для обеспечения гибкости реализации Java-языка программирования, в том что:

  • При каждой оценке не нужно выделять новый объект.

  • Объекты, созданные разными лямбда-выражениями, не должны принадлежать к разным классам (если тела идентичны, например).

  • Каждый объект, созданный оценкой, не должен принадлежать одному классу (например, захваченные локальные переменные могут быть встроены).

  • Если доступен "существующий экземпляр", он не должен быть создан при предыдущей оценке лямбда (например, он мог быть выделен во время инициализации класса включения).

В принципе, это означает, что даже одно вхождение Integer::parseInt в исходный код может приводить к разным экземплярам объектов (даже разных классов) при многократном анализе, а не о множественных появлениях. Точное решение остается за реализацией JRE. См. этот ответ, в котором обсуждается текущее поведение реализации Oracles.

Ответ 4

Хорошо, что тест не проходит. Lambdas не являются объектами, они не подпадают под свойства, такие как идентичность объекта. Вместо этого они являются adhoc реализациями функциональных интерфейсов.

Я считаю, вы не должны ожидать, что ваш код будет опираться на описанное вами поведение.