14
public class Car {

    private int maxSpeed;

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

    public int getMaxSpeed() {
        return maxSpeed;
    }
}

We can sort a list of cars by,

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

If we see the signature of the method Comparator.comparing, the input parameter type is Function<? super T, ? extends U>

In the above example, how is Car::getMaxSpeed being cast to Function<? super T, ? extends U> while the following does not compile?

  Function<Void, Integer> function = Car::getMaxSpeed;
saravana_pc
  • 2,607
  • 11
  • 42
  • 66
  • What is the compiler error? – Mark Rotteveel Jun 03 '16 at 15:22
  • The error is `Cannot resolve method`. I tried both, Car::getMaxSpeed and carX::getMaxSpeed – saravana_pc Jun 03 '16 at 15:23
  • 2
    I copy/paste your code, it works on my side, it seems to be an IDE issue – Nicolas Filotto Jun 03 '16 at 15:24
  • Please edit that information into your question. – Mark Rotteveel Jun 03 '16 at 15:25
  • 3
    I don't have any error with Eclipse Mars.2 and JDK 1.8.0_51. As a side-note, you can use `Comparator.comparingInt` to avoid boxing. – Tunaki Jun 03 '16 at 15:28
  • I don't think Void can be used as a type parameter in java. – HopefullyHelpful Jun 03 '16 at 15:32
  • 1
    There seems to be a misunderstanding here. Your original question was about `carX::getMaxSpeed` which is very different than `Car::getMaxSpeed`. Most answers talk about the first revision, not all. This is confusing: your edit invalidated part of the answers... – Tunaki Jun 03 '16 at 15:56
  • 2
    @HopefullyHelpful: you *can* declare a parameter of type `Void`, that is, expecting an instance of `java.lang.Void`, but you are unlikely to ever see something other than `null` there, so it’s not much useful. The point is, `Void` is *not* a wrapper type for `void`, it’s only a placeholder for `Void.TYPE` which holds the `Class` object representing `void.class`. – Holger Jun 03 '16 at 16:18

5 Answers5

11

That is because the getMaxSpeed method is a Function<Car, Integer>.

Namely:

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

Note

In order to reference getMaxSpeed from an instance of Car with the :: idiom, you would have to declare a Car method with signature: getMaxSpeed(Car car).

Mena
  • 47,782
  • 11
  • 87
  • 106
  • 1
    But OP is asking about `carX::getMaxSpeed`, not `Car::getMaxSpeed`, i.e. a method already bound to an instance. – tobias_k Jun 03 '16 at 15:24
  • although getMaxSpeed() does not take in a parameter, it is being cast to `Function` ? – saravana_pc Jun 03 '16 at 15:29
  • 4
    If `getMaxSpeed` is a member function then to use Car::getMaxSpeed, `Car` would have to declare a member function `getMaxSpeed()` (no parameters) or a static function `getMaxSpeed(Car)` – Hank D Jun 03 '16 at 15:30
  • 2
    @saravana_pc Method references to instance methods, when written like that, add the receiver as the first parameter. `Car::getMaxSpeed` is equivalent to `car -> car.getMaxSpeed()`. – Louis Wasserman Jun 03 '16 at 16:53
  • @saravana_pc This comment doesn't add much to the conversation, but I think there's a simpler way to explain this: How does "getMaxSpeed" knows which car's speed has to be returned? If you want a ref to a method that "takes a void" and returns its speed... well, that doesn't make sense, type error right there. Which car should it use? When you write "new Car().getMaxSpeed()", the function definitely knows which car should be used. Car::getMaxSpeed does not. – Ricardo Pieper Jun 03 '16 at 19:00
10

If you want to create a method reference for a method that takes no parameters, such as a method already bound to an instance, you should use a Supplier, not a Function:

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

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

In the method reference carX::getMaxSpeed, the "implicit" this-parameter of the function is already bound to carx, so you are left with a no-parameter-function (which, by the way, can not be used in a Comparator), and in Java 8, a no-parameter-function is just a Supplier.

Similarly, if you have a method that returns void, you end up with a Comsumer:

Consumer<Integer> f3 = carx::setMaxSpeed;
tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • 1
    I agree. The problem is then that you can't use it in a `Comparator.comparing` invocation. **edit** that's probably *not* a problem given the context, actually. – Mena Jun 03 '16 at 15:30
4

A member function with no parameters actually has a hidden parameter, the this reference. Method references of the form ClassName::memberFunction always use the first parameter of the functional type for the class instance, i.e. the instance's hidden this parameter. So, in the case of Car.getMaxSpeed(), internally it has the same parameters as a static Integer getMaxSpeed(Car car). Car::getMaxSpeed would therefore fit the functional type Function<Car,Integer>, just as a static Integer getMaxSpeed(Car car) would.

Something similar happens with member functions that take one parameter--they fit the BiFunction functional type, with the first parameter being the class instance.

Hank D
  • 6,271
  • 2
  • 26
  • 35
2

The assignment:

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

does not compile because it's a Supplier<Integer>, not a Function.

So then, why does this compile?:

Comparator.comparing(Car::getMaxSpeed)

Java 8 allows an instance method reference that is a Supplier<U> to be provided where a Function<T, U> is expected, and the compiler effectively converts the getter method into a function.

To find out why this is possible, let's look at how we invoke a getter method using reflection:

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

When calling invoke() on an instance method, we pass the instance to the invoke() method of the getter's Method - there's an implied parameter of the instance type. When looked at it this way, we see that under the hood a getter is really implemented as a Function<T, U> via the invoke() method.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • reference or example for the last statement? – HopefullyHelpful Jun 03 '16 at 15:38
  • @HopefullyHelpful The OP's own code is an example. The getter method is a supplier, but the `Comparator.comparing()` is expecting a `Function`. When the supplier method's instance type matches the expected function's parameter type, the supplier method is accepted as a function. – Bohemian Jun 03 '16 at 15:41
  • according to vampires answer that's not true. Or atleast not a conclusive example. – HopefullyHelpful Jun 03 '16 at 15:42
  • @HopefullyHelpful the code in vampire's answer doesn't even compile: `Car::getMaxSpeed()` is not a valid java expression, let alone a `Function` . Same for `carX::getMaxSpeed()`. IMHO, there's nothing actually useful, let alone true, in his answer. – Bohemian Jun 03 '16 at 15:45
  • 1
    @HopefullyHelpful Refer to http://stackoverflow.com/a/22516423/1743880 and [this comment](http://stackoverflow.com/questions/22516331/whats-the-difference-between-instance-method-reference-types-in-java-8#comment34263784_22516423) by Stuart Marks. This answer is correct. – Tunaki Jun 03 '16 at 15:54
  • @HopefullyHelpful See edited answer with reflection mechanics of why it works. – Bohemian Jun 03 '16 at 15:54
  • This has nothing to do with suppliers, and java can't magically cast from supplier to function. But UnaryOperator extends function and thus is compatible. – HopefullyHelpful Jun 03 '16 at 15:57
  • @HopefullyHelpful if you believe that, then perhaps *you* can provide an explanation of why `Comparator.comparing(Car::getMaxSpeed)` compiles, given that `getMaxSpeed()` is not a `Function`. – Bohemian Jun 03 '16 at 15:59
  • @HopefullyHelpful It seems that [Hank_D's answer](http://stackoverflow.com/a/37618524/256196) says essentially the same thing as this answer. – Bohemian Jun 03 '16 at 16:01
  • 2
    It’s not correct to say “*it’s a*” `Supplier`, as method references have no type of their own. `carX::getMaxSpeed` *is compatible* with a `Supplier` and may be used where a `Supplier` is expected. But it could also be used where a `Callable` or an `IntSupplier` is expected. These interfaces have the same functional signature, but are otherwise unrelated. – Holger Jun 03 '16 at 18:11
  • 2
    Further, the Reflection example is misleading as `Method.invoke` has two distinct parameters for the receiver and the arguments, hence, you need pass `null` as first argument when invoking a `static` method. Also, `Method.invoke` is not involved in the process at all. If you want to peek into it, [`MethodHandle`s](https://docs.oracle.com/javase/8/docs/api/?java/lang/invoke/MethodHandle.html) make no distinction between receiver and parameters and are indeed used under the hood to specify the target method. – Holger Jun 03 '16 at 18:16
0

Let's look at Function in detail:

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

}

note the R apply(T t); Applies this function to the given argument.

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

This doesn't make sense. You want to pass a Void so that the function of the Void is applied ?

A few illustrative examples of what compiles as a function

note that c->c.getMaxSpeed() and Car::getMaxSpeed are syntactically equivalent if the method is an instanceMethod. For non-static-methods the first argument is infered from the type who's method is used, and needs to be provided later (as the instance which the method will be executed on/applied on).

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));
    }
}
HopefullyHelpful
  • 1,652
  • 3
  • 21
  • 37