Ограничения ссылок на статические методы в Java 8

Я пытаюсь использовать ссылки на методы для захвата вызовов методов и сталкиваюсь с некоторыми ограничениями. Это отлично работает:

<T> void capture(Function<T, ?> in) {
}

private interface Foo {
  String getBar();
} 

capture(Foo::getBar);

Но если я изменю подпись Foo.setBar на что-то вроде этого:

private interface Foo {
  void setBar(String bar);
}

capture(Foo::setBar);

Я получаю сообщение об ошибке:

Cannot make a static reference to the non-static method setBar(String) from the type MyTest.Foo

Что за ограничение мне непонятно. В идеале я хотел бы использовать ссылки на методы для захвата вызовов стандартного установщика. Есть какой-либо способ сделать это?


person Josh Stone    schedule 21.05.2014    source источник
comment
Это вообще Java? Или этот синтаксис является новым в Java 8?   -  person Anubian Noob    schedule 22.05.2014
comment
Не могли бы вы опубликовать ошибку?   -  person Dmitry Ginzburg    schedule 22.05.2014
comment
@AnubianNoob Это действительно ссылки на методы Java8 и т. Д. ...   -  person Sinkingpoint    schedule 22.05.2014
comment
@AnubianNoob, это Java 8, парень. Наслаждайся этим!   -  person Dmitry Ginzburg    schedule 22.05.2014
comment
дайум... Я мало занимался Java 8, это выглядит до боли интересно...   -  person Anubian Noob    schedule 22.05.2014
comment
Эм, что в этом статичного? Я не вижу модификатор static?   -  person meriton    schedule 22.05.2014
comment
@meriton: ссылка на метод фактически статическая, потому что она похожа на лямбда-выражение, которое принимает Foo и вызывает метод для этой ссылки.   -  person Jon Skeet    schedule 22.05.2014


Ответы (3)


Здесь есть две проблемы:

  • Вы используете Function, который должен что-то возвращать. setBar ничего не возвращает.
  • Function принимает только один вход, но у вас есть два входа: Foo, для которого вы вызываете setBar, и аргумент String, который вы передаете в setBar.

Если вы измените вместо этого использование BiConsumer (который имеет тип возвращаемого значения void и два входа), он будет работать нормально:

static <T, U> void capture(BiConsumer<T, U> in) {
}

Вы можете перегрузить свой метод capture, чтобы иметь обе подписи:

static <T, U> void capture(BiConsumer<T, U> in) { }
static <T> void capture(Function<T, ?> in) { }

а затем используйте обе ссылки на методы:

capture(Foo::setBar);
capture(Foo::getBar);
person Jon Skeet    schedule 21.05.2014
comment
Удивительно быстрый ответ. Спасибо! Как только SO позволит мне, я приму ваш ответ. - person Josh Stone; 22.05.2014
comment
@JoshStone: Рад, что это имеет смысл. Я мало что делал с материалом Java 8 и не писал о нем, поэтому потребуется некоторое время, чтобы отточить, как объяснять вещи. Дайте мне знать, если есть что-то, что вы хотели бы, чтобы я расширил. - person Jon Skeet; 22.05.2014
comment
@JoshStone Вы должны иметь в виду, что предложение всех возможных перегрузок (особенно после того, как вы достигнете одинаковой арности) приведет к проблемам из-за стирания дженериков во время выполнения. В настоящее время я считаю, что есть даже некоторые проблемы с похожей арностью, когда задействован void, но я думаю, что исправление этой конкретной проблемы находится в разработке. - person skiwi; 22.05.2014

Foo::getBar соответствует функции, которая принимает Foo (целевой объект) и возвращает String. Интерфейс Function<Foo, String> можно использовать для представления такой функции.

С другой стороны, Foo::setBar соответствует функции, которая принимает два аргумента: Foo (целевой объект) и String (первый параметр). Соответствующий интерфейс — BiConsumer<Foo, String>. Это означает, что вам нужна перегрузка для BiConsumer:

<T, U> void capture(BiConsumer<T, U> setter) {
    // ...
}
person nosid    schedule 21.05.2014

Выставьте ссылку на метод синтаксического сахара, вы должны увидеть, что

Foo::getBar

равно

(Foo)foo -> foo.getBar()

который Function <Foo, String>

Но

Foo::setBar

является в этом контексте функцией двух переменных (foo и некоторых String str), поэтому это не функция одной переменной (Function)

Для более удобного ответа вы должны увидеть, где ссылки на методы разрешены:

  1. Ссылка на статический метод (совсем не в этом случае)
  2. Ссылка на метод экземпляра конкретного объекта (совсем не в этом случае)
  3. Ссылка на метод экземпляра произвольного объекта определенного типа (в данном случае)

    В инструкции выше есть пример, который почти такой же как и в вашем случае. И сказано, что эквивалентное лямбда-выражение будет принимать два параметра (в данном случае Foo и String), что не является Function

  4. Ссылка на конструктор (совсем не в этом случае)

person Dmitry Ginzburg    schedule 21.05.2014