Ссылка на метод в Java 8

public class Car {

    private int maxSpeed;

    public Car(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }
}

Мы можем отсортировать список автомобилей по,

    Car carX = new Car(155);
    Car carY = new Car(140);

    List<Car> cars = new ArrayList<>();
    cars.add(carX);
    cars.add(carY);

    cars.sort(Comparator.comparing(Car::getMaxSpeed));

Если мы видим сигнатуру метода Comparator.comparing, тип входного параметра Function<? super T, ? extends U>

Как в приведенном выше примере Car::getMaxSpeed приводится к Function<? super T, ? extends U>, в то время как следующее не компилируется?

  Function<Void, Integer> function = Car::getMaxSpeed;

person saravana_pc    schedule 03.06.2016    source источник
comment
В чем ошибка компилятора?   -  person Mark Rotteveel    schedule 03.06.2016
comment
Ошибка Cannot resolve method. Я пробовал оба: Car::getMaxSpeed ​​и carX::getMaxSpeed.   -  person saravana_pc    schedule 03.06.2016
comment
Я копирую/вставляю ваш код, он работает на моей стороне, похоже, это проблема IDE   -  person Nicolas Filotto    schedule 03.06.2016
comment
Пожалуйста, отредактируйте эту информацию в своем вопросе.   -  person Mark Rotteveel    schedule 03.06.2016
comment
У меня нет ошибок с Eclipse Mars.2 и JDK 1.8.0_51. В качестве примечания вы можете использовать Comparator.comparingInt, чтобы избежать бокса.   -  person Tunaki    schedule 03.06.2016
comment
Я не думаю, что Void можно использовать в качестве параметра типа в java.   -  person HopefullyHelpful    schedule 03.06.2016
comment
Кажется, тут какое-то недоразумение. Ваш первоначальный вопрос касался carX::getMaxSpeed, который сильно отличается от Car::getMaxSpeed. Большинство ответов говорят о первой версии, а не обо всех. Это сбивает с толку: ваше редактирование сделало недействительной часть ответов...   -  person Tunaki    schedule 03.06.2016
comment
@HopefulHelpful: вы можете объявить параметр типа Void, то есть ожидать экземпляр java.lang.Void, но вы вряд ли когда-нибудь увидите там что-то отличное от null, так что это не очень полезно. Дело в том, что Void не является оболочкой для void, это всего лишь заполнитель для Void.TYPE, который содержит объект Class, представляющий void.class.   -  person Holger    schedule 03.06.2016


Ответы (5)


Это потому, что метод getMaxSpeed является методом Function<Car, Integer>.

А именно:

<Car, Integer> Comparator<Car> java.util.Comparator.comparing(
    Function<? super Car, ? extends Integer> keyExtractor
)

Примечание

Чтобы сослаться на getMaxSpeed из экземпляра Car с помощью идиомы ::, вам нужно объявить метод Car с сигнатурой: getMaxSpeed(Car car).

person Mena    schedule 03.06.2016
comment
Но OP спрашивает о carX::getMaxSpeed, а не Car::getMaxSpeed, то есть о методе, уже привязанном к экземпляру. - person tobias_k; 03.06.2016
comment
хотя getMaxSpeed() не принимает параметр, он приводится к Function<Car, Integer> ? - person saravana_pc; 03.06.2016
comment
Если getMaxSpeed является функцией-членом, то для использования Car::getMaxSpeed ​​Car необходимо объявить функцию-член getMaxSpeed() (без параметров) или статическую функцию getMaxSpeed(Car) - person Hank D; 03.06.2016
comment
@saravana_pc Метод ссылается на методы экземпляра, если они написаны таким образом, добавьте приемник в качестве первого параметра. Car::getMaxSpeed эквивалентно car -> car.getMaxSpeed(). - person Louis Wasserman; 03.06.2016
comment
@saravana_pc Этот комментарий мало что добавляет к разговору, но я думаю, что есть более простой способ объяснить это: как getMaxSpeed ​​​​узнает, скорость какой машины должна быть возвращена? Если вам нужна ссылка на метод, который принимает пустоту и возвращает ее скорость... ну, это не имеет смысла, прямо здесь введите error. Какой автомобиль он должен использовать? Когда вы пишете new Car().getMaxSpeed(), функция точно знает, какой автомобиль следует использовать. Car::getMaxSpeed ​​— нет. - person Ricardo Pieper; 03.06.2016

Если вы хотите создать ссылку на метод, который не принимает параметров, например, метод, уже привязанный к экземпляру, вы должны использовать Supplier, а не Function:

Function<Car, Integer> f1 = Car::getMaxSpeed;

Car carx = new Car(42);
Supplier<Integer> f2 = carx::getMaxSpeed; 

В ссылке на метод carX::getMaxSpeed «неявный» this-параметр функции уже привязан к carx, поэтому у вас остается функция без параметров (которую, кстати, нельзя использовать в Comparator), и в Java 8 функция без параметров — это просто Supplier.

Точно так же, если у вас есть метод, который возвращает void, вы получите Comsumer:

Consumer<Integer> f3 = carx::setMaxSpeed;
person tobias_k    schedule 03.06.2016
comment
Я согласен. Проблема в том, что вы не можете использовать его в вызове Comparator.comparing. редактировать это, вероятно, не проблема, учитывая контекст, на самом деле. - person Mena; 03.06.2016

Функция-член без параметров фактически имеет скрытый параметр, ссылку this. Ссылки на методы формы ClassName::memberFunction всегда используют первый параметр функционального типа для экземпляра класса, то есть скрытый параметр this экземпляра. Итак, в случае с Car.getMaxSpeed() внутри он имеет те же параметры, что и static Integer getMaxSpeed(Car car). Таким образом, Car::getMaxSpeed соответствует функциональному типу Function<Car,Integer>, как и static Integer getMaxSpeed(Car car).

Нечто подобное происходит с функциями-членами, которые принимают один параметр — они соответствуют функциональному типу BiFunction, где первым параметром является экземпляр класса.

person Hank D    schedule 03.06.2016

Назначение:

Function<Void, Integer> function = carX::getMaxSpeed;

не компилируется, потому что это Supplier<Integer>, а не Function.

Итак, почему это компилируется?:

Comparator.comparing(Car::getMaxSpeed)

Java 8 позволяет указать ссылку на метод экземпляра, которая представляет собой Supplier<U>, вместо ожидаемого Function<T, U>, и компилятор эффективно преобразует метод получения в функцию.

Чтобы выяснить, почему это возможно, давайте посмотрим, как мы вызываем метод получения с помощью отражения:

System.out.println(Car.class.getMethod("getMaxSpeed").invoke(carX)); // "155"

При вызове invoke() в методе экземпляра мы передаем экземпляр в метод invoke() геттера Method - там подразумевается параметр типа экземпляра. Если посмотреть на это таким образом, мы увидим, что под капотом геттер действительно реализован как Function<T, U> через метод invoke().

person Bohemian♦    schedule 03.06.2016
comment
ссылка или пример для последнего утверждения? - person HopefullyHelpful; 03.06.2016
comment
@HopefulHelpful Собственный код OP является примером. Метод получения является поставщиком, но Comparator.comparing() ожидает Function. Когда тип экземпляра метода поставщика соответствует типу параметра ожидаемой функции, метод поставщика принимается как функция. - person Bohemian♦; 03.06.2016
comment
согласно ответам вампиров, это неправда. Или, по крайней мере, не убедительный пример. - person HopefullyHelpful; 03.06.2016
comment
@HopefulHelpful код в ответе вампира даже не компилируется: Car::getMaxSpeed() не является допустимым выражением Java, не говоря уже о Function<Car, Integer> . То же самое для carX::getMaxSpeed(). ИМХО, ничего собственно полезного, а тем более истинного в его ответе нет. - person Bohemian♦; 03.06.2016
comment
@HopefullHelpful См. stackoverflow.com/a/22516423/1743880 и этот комментарий Стюарта Маркса. Этот ответ правильный. - person Tunaki; 03.06.2016
comment
@HopefulHelpful См. отредактированный ответ с механикой отражения того, почему это работает. - person Bohemian♦; 03.06.2016
comment
Это не имеет ничего общего с поставщиками, и java не может волшебным образом преобразовать поставщика в функцию. Но UnaryOperator расширяет функцию и поэтому совместим. - person HopefullyHelpful; 03.06.2016
comment
@HopefulHelpful, если вы верите в это, то, возможно, вы можете объяснить, почему Comparator.comparing(Car::getMaxSpeed) компилируется, учитывая, что getMaxSpeed() не является Function<Car, Integer>. - person Bohemian♦; 03.06.2016
comment
@HopefulHelpful Кажется, что ответ Hank_D говорит по сути то же самое, что и этот ответ. - person Bohemian♦; 03.06.2016
comment
Неправильно говорить «это» Supplier<Integer>, так как ссылки на методы не имеют собственного типа. carX::getMaxSpeed совместим с Supplier<Integer> и может использоваться там, где ожидается Supplier<Integer>. Но его также можно использовать там, где ожидается Callable<Integer> или IntSupplier. Эти интерфейсы имеют одинаковую функциональную сигнатуру, но в остальном они не связаны. - person Holger; 03.06.2016
comment
Кроме того, пример Reflection вводит в заблуждение, поскольку Method.invoke имеет два разных параметра для получателя и аргументов, следовательно, вам нужно передать null в качестве первого аргумента при вызове метода static. Также Method.invoke вообще не участвует в процессе. Если вы хотите заглянуть в него, MethodHandles не делает различий между получателем и параметрами и действительно используется для указания целевого метода. - person Holger; 03.06.2016

Давайте подробно рассмотрим Function:

Interface Function<T,R> {

    default <V> Function<T,V>   andThen(Function<? super R,? extends V> after){}

    R   apply(T t);

    default <V> Function<V,R>   compose(Function<? super V,? extends T> before){}

    static <T> Function<T,T>    identity();

}

обратите внимание на R apply(T t); применяет эту функцию к данному аргументу.

Function<Void, Integer> function = Void::?????;
Void voidInstance = null;
function.apply(voidInstance);

Это не имеет смысла. Вы хотите передать Пустоту так, чтобы функция Пустоты применялась?

Несколько наглядных примеров того, что компилируется как функция

обратите внимание, что c->c.getMaxSpeed() и Car::getMaxSpeed синтаксически эквивалентны, если метод является instanceMethod. Для нестатических методов первый аргумент выводится из типа, метод которого используется, и должен быть предоставлен позже (как экземпляр, к которому метод будет выполняться/применяться).

public class Car {

    private int maxSpeed;

    public Car(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    public int getMaxSpeed() {
        return this.maxSpeed;
    }
    public Void setMaxSpeed() {
        this.maxSpeed = 12;
        return null;
    }
    public static int intStaticFunction(Void v) {
        return new Random().nextInt();
    }
    public static Void voidStaticFunction(Void v) {
        return null;
    }
    public static void main(String[] args) {
        final Car carX = new Car(155);
        final Car carY = new Car(140);

        final List<Car> cars = new ArrayList<>();
        cars.add(carX);
        cars.add(carY);

        cars.sort(Comparator.comparing(Car::getMaxSpeed));
        final Function<Car, Integer> function1 = c->c.getMaxSpeed();
        final Function<Car, Integer> function2 = Car::getMaxSpeed;
        final Function<Car, Void> function3 = Car::setMaxSpeed;
        final Function<Void, Void> function4 = n->n;
        final Function<Void, Integer> function5 = n->5;
        final Function<Void, Integer> function6 = Car::intStaticFunction;
        final Function<Void, Void> function7 = Car::voidStaticFunction;
        final Function<Car, Integer> function8 = function1::apply;
        final Function<Car, Integer> function9 = function2::apply;
        System.out.println(function1.apply(carX));
        System.out.println(function2.apply(carX));
        System.out.println(function8.apply(carX));
        System.out.println(function9.apply(carX));
        System.out.println(function3.apply(carX));
        System.out.println(function1.apply(carX));
        System.out.println(function2.apply(carX));
        System.out.println(function8.apply(carX));
        System.out.println(function9.apply(carX));
        System.out.println();
        System.out.println(function4.apply(null));
        System.out.println(function5.apply(null));
        System.out.println(function6.apply(null));
        System.out.println(function7.apply(null));
    }
}
person HopefullyHelpful    schedule 03.06.2016