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;
8
  • What is the compiler error? Commented Jun 3, 2016 at 15:22
  • 2
    I copy/paste your code, it works on my side, it seems to be an IDE issue Commented Jun 3, 2016 at 15:24
  • 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. Commented Jun 3, 2016 at 15:28
  • 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... Commented Jun 3, 2016 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. Commented Jun 3, 2016 at 16:18

5 Answers 5

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

Sign up to request clarification or add additional context in comments.

5 Comments

But OP is asking about carX::getMaxSpeed, not Car::getMaxSpeed, i.e. a method already bound to an instance.
although getMaxSpeed() does not take in a parameter, it is being cast to Function<Car, Integer> ?
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)
@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().
@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.
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;

1 Comment

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.
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.

Comments

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.

11 Comments

reference or example for the last statement?
@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.
@HopefullyHelpful Refer to stackoverflow.com/a/22516423/1743880 and this comment by Stuart Marks. This answer is correct.
It’s not correct to say “it’s aSupplier<Integer>, as method references have no type of their own. carX::getMaxSpeed is compatible with a Supplier<Integer> and may be used where a Supplier<Integer> is expected. But it could also be used where a Callable<Integer> or an IntSupplier is expected. These interfaces have the same functional signature, but are otherwise unrelated.
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, MethodHandles make no distinction between receiver and parameters and are indeed used under the hood to specify the target method.
|
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));
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.