问题
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;
回答1:
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)
.
回答2:
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;
回答3:
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.
回答4:
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.
回答5:
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));
}
}
来源:https://stackoverflow.com/questions/37618069/method-reference-in-java-8